Skip to content

Blog


Lancer le mode sombre tout en construisant un système de conception évolutif

17 mars 2021

|
Laura Rodriguez

Laura Rodriguez

A large number of our DoorDash deliveries happen during the evening and in late night hours. Dashers, our delivery partners, were finding it really hard to use the Dasher app because the app’s bright screens did not adapt to the lower lighting conditions. The abundance of white in the design meant that critical information, such as pickup or dropoff instructions, order items, and even just directions to the next destination, were hard to read in the dark.

Figure 1: The bright colors in the DoorDash app can be hard on the Dasher’s eyes when delivering food at night.

Outre ce problème de lisibilité, une exposition prolongée à des écrans lumineux la nuit peut entraîner une sécheresse et des démangeaisons des yeux, une vision floue et des maux de tête. Enfin, l'augmentation de la luminosité de l'écran dans des environnements peu éclairés peut réduire l'autonomie de la batterie à un moment critique lorsque les Dashers sont sur la route. En résumé, ces problèmes se sont traduits par une expérience globalement médiocre de la plateforme et par une baisse de la satisfaction des Dashers. 

À la recherche d'une solution, quelques ingénieurs ont formé une équipe au cours d'une semaine de hackathon pour créer le mode sombre, une fonctionnalité commune à de nombreuses applications mobiles qui modifie la palette de couleurs pour des couleurs plus sombres afin de faciliter la lecture et d'épargner les yeux des utilisateurs dans des environnements plus sombres, pour nos applications Dasher. Bien que nous nous soyons concentrés sur les écrans de livraison les plus critiques, nous avons réalisé que l'expérience serait incomplète sans la prise en charge du mode sombre dans l'ensemble de l'application Dasher. La mise en œuvre du mode sombre a nécessité de relever plusieurs défis en matière de conception et d'ingénierie.

Sur le plan de la conception, le principal défi consistait à définir et à créer un thème de conception capable de passer, par programmation, du mode clair au mode sombre. En outre, l'équipe a dû concevoir de nouveaux éléments de conception, car les éléments existants ne pouvaient pas être transposés en mode sombre. En fait, il s'agissait de réarchitecturer notre système de conception pour abstraire les composants et les couleurs de l'interface utilisateur dans une sémantique d' ordre supérieur qui peut à son tour se traduire par de multiples couleurs en fonction du contexte et de l'environnement.

Sur le plan technique, toutes les couleurs utilisées dans les écrans de l'application étaient codées en dur en RVB, ce qui entraînait des incohérences, même au sein de notre propre marque. Les développeurs définissaient manuellement la même couleur rouge dans les différents écrans, ce qui augmentait le temps de développement et entraînait des erreurs de copie involontaires. En outre, ces couleurs codées en dur signifiaient que nous ne pouvions pas ajuster l'interface utilisateur de manière programmatique en fonction de l'apparence ou des conditions de luminosité.

Globalement, nous devions travailler sur une solution évolutive pour non seulement disposer d'un mode sombre personnalisé dans l'application, mais aussi pour définir notre interface utilisateur afin qu'elle s'adapte de manière transparente à la meilleure expérience Dasher pour l'environnement et les conditions actuels.

Construire le mode sombre avec un thème de conception programmatique 

Pour résoudre le problème UX et les problèmes que nos équipes frontales rencontraient avec un design unifié, nous avons construit un modèle programmatique et installé le Dark Mode. Notre équipe a d'abord créé un système de design qui pouvait représenter les marques DoorDash et Caviar dans des couleurs plus sombres, puis a implémenté le système de design ainsi que la permutation en mode sombre dans nos applications Android et iOS.  

Construire une architecture sémantique robuste pour les couleurs dans notre système de langage de conception

La prise en charge du mode sombre n'est pas aussi simple que d'appuyer sur un interrupteur ou d'inverser simplement les couleurs noir et blanc, et vice versa. La conception d'une version sombre de l'application, puis la possibilité pour les utilisateurs de basculer en toute transparence entre la version sombre et la version claire, ont nécessité un effort coordonné de la part de l'équipe. 

La première chose que nous avons faite pour concevoir le mode sombre a été d'auditer le flux d'utilisateurs On-a-Dash dans l'application Dasher. Nous avons rapidement remarqué que la prise en charge du mode sombre uniquement pour le flux On-a-Dash représentait la même quantité de travail que la prise en charge de l'ensemble de l'application, de sorte que notre audit et notre mise en œuvre se sont étendus à tous les autres écrans de l'application Dasher. Nous avons utilisé plus de 50 captures d'écran des écrans les plus représentatifs pour fournir des spécifications de couleur pour l'ensemble du texte, des icônes, des bordures et des arrière-plans. 

un exemple visuel de notre audit de conception de l'application dasher
Figure 2 : La création de l'audit de conception et des spécifications de l'application Dasher n'a pas été une mince affaire. Nous avons utilisé Themer, un plugin Figma, pour tester facilement nos maquettes en mode sombre.

