diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/AccountIdentity.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/AccountIdentity.kt
index 7f834de..6506346 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/AccountIdentity.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/AccountIdentity.kt
@@ -1,21 +1,23 @@
package de.jeanlucmakiola.agendula.ui.accounts
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.rounded.AlternateEmail
-import androidx.compose.material.icons.rounded.Cloud
import androidx.compose.material.icons.rounded.CloudSync
-import androidx.compose.material.icons.rounded.Dns
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -65,11 +67,52 @@ internal fun AccountEntity.identity(): AccountIdentity {
* White-on-brand rather than a brand-tinted glyph on a neutral chip, because it
* is the shape the marks are actually drawn in and it is the one treatment that
* needs no second colour for dark mode. Providers with no brand colour of their
- * own take the app's [MaterialTheme] accent and say what they are — a mail
- * service, a server, a cloud.
+ * own take the app's [MaterialTheme] accent.
+ *
+ * There are three treatments, and which one a provider gets is decided by how
+ * that provider actually draws itself:
+ *
+ * 1. **A badge** — [CalDavProvider.badge] — fills the circle edge to edge in its
+ * own colours. Fastmail's icon *is* a ring, so a disc behind it would be a
+ * ring inside a circle, and knocking it back to white would throw away the
+ * logo's larger half.
+ * 2. **A mark** — [CalDavProvider.mark] — is tinted white on the brand's disc,
+ * which is how Nextcloud, Apple and Posteo draw these marks themselves.
+ * 3. **A lettermark** for everything else. Material's `AlternateEmail` was doing
+ * duty for six providers at once, so a list meant to be read by mark showed
+ * one glyph six times; the brand's own initial tells them apart and claims
+ * nothing. The case comes from [CalDavProvider.label], which is why iCloud
+ * and mailbox.org keep their lowercase letterforms.
+ *
+ * A real logo beats a letter; an *approximated* logo beats neither, which is why
+ * the rest wait for their own art rather than for a good guess at it — and why
+ * every mark here is generated from the vendor's own file, not traced by eye.
*/
@Composable
internal fun ProviderLogo(provider: CalDavProvider?, size: Dp = 40.dp) {
+ val badge = provider?.badge
+ if (badge != null) {
+ // ⚠️ On white, not on nothing. The badge is a ring with a transparent
+ // middle, drawn for a white page — dropped straight onto the row it lets
+ // the surface through, and in dark mode the navy envelope inside it goes
+ // very nearly invisible. White is the background the art is drawn for, so
+ // it is the background it gets, in both themes.
+ Box(
+ modifier = Modifier
+ .size(size)
+ .clip(CircleShape)
+ .background(Color.White),
+ contentAlignment = Alignment.Center,
+ ) {
+ Image(
+ painter = painterResource(badge),
+ contentDescription = null,
+ modifier = Modifier.size(size),
+ )
+ }
+ return
+ }
+
val accent = provider?.accent
Box(
modifier = Modifier
@@ -79,36 +122,75 @@ internal fun ProviderLogo(provider: CalDavProvider?, size: Dp = 40.dp) {
contentAlignment = Alignment.Center,
) {
val tint = if (accent != null) Color.White else MaterialTheme.colorScheme.onPrimary
- val glyph = Modifier.size(size * GLYPH_FRACTION)
- if (provider == CalDavProvider.NEXTCLOUD) {
- Icon(
- painter = painterResource(R.drawable.ic_provider_nextcloud),
- contentDescription = provider.label,
+ val mark = provider?.mark
+ when {
+ mark != null -> Icon(
+ painter = painterResource(mark.res),
+ contentDescription = null,
tint = tint,
- modifier = glyph,
+ modifier = Modifier.size(size * mark.fraction),
)
- } else {
- Icon(
- imageVector = when (provider) {
- CalDavProvider.BAIKAL,
- CalDavProvider.DAVICAL,
- CalDavProvider.SOGO,
- -> Icons.Rounded.Dns
- CalDavProvider.ICLOUD -> Icons.Rounded.Cloud
-
- null -> Icons.Rounded.CloudSync
-
- else -> Icons.Rounded.AlternateEmail
- },
- contentDescription = provider?.label,
+ // A server we know nothing about has no initial to wear.
+ provider == null -> Icon(
+ imageVector = Icons.Rounded.CloudSync,
+ contentDescription = null,
tint = tint,
- modifier = glyph,
+ modifier = Modifier.size(size * GLYPH_FRACTION),
+ )
+
+ else -> Text(
+ text = provider.letter,
+ color = tint,
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = with(LocalDensity.current) { (size * LETTER_FRACTION).toSp() },
+ // The name is on the row beside it. A screen reader announcing a
+ // bare "F" before "Fastmail" is noise, not information.
+ modifier = Modifier.clearAndSetSemantics { },
)
}
}
}
+/** A provider whose official icon is a finished badge, colours and all. */
+private val CalDavProvider.badge: Int?
+ get() = when (this) {
+ CalDavProvider.FASTMAIL -> R.drawable.ic_provider_fastmail
+ CalDavProvider.MAILBOX_ORG -> R.drawable.ic_provider_mailbox
+ else -> null
+ }
+
+/**
+ * The letter a service is known by.
+ *
+ * Its own initial, except where the service's actual mark *is* a different
+ * letter: Yandex's is a Cyrillic Я, which is something we can set rather than
+ * art we would have to trace — the only vector they publish is a 64px raster.
+ */
+private val CalDavProvider.letter: String
+ get() = when (this) {
+ CalDavProvider.YANDEX -> "Я"
+ else -> label.take(1)
+ }
+
+/**
+ * A monochrome mark and how much of the disc it is given.
+ *
+ * The fraction is not one number because the marks are not one shape: a wide
+ * mark squared off into the same box reads smaller than a compact one, so it is
+ * given more room to land on the same optical weight.
+ */
+private data class Mark(val res: Int, val fraction: Float)
+
+private val CalDavProvider.mark: Mark?
+ get() = when (this) {
+ CalDavProvider.NEXTCLOUD -> Mark(R.drawable.ic_provider_nextcloud, WIDE_MARK_FRACTION)
+ CalDavProvider.ICLOUD -> Mark(R.drawable.ic_provider_icloud, WIDE_MARK_FRACTION)
+ CalDavProvider.POSTEO -> Mark(R.drawable.ic_provider_posteo, GLYPH_FRACTION)
+ else -> null
+ }
+
/**
* The provider's own brand colour, where it publishes one recognisable enough to
* be worth carrying. Null means "we would be inventing it" — Baïkal, DAViCal and
@@ -125,11 +207,19 @@ private val CalDavProvider.accent: Color?
CalDavProvider.GOOGLE -> Color(0xFF1A73E8)
CalDavProvider.FASTMAIL -> Color(0xFF2B6CB0)
CalDavProvider.MAILBOX_ORG -> Color(0xFF0069B4)
- CalDavProvider.POSTEO -> Color(0xFF5E9B23)
+ // Their own, off their app icon — not the darker green that was
+ // guessed at before the art arrived.
+ CalDavProvider.POSTEO -> Color(0xFFA9D158)
CalDavProvider.ZOHO -> Color(0xFFE42527)
- CalDavProvider.YANDEX -> Color(0xFFE03A1B)
+ CalDavProvider.YANDEX -> Color(0xFFFF2500)
CalDavProvider.BAIKAL, CalDavProvider.DAVICAL, CalDavProvider.SOGO -> null
}
/** The mark sits on the disc the way a launcher icon does — a little over half. */
private const val GLYPH_FRACTION = 0.55f
+
+/** A letter reads smaller than a glyph of the same box, so it is given less. */
+private const val LETTER_FRACTION = 0.44f
+
+/** A wide mark squared off into the same box has to be given more to match. */
+private const val WIDE_MARK_FRACTION = 0.72f
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/AddAccountMessage.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/add/AddAccountMessage.kt
similarity index 100%
rename from app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/AddAccountMessage.kt
rename to app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/add/AddAccountMessage.kt
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/AddAccountScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/add/AddAccountScreen.kt
similarity index 100%
rename from app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/AddAccountScreen.kt
rename to app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/add/AddAccountScreen.kt
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/AddAccountViewModel.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/add/AddAccountViewModel.kt
similarity index 100%
rename from app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/AddAccountViewModel.kt
rename to app/src/main/java/de/jeanlucmakiola/agendula/ui/accounts/add/AddAccountViewModel.kt
diff --git a/app/src/main/res/drawable/ic_provider_fastmail.xml b/app/src/main/res/drawable/ic_provider_fastmail.xml
new file mode 100644
index 0000000..c0f151a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_provider_fastmail.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_provider_icloud.xml b/app/src/main/res/drawable/ic_provider_icloud.xml
new file mode 100644
index 0000000..7d55522
--- /dev/null
+++ b/app/src/main/res/drawable/ic_provider_icloud.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_provider_mailbox.xml b/app/src/main/res/drawable/ic_provider_mailbox.xml
new file mode 100644
index 0000000..afec60c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_provider_mailbox.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_provider_nextcloud.xml b/app/src/main/res/drawable/ic_provider_nextcloud.xml
index 7c089d9..d069e5a 100644
--- a/app/src/main/res/drawable/ic_provider_nextcloud.xml
+++ b/app/src/main/res/drawable/ic_provider_nextcloud.xml
@@ -1,24 +1,25 @@
-
-
-
+ android:viewportWidth="150"
+ android:viewportHeight="150">
+
+
+
diff --git a/app/src/main/res/drawable/ic_provider_posteo.xml b/app/src/main/res/drawable/ic_provider_posteo.xml
new file mode 100644
index 0000000..2603095
--- /dev/null
+++ b/app/src/main/res/drawable/ic_provider_posteo.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/test/java/de/jeanlucmakiola/agendula/ui/accounts/AddAccountViewModelTest.kt b/app/src/test/java/de/jeanlucmakiola/agendula/ui/accounts/add/AddAccountViewModelTest.kt
similarity index 100%
rename from app/src/test/java/de/jeanlucmakiola/agendula/ui/accounts/AddAccountViewModelTest.kt
rename to app/src/test/java/de/jeanlucmakiola/agendula/ui/accounts/add/AddAccountViewModelTest.kt