accounts: real provider marks, from the vendors' own art
Nextcloud, Fastmail, iCloud, mailbox.org and Posteo wear their own logos instead of one shared @ glyph across six providers. Fastmail and mailbox.org are full-colour badges; the rest are tinted marks on the brand disc. Anything without usable art takes the brand's initial, which for Yandex is its own Я. Posteo's green is its real one now.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
The Fastmail mark, from their own media kit (FM-Icon-CMYK.eps, converted).
|
||||
|
||||
⚠️ Full colour and **not** tinted, unlike the monochrome marks: this one is a
|
||||
badge in its own right — the ring is part of the logo — so it fills the avatar
|
||||
edge to edge instead of sitting on a disc. A disc behind it would be a ring
|
||||
inside a circle, and flattening it to white loses the ring entirely.
|
||||
|
||||
The hexes are read off FM-Icon-RGB.png in the same kit rather than converted
|
||||
from the CMYK art, because CMYK→RGB drifts and the kit ships the screen
|
||||
values: #0067BA / #69B3E8 ring, #334553 envelope, #FCBF08 flap.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M 839.199219 296.488281 C 880.015625 358.324219 903.835938 432.363281 903.835938 512 C 903.835938 728.40625 728.402344 903.835938 512 903.835938 C 376.792969 903.835938 257.582031 835.351562 187.15625 731.183594 L 100.378906 789.035156 C 189.507812 921.199219 340.597656 1008.128906 512 1008.128906 C 786.003906 1008.128906 1008.128906 786.003906 1008.128906 512 C 1008.128906 410.960938 977.847656 317.03125 925.976562 238.636719 L 839.199219 296.488281"
|
||||
android:fillColor="#69B3E8" />
|
||||
<path
|
||||
android:pathData="M 120.164062 512 C 120.164062 295.597656 295.59375 120.164062 512 120.164062 C 648.769531 120.164062 769.109375 190.285156 839.199219 296.488281 L 925.976562 238.636719 C 837.179688 104.4375 684.964844 15.871094 512 15.871094 C 237.996094 15.871094 15.875 237.996094 15.875 512 C 15.875 614.601562 47.027344 709.921875 100.378906 789.035156 L 187.15625 731.183594 C 144.863281 668.625 120.164062 593.199219 120.164062 512"
|
||||
android:fillColor="#0067BA" />
|
||||
<path
|
||||
android:pathData="M 276.148438 669.234375 L 731.269531 669.234375 C 740.429688 669.234375 747.855469 661.808594 747.855469 652.648438 L 747.851562 354.765625 L 276.148438 669.234375"
|
||||
android:fillColor="#334553" />
|
||||
<path
|
||||
android:pathData="M 512 512 L 276.148438 354.765625 L 276.148438 669.234375 C 278.558594 667.628906 280.390625 666.402344 276.148438 669.234375 L 512 512"
|
||||
android:fillColor="#FCBF08" />
|
||||
</vector>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
The iCloud mark: Apple's cloud silhouette, from their own logo.
|
||||
|
||||
Flattened to one colour and tinted by the caller, like every other leading
|
||||
glyph — the source art fills the cloud with a #3e82f4 → #93dcf7 gradient, which
|
||||
would fight the brand-coloured disc it sits on and would need a second variant
|
||||
for dark mode. The outline is the recognisable part; the gradient is not.
|
||||
|
||||
The source viewBox is 80 × 52 — a cloud is wider than it is tall — so the
|
||||
viewport is squared off and the path nudged down by half the difference,
|
||||
because the caller sizes every mark in a square box.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="80"
|
||||
android:viewportHeight="80">
|
||||
<group android:translateY="14">
|
||||
<path
|
||||
android:pathData="M 45.864371,0.751258 A 21.518704,21.518704 0 0 0 27.127725,11.764594 11.804325,11.804325 0 0 0 21.976186,10.573131 11.804325,11.804325 0 0 0 10.35494,20.48888 16.255137,16.255137 0 0 0 0.37795502,35.481798 16.255137,16.255137 0 0 0 16.640956,51.721206 16.255137,16.255137 0 0 0 18.679799,51.57426 h 45.187983 a 15.130063,15.202083 0 0 0 0.712824,0.03447 15.130063,15.202083 0 0 0 0.679264,-0.03447 h 1.082164 V 51.49383 A 15.130063,15.202083 0 0 0 79.703385,36.406573 V 36.3774 A 15.130063,15.202083 0 0 0 67.386251,21.477308 21.518704,21.518704 0 0 0 45.864371,0.751258 Z"
|
||||
android:fillColor="#FF000000" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
The mailbox.org mark, from their own favicon-width-background.svg.
|
||||
|
||||
⚠️ Full colour and **not** tinted: their treatment is a dark mark on their
|
||||
green, not a white one, so it carries its own field — #abe659 behind
|
||||
#19353f — and is clipped to the avatar's circle by the caller. Tinting
|
||||
it white would repaint the mark in a colour mailbox.org never uses.
|
||||
|
||||
The background path overruns the viewport in the source file, which is what
|
||||
makes it bleed to the edge of the circle rather than leaving a hairline.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="512"
|
||||
android:viewportHeight="512">
|
||||
<path
|
||||
android:pathData="M-3-4.5h520.1v521H-3z"
|
||||
android:fillColor="#ABE659" />
|
||||
<path
|
||||
android:pathData="M349.7 219.1c8.5 4.8 12.1 4.8 19.3-2.4l4.8-4.8c3.6-4.8 6-7.3 4.8-13.3L337.5 72.9c-3.6-13.3-13.3-20.5-27.8-20.5h-60.4c-9.7 0-14.5 2.4-19.3 8.5l-16.9 21.8c-12.1 16.9-9.7 25.4 6 37.5m-56.8 172.7c-8.5-4.8-12.1-4.8-19.3 2.4l-4.8 4.8c-3.6 4.8-6 7.3-4.8 13.3l41.1 125.7c3.6 13.3 13.3 20.5 27.8 20.5h60.4c9.7 0 14.5-2.4 19.3-8.5l16.9-21.8c12.1-16.9 9.7-25.4-6-37.5m0-42.1c-4.8 8.5-4.8 12.1 2.4 19.3l4.8 4.8c4.8 3.6 7.3 6 13.3 4.8l125.7-41.1c13.3-3.6 20.5-13.3 20.5-27.8v-60.4c0-9.7-2.4-14.5-8.5-19.3l-21.8-16.9c-16.9-12.1-25.4-9.7-37.5 6m-172.7-56.8c4.8-8.5 4.8-12.1-2.4-19.3l-4.8-4.8c-4.8-3.6-7.3-6-13.3-4.8L72.9 174.5c-13.3 3.6-20.5 13.3-20.5 27.8v60.4c0 9.7 2.4 14.5 8.5 19.3l21.8 16.9c16.9 12.1 25.4 9.7 37.5-6"
|
||||
android:fillColor="#19353F" />
|
||||
</vector>
|
||||
@@ -1,24 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
The Nextcloud mark: three tangent circles, the middle one larger. Outlined
|
||||
rather than filled so it sits in the same monochrome family as every other
|
||||
leading glyph, and tinted by the caller so it follows the theme.
|
||||
The Nextcloud mark, lifted from their own standard logo SVG (nextcloud.com/press).
|
||||
|
||||
Replaces a hand-drawn approximation of three tangent circles. The real one is a
|
||||
single filled path, not three strokes, and the proportions are theirs rather
|
||||
than a guess at them.
|
||||
|
||||
The wordmark beside it in the source file is dropped — the row already says
|
||||
"Nextcloud" — and the mark is tinted by the caller, which is Nextcloud's own
|
||||
treatment: white on their blue.
|
||||
|
||||
The mark sits at y 35.5–90.5 in a 150 box, so it is nudged down 12 to centre.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="192"
|
||||
android:viewportHeight="192">
|
||||
<path
|
||||
android:pathData="M66,96 a30,30 0 1,0 60,0 a30,30 0 1,0 -60,0"
|
||||
android:strokeWidth="12"
|
||||
android:strokeColor="#FF000000" />
|
||||
<path
|
||||
android:pathData="M22,96 a22,22 0 1,0 44,0 a22,22 0 1,0 -44,0"
|
||||
android:strokeWidth="12"
|
||||
android:strokeColor="#FF000000" />
|
||||
<path
|
||||
android:pathData="M126,96 a22,22 0 1,0 44,0 a22,22 0 1,0 -44,0"
|
||||
android:strokeWidth="12"
|
||||
android:strokeColor="#FF000000" />
|
||||
android:viewportWidth="150"
|
||||
android:viewportHeight="150">
|
||||
<group android:translateY="12">
|
||||
<path
|
||||
android:pathData="M75.091,35.685c-12.438,0-22.981,8.433-26.248,19.857-2.84-6.06-8.994-10.305-16.082-10.305-9.748,0-17.761,8.013-17.761,17.761s8.013,17.765,17.761,17.765c7.088,0,13.242-4.248,16.082-10.309,3.268,11.426,13.81,19.861,26.248,19.861,12.346,0,22.835-8.308,26.186-19.605,2.892,5.924,8.97,10.053,15.958,10.053,9.748,0,17.765-8.017,17.765-17.765s-8.017-17.761-17.765-17.761c-6.988,0-13.065,4.127-15.958,10.05-3.352-11.296-13.84-19.601-26.186-19.601h0ZM75.091,46.11c9.39,0,16.89,7.497,16.89,16.887s-7.501,16.89-16.89,16.89-16.887-7.501-16.887-16.89,7.497-16.887,16.887-16.887ZM32.762,55.663c4.114,0,7.338,3.221,7.338,7.335s-3.225,7.338-7.338,7.338-7.335-3.225-7.335-7.338,3.221-7.335,7.335-7.335ZM117.234,55.663c4.114,0,7.338,3.221,7.338,7.335s-3.225,7.338-7.338,7.338-7.335-3.225-7.335-7.338,3.221-7.335,7.335-7.335Z"
|
||||
android:fillColor="#FF000000" />
|
||||
</group>
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
The Posteo mark: the two slashes their logo leads with, taken from their own
|
||||
posteo-logo.svg rather than drawn by eye.
|
||||
|
||||
Posteo's logo is a wordmark — "//POSTEO." white on their green, with the final
|
||||
full stop in orange — and a wordmark is the one kind of logo that cannot be a
|
||||
22dp leading glyph: at this size it is a smudge, and the row already says
|
||||
"Posteo" beside it. So the mark is the part of that logo that survives being
|
||||
small, which is their own art rather than anything invented for the purpose.
|
||||
|
||||
White on green is their own treatment, so it is tinted like the other marks.
|
||||
The pair sits at x 0.641–24.822, y 12.987–39.646 in the source file and is
|
||||
translated to centre it in a square box.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="40"
|
||||
android:viewportHeight="40">
|
||||
<group
|
||||
android:translateX="7.269"
|
||||
android:translateY="-6.316">
|
||||
<path
|
||||
android:pathData="M11.614 14.417 5.245 39.148l-4.604.498 6.37-24.731 4.603-.498z"
|
||||
android:fillColor="#FF000000" />
|
||||
<path
|
||||
android:pathData="M24.82 12.987 18.453 37.72l-4.604.498 6.369-24.732 4.604-.498z"
|
||||
android:fillColor="#FF000000" />
|
||||
</group>
|
||||
</vector>
|
||||
Reference in New Issue
Block a user