The second step was to analyze our current color semantic architecture, the color names that tell us how, when, or where a color should be used. At the time, we had 121 color semantics in our design language system (DLS). We created a naming structure (i.e. Location + Property + State) that would allow us to scale in the future. As an example, ‚ÄòButton.Background.Pressed‚Äô indicates type, location, and state. 

Nous avons également élargi notre sémantique pour couvrir certains éléments manquants et ajouté un support pour tous nos composants. À la fin de ce processus, nous avons obtenu 223 sémantiques de couleurs. (Depuis le lancement du mode sombre en février 2020, nous en avons ajouté 214 autres !) Chacune de ces sémantiques de couleur a été mappée à un jeton de couleur (par exemple Red60) non seulement pour le Light Mode et le Dark Mode, mais aussi pour Caviar et notre application marchande, ce qui nous donne des capacités de thématisation complètes. 

Comparaison visuelle entre le mode clair et le mode foncé
Figure 3 : Nous avons testé notre nouvelle structure sémantique des couleurs sur tous les écrans afin de pouvoir comparer les correspondances entre le mode clair et le mode foncé.

One critical step in this process involved testing our new colors for Dark Mode in a dark room. This testing revealed that all colors, whites, and dark greys look completely different in a dark setting. For example, we don‚Äôt use full white or black in Dark Mode because white bleeds too much into nearby pixels and makes it harder to read. 

image de l'essai du mode sombre dans une pièce sombre
Figure 4 : Les tests dans une pièce sombre ont été essentiels pour ajuster nos blancs et nos noirs au niveau de luminosité correct.

Enfin, une fois que nous disposions d'un ensemble complet d'écrans issus de l'audit et de l'architecture de couleurs nécessaire, nous avons commencé à spécifier les écrans dans Figma en annotant la couleur correcte pour toutes les étiquettes de texte, les icônes, les boutons, les bordures et les arrière-plans.

exemple visuel de la sémantique des couleurs
Figure 5 : la sémantique des couleurs nous aide à savoir comment et où la couleur est utilisée, et donc à lui attribuer la valeur correcte dans différents contextes, comme le mode clair ou le mode sombre.

Une communication ouverte a été essentielle à chaque étape du processus entre les équipes de conception et d'ingénierie. Cette communication étroite nous a permis de tester et de résoudre rapidement tous les problèmes que nous avons constatés dans l'application.

visualisation de la façon dont notre équipe a cherché des erreurs de conception
Figure 6 : Nous avons organisé des soirées de test d'une heure tous les vendredis afin de trouver les bogues visuels restants.

Enfin, nous avons profité de l'occasion pour actualiser non seulement toutes les couleurs de l'application, mais aussi certains composants, tels que les boutons, les feuilles inférieures et les icônes.

Construction d'un thème de conception de système : mise en œuvre détaillée de l'iOS 

À partir d'iOS 13, Apple a introduit des couleurs système sémantiquement définies pour les éléments de l'interface utilisateur qui s'adaptent à la fois aux modes Clair et Foncé. La grande majorité de nos Dashers qui utilisent des appareils iOS utilisaient déjà la dernière version d'iOS, nous avons donc décidé de tirer parti de cette nouvelle fonctionnalité et de nous appuyer sur notre architecture de couleurs sémantiques existante dans notre DLS. 

Voici les étapes que nous avons suivies pour mettre en œuvre le mode sombre : 

S'assurer que l'application n'est pas codée en dur pour le mode lumineux

In the app’s Info.plist, make sure UIUserInterfaceStyle either is not present, or it’s set to Automatic. This will allow the UI to automatically adapt based on the system’s appearance settings.

Convertir les couleurs existantes en sémantique DLS

