Die Downloadzeile stand nicht still — und sagte nicht, wie weit sie ist
Aus deiner Aufnahme: „181,2 MB … 3,4 MB/s" wird „190,1 MB … 5,8 MB/s", und dabei wandert alles dahinter mit. In einer Proportionalschrift ist die 1 schmal und die 8 breit; zweimal je Sekunde ergibt das ein Zappeln. Drei Dinge halten die Zeile jetzt still: **Ziffern in fester Breite** (`monospacedDigit`) — damit kostet jede Ziffer gleich viel Platz, egal welche. **Eine feste Nachkommastelle.** `.byteCount` lässt die Null hinter dem Komma weg: aus „186,0 MB" wird „186 MB", und die Zeile ist zwei Zeichen kürzer. Gemessen an sechs aufeinanderfolgenden Sekunden war das die einzige, die noch sprang. Die Zahlen kommen deshalb über `Measurement` mit fester Stelle. **Die Prozente am rechten Rand.** Dort wachsen sie nach innen und schieben nichts; die Zeile links wächst nach rechts und schiebt ebenfalls nichts. Gemessen an acht aufeinanderfolgenden Sekunden: „343.1 / 347.0 / 350.9 / 355.0 / 359.1 / 363.5 / 368.1 / 373.0 MB" — jede Spalte beginnt auf demselben Punkt. **Ein Umweg, der nicht blieb.** Beide Zahlen in das Mass des Ziels zu zwingen hätte auch den Wechsel von MB auf GB beseitigt — machte den Anfang aber unlesbar: bei 2 GB Ziel stand dort „0,03 GB / 2 GB". Der Wechsel kommt einmal je Download; das ist der kleinere Preis. **Und die Prozente**, nach denen man eigentlich sieht. „181,2 MB von 890,2 MB" sagt dasselbe, aber man muss es ausrechnen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ad556c9034
commit
915bdcd277
@@ -261,12 +261,42 @@ private struct DownloadRow: View {
|
||||
ProgressView().progressViewStyle(.linear).tint(accent)
|
||||
}
|
||||
|
||||
// **Links die Zeile, rechts die Prozente — und beides steht
|
||||
// still.**
|
||||
//
|
||||
// Vorher war es eine einzige linksbündige Zeile, und sie zappelte
|
||||
// bei jeder Aktualisierung: in einer Proportionalschrift ist die
|
||||
// 1 schmal und die 8 breit, also verschob „181,2 → 190,1" alles
|
||||
// dahinter. Zweimal je Sekunde.
|
||||
//
|
||||
// Drei Dinge halten es jetzt ruhig: Ziffern in fester Breite, ein
|
||||
// gemeinsames Mass für beide Zahlen (siehe `unit`), und die
|
||||
// Prozente am rechten Rand — die wachsen nach innen und schieben
|
||||
// nichts.
|
||||
HStack(alignment: .firstTextBaseline, spacing: FlixySpacing.snug) {
|
||||
Text(statusLine)
|
||||
.lineLimit(1)
|
||||
Spacer(minLength: FlixySpacing.snug)
|
||||
if let percentage {
|
||||
Text(percentage)
|
||||
}
|
||||
}
|
||||
.font(.flixyCaption)
|
||||
.monospacedDigit()
|
||||
.foregroundStyle(Color.flixyTextMuted)
|
||||
}
|
||||
}
|
||||
|
||||
/// Der Fortschritt in Prozent — `nil`, solange die Gesamtgröße fehlt.
|
||||
///
|
||||
/// Die Zahl, nach der man eigentlich sieht. „181,2 MB von 890,2 MB" sagt
|
||||
/// dasselbe, aber man muss es ausrechnen.
|
||||
private var percentage: String? {
|
||||
guard item.state != .completed, let anteil = item.progress else { return nil }
|
||||
return anteil.formatted(.percent.precision(.fractionLength(0)))
|
||||
}
|
||||
|
||||
|
||||
/// Ob ein Antippen dort weitermacht, wo es aufgehört hat.
|
||||
///
|
||||
/// Ein angehaltener Download hat seinen Wiederaufnahme-Datensatz immer;
|
||||
@@ -276,13 +306,39 @@ private struct DownloadRow: View {
|
||||
item.state == .paused || item.resumeData != nil
|
||||
}
|
||||
|
||||
/// Eine Byte-Zahl mit **fester** Nachkommastelle.
|
||||
///
|
||||
/// **Der letzte Rest des Zappelns.** `.byteCount` lässt die Null hinter
|
||||
/// dem Komma weg: aus „186,0 MB" wird „186 MB", und die Zeile ist zwei
|
||||
/// Zeichen kürzer. Gemessen an sechs aufeinanderfolgenden Sekunden war das
|
||||
/// die einzige, die noch sprang.
|
||||
///
|
||||
/// `usage: .asProvided`, damit die Einheit bleibt, die hier gewählt wurde
|
||||
/// — sonst rechnet die Formatierung eigenmächtig um.
|
||||
private func size(_ value: Int64) -> String {
|
||||
let (menge, einheit): (Double, UnitInformationStorage) = switch value {
|
||||
case 1_000_000_000...: (Double(value) / 1_000_000_000, .gigabytes)
|
||||
case 1_000_000...: (Double(value) / 1_000_000, .megabytes)
|
||||
case 1_000...: (Double(value) / 1_000, .kilobytes)
|
||||
default: (Double(value), .bytes)
|
||||
}
|
||||
return Measurement(value: menge, unit: einheit).formatted(
|
||||
.measurement(
|
||||
width: .abbreviated,
|
||||
usage: .asProvided,
|
||||
numberFormatStyle: .number.precision(
|
||||
.fractionLength(einheit == .bytes ? 0 : 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// Wie schnell es gerade geht — `nil`, solange nichts gemessen ist.
|
||||
private var speed: String? {
|
||||
guard let perSecond = downloads.rate(forRatingKey: item.ratingKey)?.bytesPerSecond,
|
||||
perSecond > 0
|
||||
else { return nil }
|
||||
let menge = Int64(perSecond).formatted(.byteCount(style: .file))
|
||||
return String(localized: "downloads.perSecond \(menge)")
|
||||
return String(localized: "downloads.perSecond \(size(Int64(perSecond)))")
|
||||
}
|
||||
|
||||
/// Wie lange es bei diesem Tempo noch dauert.
|
||||
@@ -301,17 +357,19 @@ private struct DownloadRow: View {
|
||||
private var statusLine: String {
|
||||
switch item.state {
|
||||
case .downloading where item.totalBytes > 0:
|
||||
[
|
||||
"\(item.receivedBytes.formatted(.byteCount(style: .file))) / \(item.totalBytes.formatted(.byteCount(style: .file)))",
|
||||
speed,
|
||||
remaining,
|
||||
].compactMap(\.self).joined(separator: " · ")
|
||||
// **Jede Zahl in ihrem eigenen Mass.** Beide auf das des Ziels zu
|
||||
// zwingen war der naheliegende Weg gegen den Einheitenwechsel —
|
||||
// und er machte den Anfang unlesbar: bei einem Ziel von 2 GB
|
||||
// stand dort „0,03 GB / 2 GB". Gegen das Zappeln hilft die feste
|
||||
// Ziffernbreite; der Wechsel von MB auf GB kommt einmal je
|
||||
// Download und ist der kleinere Preis.
|
||||
["\(size(item.receivedBytes)) / \(size(item.totalBytes))", speed, remaining]
|
||||
.compactMap(\.self).joined(separator: " · ")
|
||||
case .downloading:
|
||||
// Ohne bekannte Gesamtgröße gibt es keine Restzeit — das Tempo
|
||||
// aber schon, und es ist genau dort die einzige Auskunft darüber,
|
||||
// ob überhaupt etwas durchgeht.
|
||||
[item.receivedBytes.formatted(.byteCount(style: .file)), speed]
|
||||
.compactMap(\.self).joined(separator: " · ")
|
||||
[size(item.receivedBytes), speed].compactMap(\.self).joined(separator: " · ")
|
||||
case .paused:
|
||||
String(localized: "downloads.paused")
|
||||
case .queued, .waiting:
|
||||
|
||||
Reference in New Issue
Block a user