Par exemple, au lieu d'utiliser #FFFFFF pour un texte noir, utilisez .text(.primary) pour indiquer que la couleur utilisée est celle d'un texte qui a une fonction principale à l'écran, comme les titres. Pour ce faire, suivez les étapes suivantes :

  1. Liste de toutes les couleurs RVB utilisées dans l'application.
  2. Élaborez un tableau de conversion pour chaque couleur en fonction de son contexte. Par exemple, (#000000 - blanc) correspond à :
    • .primary(.background) pour un fond de page.
    • .button(.primary(.foreground)) pour le texte du bouton.
    • .modal(.background) pour une fenêtre modale.
  3. Passez en revue tous les fichiers Swift du projet et remplacez-les par la sémantique de couleur correspondante à l'aide du tableau de conversion ci-dessus.
  4. Le .xib sont plus difficiles à convertir car XCode n'affiche pas les résultats de recherche des fichiers .xib. Pour cela, utilisez Code Visual Studio ou un autre IDE capable de rechercher des fichiers .xib au format XML et d'y remplacer les couleurs.
  5. Veillez à retirer toute UIColor des extensions ou des bibliothèques d'aide dans la base de code qui renvoient des couleurs codées en dur, afin que les autres développeurs de l'équipe sachent qu'ils ne doivent pas les utiliser lorsqu'ils continuent à développer d'autres fonctionnalités.

Vérifier que toutes les couleurs sémantiques DLS ont une apparence définie `any` et `dark`.

Assurez-vous que tous les tokens correspondant à la sémantique des couleurs dans la liste de l'étape précédente ont des apparences claires et foncées définies. iOS 13 a facilité cette opération en permettant d'associer plusieurs tokens à une sémantique, comme le montre la figure 7 ci-dessous :

Créer des jetons pour des affichages spécifiques
Figure 7 : À partir d'iOS 13, il est possible de définir des jetons en fonction d'une apparence claire, foncée ou quelconque. 

S'assurer que chaque couleur de chaque élément de l'interface utilisateur est définie

Après avoir traité toutes les couleurs existantes dans les étapes précédentes, passez à tous les éléments qui n'ont pas de jeu de couleurs spécifique. Ces éléments utiliseraient les couleurs par défaut du système au lieu du thème DLS qui devrait être appliqué. Passez une seconde fois sur toutes les vues (programmatiques et .xibs) pour vous assurer que chaque élément a une couleur sémantique correctement définie, y compris pour l'avant-plan et l'arrière-plan. Assurez-vous que rien ne pointe vers un élément default ou system valeur.

image de l'utilisation d'Ukit
Figure 8 : UIKit définit une couleur par défaut basée sur l'élément, qui s'adaptera dynamiquement au mode sombre en fonction des couleurs du système au lieu du thème DLS.

Bien que la définition de chaque couleur pour chaque élément de l'interface utilisateur puisse sembler une tâche ardue, une façon utile de procéder consiste à remplacer temporairement et localement le thème DLS par une couleur très vive, comme le violet, chaque fois qu'une couleur sémantique DLS est correctement utilisée. Cela permet de distinguer visuellement les couleurs qui utilisent correctement le thème DLS de celles qui ne le font pas.

struct CustomTheme: ThemeProvider {
    func color(for token: ColorAssets) -> Color {
        .systemPurple
    }
}

Figure 9: Overriding the custom DLS theme to return a hard-coded, bright color can be useful to detect when the app’s elements are using UIKit’s default colors instead of the theme.

Lancez ensuite l'application et concentrez-vous sur la correction de tout ce qui n'est pas violet !

Figure 10: Anything that is not using the custom DLS theme (and thus isn’t purple) is either using UIKit’s default colors, or is hard- coded to a different value, and will need to be fixed.

Les images doivent utiliser plusieurs jetons ou être définies comme des modèles.

En ce qui concerne les images, la complexité de la ressource doit déterminer l'approche à utiliser. Pour les images simples qui ont une seule trace de couleur, définissez le jeu d'images pour qu'il soit rendu en tant qu'image modèle par défaut, et définissez la couleur de teinte comme une couleur sémantique qui changera en fonction de l'apparence claire ou foncée, comme illustré dans la figure 11, ci-dessous :

Figure 11 : En définissant l'ensemble d'images comme modèle, la couleur proviendra sémantiquement de la couleur de la teinte, qui s'adaptera aux changements d'apparence. 

Les images plus complexes comportant plusieurs traces, en particulier celles qui ne doivent pas être modifiées en fonction de l'apparence, sont mieux traitées avec un ensemble supplémentaire de ressources pour gérer le mode sombre, comme le montre la figure 12 ci-dessous :

Figure 12 : XCode permet également des apparences multiples (clair, foncé, quelconque) dans les ensembles d'images.

Les transitions entre le mode clair et le mode sombre doivent être transparentes.

Once every UI element has been updated to using a semantic DLS color value, it‚Äôs necessary  to make sure the transition between light and dark appearances is smooth, especially when the app is open. We can rely on the framework to automatically transition most of our colors, but the concept of Light and Dark Mode is implemented in UIKit, not in CoreAnimation. Cela signifie que le CoreAnimation layer does not know how to respond to an appearance change, so the color needs to be explicitly updated. For this, we can use UIKit‚Äôs traitCollectionDidChangequi est une fonction de rappel en cas de changement d'apparence.

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        if previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle {
            updateColors() // update the colors if the appearance changed.
        }
    }
Figure 13 : Le fait de surcharger le rappel pour les changements de collection de traits nous permet de mettre à jour manuellement toutes les couleurs définies au niveau de la couche CAL.

Les itérations multiples et les tests sont essentiels

L'ensemble de ce processus est une énorme révision de tous les éléments, composants et écrans de l'interface utilisateur. Itérez les tests et la correction des bogues en passant en revue tous les flux de l'application, en veillant à vérifier les apparences claires et foncées, ainsi que les transitions entre les deux. 

Développer un thème pour le mode sombre d'Android 

Lors de la mise en œuvre du mode sombre pour les appareils Android, deux options étaient possibles :

  1. ‚ÄúEasy Dark Mode‚Äù with the Force Dark option -  Android 10 and above devices

La prise en charge du mode sombre n'était pas une nouveauté sur Android, mais la mise à jour Android 10 a introduit un commutateur au niveau du système permettant de basculer entre les thèmes sombres et clairs. Comme les utilisateurs disposent désormais de cette option, ils s'attendent à ce que la plupart des applications prennent également en charge le mode sombre. 

Hence the Android framework offered the Force Dark option, a quick solution to support Dark Mode. The side effect of this shortcut is that there is minimal control over how the app looks, and the feature is only supported on devices running Android 10. This wasn‚Äôt the best option for us because we needed the same look and feel across multiple devices, not just Android 10, and we also were looking for better customizability on how we design our app in Light and Dark Modes. 

  1. Mode sombre avec une implémentation personnalisée - fonctionne pour toutes les versions supportées par Android 

La construction d'une implémentation personnalisée est une approche idéale, car elle permet d'obtenir un thème sombre personnalisé offrant un contrôle total sur la façon de définir l'aspect et la convivialité de l'application. Cette technique prend un peu de temps et nécessite plus d'heures de développement que la première approche, mais elle fonctionne sur tous les appareils, et pas seulement sur Android 10. 

Un autre avantage de cette deuxième approche est l'extensibilité ; cette architecture prend intrinsèquement en charge le multithème et ne nécessite que très peu de changements à l'avenir. 

Mise à jour du thème parent du mode clair au mode nuit

Our app currently uses Android’s AppCompat theme, and even though it’s usually recommended to switch to MaterialComponents themes, we decided not to. We made this choice because components may break or change the look and behavior of the app, requiring extensive end-to-end testing before making any big changes.

Dans notre cas, nous avons décidé de mettre à jour le thème d'AppCompat

  • De ‚ÄúTheme.AppCompat.Light.NoActionBar‚Äù à "Theme.AppCompat.DayNight.NoActionBar"
  • The DayNight theme here enables the ‚Äúvalues-night‚Äù resource qualifier.

Comme expliqué ci-dessus, s'il n'est pas possible d'effectuer une transition complète vers le thème MaterialComponents en raison de contraintes de temps ( ) et d'autres défis, héritez plutôt du thème Material Components Bridge. Les thèmes Bridge héritent fondamentalement des thèmes AppCompat, mais définissent également les nouveaux attributs du thème Material Components. Lorsque vous utilisez un thème Bridge, commencez à utiliser les composants Material Design sans modifier le thème de l'application.

Mise à jour de ‚ÄúTheme.MaterialComponents.DayNight.NoActionBar.Bridge‚Äù

Add a new “values-night” and “drawable-night” resource directory to hold resources for Dark Mode

  • Ajouter un nouveau `colors.xml` à l'intérieur du fichier de ressources `values-night` qui contient toutes les couleurs nécessaires au mode sombre. L'application utilise les ressources de ce répertoire lorsqu'elle est en mode sombre.
  • Utilisez notre DLS pour traduire toutes les couleurs RVB codées en dur en couleurs sémantiques qui décrivent l'élément plutôt qu'une couleur spécifique. Ensuite, pour chaque élément, définissez les jetons de couleur correspondants en mode sombre. 
  • Par exemple :
  1. Avant 
 <color name="text_black">#191919</color>
 <color name="color_white">#fffff</color>
 <color name="bg_whisper">#E7E7E7</color> 
  1. Après - Mode lumière
 <color name="text_primary">#191919</color>
 <color name="background_primary">#fffff</color>
 <color name="border_primary">#e7e7e7</color> 
  1. Après - Mode sombre
 <color name="text_primary">#E7E7E7</color>
 <color name="background_primary">#191919</color>
 <color name="border_primary">#313131</color> 

Ajouter un interrupteur pour basculer entre le mode lumineux et le mode sombre

Pour Android 10, il y a un interrupteur au niveau du système pour basculer entre les modes Clair et Foncé. Pour prendre en charge les versions plus anciennes d'Android, nous avons ajouté une option de basculement dans la page Paramètres de l'application qui peut être utilisée pour basculer entre les modes Clair et Foncé. 

Figure 14 : Un interrupteur intégré à l'application permet aux Dashers d'ignorer les paramètres actuels du système et de sélectionner directement un thème.

Mise en œuvre pour passer du mode lumineux au mode sombre :

fun changeAppTheme(themeType: ThemeType) {
   when (themeType) {
       ThemeType.LIGHT_MODE -> {
           AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
       }
       ThemeType.DARK_MODE -> {
           AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
       }
       else -> {
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
               AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
           } else {
               AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
           }
       }
   }
}
Figure 15 : Mise à jour du thème parent pour exploiter le paramètre actuel.

Créer un nouveau fichier de ressources nommé `themes.xml` c'est-à-dire res/values/themes.xml

Le thématisme est essentiellement la capacité de concevoir systématiquement un produit pour mieux refléter sa marque. Les thèmes et les styles sont différents. Un thème est une collection d'attributs qui font référence aux ressources de l'application et qui s'appliquent à l'ensemble de l'application ou à la hiérarchie des vues, alors que les styles ne s'appliquent qu'à une vue spécifique.

Avant de définir le niveau du système et les nouveaux attributs du thème, nous avons déplacé toute l'implémentation liée au thème dans son propre fichier appelé `themes.xml`. Cela permet de mieux organiser notre code en améliorant la lisibilité et la maintenabilité, tout en établissant une séparation claire entre les styles et les thèmes.

Configurer les attributs d'un thème d'application

Comme mentionné dans notre documentation officielle, `Un thème est une collection de ressources nommées appelées attributs de thème qui est appliquée à l'ensemble d'une application, d'une activité ou d'une hiérarchie de vues`. Ainsi, chaque vue que nous utilisons dans notre layout est intéressée par certains de ces attributs. 

Let‚Äôs look at how our app theme attributes are defined with appropriate semantic values as per our design specifications and then later referenced in our layouts and views. 

Auparavant, notre thème était incomplet, de nombreux attributs au niveau du système étant encore définis par défaut, tandis que d'autres étaient codés en dur et ne se référaient pas à nos spécifications de conception. 

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
   <item name="colorPrimary">@color/red</item>
   <item name="colorPrimaryDark">@color/@darker_red</item>
   <item name="colorAccent">@color/red</item>
   <item name="android:windowBackground">@color/background_gray</item>
   <item name="android:textColorPrimary">@color/dark_grey</item>
   <item name="android:textColorPrimaryInverse">@android:color/white</item>
   <item name="android:textColorSecondary">@color/heading_grey</item>
   <item name="android:textColorTertiary">@color/light_grey</item>
   <item name="android:textColor">@android:color/black</item>
   <item name="colorControlActivated">@color/red</item>
   <item name="colorControlNormal">@android:color/darker_gray</item>
</style>

Figure 16 : La plupart de nos couleurs ont été codées en valeurs RVB sans tenir compte du contexte.

Comme indiqué ci-dessous, nous avons ensuite mis à jour tous les attributs de thème au niveau du système avec des couleurs et des valeurs sémantiques appropriées, avec des jetons de couleur correspondants définis pour les modes Clair et Foncé dans notre DLS. 

<!-- Color palette  -->
<item name="colorPrimary">@color/primary</item>
<item name="colorAccentedPrimary">@color/text_accented_primary</item>
<item name="colorAccentedSecondary">@color/text_accented_secondary</item>
<item name="colorAccent">@color/on_accent</item>
<item name="colorOnSecondary">@color/text_primary</item>
<item name="colorOnError">@color/text_error</item>
<item name="colorOnPrimary">@color/text_primary</item>
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorOnBackground">@color/text_primary</item>
<item name="colorControlActivated">@color/red</item>
<item name="colorControlNormal">@android:color/darker_gray</item>
<item name="android:windowBackground">@color/background_primary</item>
<item name="android:textColor">@color/text_primary</item>
<item name="android:editTextColor">@color/text_primary</item>
<item name="android:colorAccent">@color/on_accent</item>
<item name="android:textColorPrimary">@color/text_primary</item>
<item name="textColorDisabled">@color/text_disabled</item>
<item name="android:textColorSecondary">@color/text_secondary</item>
Figure 17 : Mise à jour des attributs de la palette de couleurs au niveau du système, à partir de valeurs de couleur codées en dur, pour tirer parti de la DLS et des couleurs sémantiques de référence.

Outre les attributs au niveau du système, nous disposons d'une grande variété de typographies que nous utilisons dans notre application. Nous avons défini un certain nombre d'attributs de thème personnalisés en fonction de notre architecture sémantique, ce qui a permis de promouvoir la réutilisation des ressources dans l'ensemble de l'application.

Typographie

<item name="android:textAppearance">@style/TextAppearance.Regular.TextFieldText.Medium</item>
<item name="android:textAppearanceSmall">@style/TextAppearance.TextFieldText.Small</item>
<item name="android:textAppearanceMedium">@style/TextAppearance.TextFieldText.Medium</item>
<item name="android:textAppearanceLarge">@style/TextAppearance.TextFieldText.Large</item>
<item name="textAppearanceTextFieldText">@style/TextAppearance.TextFieldText.Medium</item>
<item name="textAppearanceTextFieldTextPrimary">@style/TextAppearance.TextFieldText.Medium.Primary</item>
<item name="textAppearanceRegularTextFieldTextSmall">@style/TextAppearance.Regular.TextFieldText.Small</item>
<item name="textAppearanceRegularTextFieldText">@style/TextAppearance.Regular.TextFieldText.Medium</item>
<item name="textAppearanceRegularTextFieldTextPrimary">@style/TextAppearance.Regular.TextFieldText.Medium.Primary</item>
<item name="textAppearanceRegularTextFieldTextLarge">@style/TextAppearance.Regular.TextFieldText.Large</item>
<item name="textAppearanceSmallLabel">@style/TextAppearance.SmallLabel</item>
<item name="textAppearanceTextFieldLabel">@style/TextAppearance.TextFieldLabel</item>
<item name="textAppearanceMajorPageTitle">@style/TextAppearance.MajorPageTitle</item>
<item name="textAppearancePageTitle">@style/TextAppearance.PageTitle</item>
<item name="textAppearancePageDescriptionBody">@style/TextAppearance.PageDescriptionBody</item>
<item name="textAppearancePageSubtext">@style/TextAppearance.PageSubtext</item>
<item name="textAppearanceSectionTitleLarge">@style/TextAppearance.SectionTitleLarge</item>
<item name="textAppearanceSectionTitle">@style/TextAppearance.SectionTitle</item>
<item name="textAppearanceSectionSubtext">@style/TextAppearance.SectionSubtext</item>
<item name="textAppearanceContentBody">@style/TextAppearance.ContentBody</item>
<item name="textAppearanceLabel">@style/TextAppearance.Label</item>
<item name="textAppearanceCalloutLabel">@style/TextAppearance.CalloutLabel</item>
<item name="textAppearanceNavBarTitle">@style/TextAppearance.NavBarTitle</item>
<item name="textAppearanceButtonSmall">@style/TextAppearance.ButtonSmall</item>
<item name="textAppearanceButtonMedium">@style/TextAppearance.ButtonMedium</item>
<item name="textAppearanceButtonLarge">@style/TextAppearance.ButtonLarge</item>
<item name="textAppearanceListRowTitleLarge">@style/TextAppearance.ListRowTitleLarge</item>
<item name="textAppearanceListRowSubtextLarge">@style/TextAppearance.ListRowSubtextLarge</item>
Figure 18 : Nous avons ajouté des attributs de thème pour la typographie afin de les faire correspondre à nos styles de texte DLS.

Définir les couleurs de la fenêtre et de l'arrière-plan au niveau du système Android

En plus de définir des attributs de thème au niveau de la plateforme, nous avons également ajouté de nouveaux attributs personnalisés selon les spécifications de notre langage de conception. Comme nous l'avons mentionné plus haut, chacun de ces attributs s'est ensuite vu attribuer des jetons de couleur appropriés pour les modes Clair et Foncé. 

<!-- Window colors -->
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowLightStatusBar" tools:targetApi="m">@bool/use_light_status</item>
<item name="colorBackgroundSecondary">@color/background_secondary</item>
<item name="colorBackgroundTertiary">@color/background_tertiary</item>
<item name="colorBackgroundElevated">@color/background_elevated</item>
<item name="colorBackgroundPrimaryInverted">@color/background_primary_inverted</item>
<item name="android:colorForeground">@color/on_accent</item>
<item name="android:colorBackground">@color/background_primary</item>
<item name="android:listDivider">@color/border_primary</item>
<item name="colorBorderPrimary">@color/border_primary</item>
<item name="colorBorderSecondary">@color/border_secondary</item>

<!-- Text colors -->
<item name="android:textColor">@color/text_primary</item>
<item name="android:editTextColor">@color/text_primary</item>
<item name="android:colorAccent">@color/on_accent</item>
<item name="android:textColorPrimary">@color/text_primary</item>
<item name="textColorDisabled">@color/text_disabled</item>
<item name="android:textColorSecondary">@color/text_secondary</item>
<item name="android:textColorPrimaryInverse">@android:color/white</item>
<item name="android:textColorTertiary">@color/text_tertiary</item>
<item name="textColorPositive">@color/text_positive</item>
<item name="textColorHighlight">@color/text_highlight</item>
<item name="textColorAction">@color/text_action</item>
Figure 19 : Nous avons défini des attributs de style au niveau du système Android pour les faire correspondre à nos jetons de couleur DLS.

Couleurs d'avant-plan et d'arrière-plan des boutons

<item name="colorButtonPrimaryForeground">@color/button_primary_foreground</item>
<item name="colorButtonPrimaryForegroundPressed">@color/button_primary_foreground_pressed</item>
<item name="colorButtonPrimaryForegroundHovered">@color/button_primary_foreground_hovered</item>
<item name="colorButtonPrimaryForegroundDisabled">@color/button_primary_foreground_disabled</item>
<item name="colorButtonPrimaryBackground">@color/button_primary_background</item>
<item name="colorButtonPrimaryBackgroundPressed">@color/button_primary_background_pressed</item>
<item name="colorButtonPrimaryBackgroundHovered">@color/button_primary_background_hovered</item>
<item name="colorButtonPrimaryBackgroundDisabled">@color/button_primary_background_disabled</item>
<item name="colorButtonSecondaryBackground">@color/button_tertiary_background</item>
<item name="colorButtonSecondaryToggleBackground">@color/button_secondary_toggle_background</item>
<item name="colorButtonSecondaryForeground">@color/button_tertiary_foreground</item>
<item name="colorButtonSecondaryForegroundPressed">@color/button_tertiary_foreground_pressed</item>
<item name="colorButtonSecondaryForegroundHovered">@color/button_tertiary_foreground_hovered</item>
<item name="colorButtonSecondaryForegroundDisabled">@color/button_tertiary_foreground_disabled</item>
<item name="colorButtonSecondaryBackgroundPressed">@color/button_tertiary_background_pressed</item>
<item name="colorButtonSecondaryBackgroundHovered">@color/button_tertiary_background_hovered</item>
<item name="colorButtonSecondaryBackgroundDisabled">@color/button_tertiary_background_disabled</item>

Figure 20 : Les couleurs d'avant-plan et d'arrière-plan des boutons doivent également tirer parti des abstractions DLS.

Mise à jour des thèmes Dialog et Bottom Sheet. En suivant les mêmes étapes que celles décrites ci-dessus, nous avons mis à jour les thèmes des composants Dialog et Bottom Sheet.

Mise à jour des mises en page pour faire référence aux attributs du thème

Après l'étape 5, nous avons passé en revue chacune des présentations de l'application et mis à jour les vues pour qu'elles fassent référence aux attributs du thème à partir des valeurs RVB codées en dur. 

Premier exemple

Below, we have screenshots of both Light and Dark Modes of our Promotions screen. Although they look almost identical, the Dark Mode should show a different color scheme. The reason they look the same is because the layouts were hard-coded to use white background RGB color values. 

Figure 20 : Les couleurs codées en dur donnent le même écran en clair et en foncé.

Once we apply the fix, the screens adapt based on their state. 

Figure 21 : La mise à jour des couleurs pour exploiter les couleurs DLS nouvellement créées avec des variations claires et foncées permet d'obtenir un écran qui s'adapte de manière transparente aux conditions de luminosité et d'obscurité.

Exemple deux

Ici, nous avons défini le style `AcceptDeclineInstructionsText` pour une vue de texte spécifique avec une couleur de texte codée en dur `@color/black`. Et, en raison des valeurs codées en dur, le texte est devenu totalement illisible en mode sombre. 

<TextView
        android:id="@+id/details_footer_text"
        style="@style/AcceptDeclineInstructionsText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:textColor="@color/black"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/accept_modal_instructions"
        tools:text="If you decline this order, it won\'t affect your acceptance rate."
        tools:visibility="visible" />
Figure 22 : Le codage en dur des couleurs sur les couleurs statiques du système, telles que le noir, entraîne l'affichage de la même couleur quelles que soient les conditions de luminosité. L'extrait de code ci-dessus illustre le bogue. La capture d'écran montre l'expérience visuelle qui en résulte.

Pour améliorer la lisibilité du texte en mode sombre, nous avons mis à jour le style d'affichage du texte pour qu'il fasse référence à un attribut de thème appelé `textAppearanceSectionSubtext` qui est défini dans `themes.xml`. Cet attribut de thème est simplement un style avec toutes les propriétés définies, telles que la typographie de la police, le poids et la sémantique des couleurs avec les jetons de couleur nécessaires pour les modes Clair et Foncé. Ainsi, nous sommes passés d'un réglage statique à un réglage dynamique qui permet de passer facilement du mode clair au mode foncé.

This attribute increases readability, reusability, and developer velocity, as developers don’t need to keep defining these one-off styles anymore. In addition, it gives more flexibility, as we can view component styles from one place and see it reflected throughout the app.

<TextView
        android:id="@+id/details_footer_text"
        style="?attr/textAppearanceSectionSubtext"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/accept_modal_instructions"
        tools:text="If you decline this order, it won\'t affect your acceptance rate."
        tools:visibility="visible" />
Figure 23 : l'abstraction de la couleur et du style du texte pour référencer un style DLS garantit que le texte sera visible à la fois en mode clair et en mode foncé. L'extrait de code ci-dessus résout le problème, la capture d'écran montrant le résultat final.

Gestion de l'élévation (ombre portée) en mode sombre

En mode clair, les surfaces surélevées s'appuient généralement sur une ombre portée, tout en conservant la même couleur primaire et l'arrière-plan de la surface. En mode sombre, cependant, nous ne pouvons pas nous contenter d'une ombre portée pour rehausser une surface, car l'arrière-plan est déjà sombre et la surface deviendrait presque invisible. L'inversion de l'ombre portée vers une couleur claire entraînerait un effet de brillance autour de la surface, ce qui n'est pas ce que nous voulons. 

Au lieu de cela, nous devons indiquer l'élévation en utilisant différentes teintes de fond. Plus une surface est élevée, plus la couleur de son arrière-plan est claire. Dans notre cas, nous avons dû créer une nouvelle nuance de gris à cette fin, ainsi qu'une nouvelle sémantique de couleur appelée Background.Elevated. La figure 24 ci-dessous montre un exemple de surface surélevée, une feuille de fond, et la façon dont elle passe du mode clair au mode foncé.

Figure 24: We can’t rely purely on drop shadows to indicate elevation in Dark Mode. Instead, we need to use different background tones to create separation between the primary background and an elevated surface.

Prise en charge du mode sombre dans Google Maps

Google Maps ne prend en charge que le mode lumineux, et notre application utilise Google Maps pour un certain nombre de cas d'utilisation, y compris l'affichage : 

  • Les cartes thermiques et les points de départ à proximité dans l'écran d'accueil. 
  • Points chauds à proximité lorsque les Dashers attendent des commandes.
  • La destination de prise en charge et de dépôt dans l'écran d'acceptation de la commande et lorsqu'un Dasher se rend chez un commerçant ou un consommateur.

Il est important pour nous de prendre en charge la palette de couleurs sombres de Google Maps en mode sombre. Voici comment intégrer le mode sombre dans Google Maps : 

Ce lien de documentation sur la stylisation explique comment appliquer des styles de carte à Google Maps et comprend des détails sur la façon de générer des schémas de couleurs pour les modes Clair et Foncé. 

Le fichier de configuration utilisé pour appliquer les styles de carte contient tous les détails nécessaires pour personnaliser les éléments tels que la géométrie, les waypoints, les couleurs des polylignes, l'arrière-plan et les marqueurs. Dans notre cas, nous avons défini ces éléments en utilisant nos couleurs DLS comme base de référence. 

La figure 25 ci-dessous montre la page d'accueil de Dasher, qui comprend une carte avec tous les points de départ à proximité et leur degré d'occupation.

Figure 25 : La prise en charge du mode sombre dans Google Maps implique l'application d'un style de carte personnalisé à l'expérience.

Mettre à jour toutes les références programmatiques codées en dur pour utiliser les attributs de thème 

Dans certains cas d'utilisation, il peut être nécessaire de modifier les couleurs par programmation, par exemple lorsqu'un utilisateur termine une étape du processus de commande et que la couleur change à l'écran. Comme ce changement d'affichage n'est pas défini de manière statique dans la mise en page, nous devons nous assurer qu'il est traité de la même manière. Nous avons donc identifié ces cas dans l'application et les avons mis à jour pour qu'ils fassent référence aux attributs de thème plutôt qu'aux valeurs RVB. 

Une chose à noter ici est que, lorsqu'on se réfère aux attributs de thème par programmation, il est important d'utiliser le contexte de la vue afin de récupérer les bonnes ressources. Le contexte de l'application est lié au cycle de vie de l'application et il appliquera toujours le thème et les ressources par défaut qui ont été sélectionnés lors du lancement de l'application, sans tenir compte des changements de configuration effectués ultérieurement. 

Victoires

La prise en charge du mode sombre a été un succès auprès des Dashers, car elle leur a permis d'améliorer l'expérience utilisateur, en particulier la lisibilité dans les environnements peu éclairés, la réduction de la fatigue oculaire et l'allongement de la durée de vie de la batterie, en particulier sur les écrans OLED. Après le lancement de cette expérience, nous avons reçu de nombreux éloges sur nos canaux de commentaires Dasher et nous sommes heureux de constater qu'un nombre croissant de Dashers tirent parti du mode sombre lors de leurs livraisons nocturnes.

En ce qui concerne la conception, nous avons développé et amélioré la structure de la sémantique des couleurs de notre DLS. Nous avons créé et documenté toutes les nouvelles bibliothèques de couleurs DLS (Doordash, Caviar et merchant) pour les modes clair et foncé. Cela nous a fourni une base solide pour des capacités de thématisation plus robustes dans DLS à l'avenir. En outre, cette expérience a donné à notre équipe de conception l'expertise nécessaire pour créer des versions en mode sombre de n'importe quelle fonctionnalité ou produit nouveau ou existant.

La création du mode sombre nous a poussés à construire un support de thématisation cohérent sur plusieurs plateformes. Bien qu'il s'agisse d'une tâche importante, elle nous a permis de créer plus facilement des fonctionnalités prenant en charge le mode sombre dans l'ensemble de l'entreprise. En fait, cela nous a permis de nous assurer que toute nouvelle fonctionnalité que nous avons développée à partir de ce moment-là en utilisant la sémantique DLS prendrait en charge le mode sombre dès le départ. Cela a également augmenté la productivité et la satisfaction des développeurs et a permis à l'équipe de tester plus facilement le mode clair et le mode sombre pour chaque fonctionnalité que nous livrons.

Conclusion

Supporting Dark Mode in mobile apps is a feature most users already expect and a requirement to provide beautiful and scalable UIs. DoorDash‚Äôs experience building Dark Mode is not unique. While it's quickly becoming a requirement, many companies utilize the same kind of statically built experience that does not support Dark Mode or adapt to appearance changes. This results in inconsistent and/or unreadable UI, and in the long run slows down development of new experiences. By building a semantic design structure and theming, companies can not only delight their customers with new features like Dark Mode but can also ensure better consistency and more easily apply changes in the future. 

About the Author

Emplois connexes

Job ID: 2915998
Localisation
Sao Paulo, Brazil
Département
Ingénierie
Localisation
Sunnyvale, CA
Département
Ingénierie
Localisation
Pune, Inde
Département
Ingénierie
Localisation
São Paulo, Brésil
Département
Ingénierie
Localisation
San Francisco, CA; Tempe, AZ
Département
Ingénierie