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)
|
ProgressView().progressViewStyle(.linear).tint(accent)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(statusLine)
|
// **Links die Zeile, rechts die Prozente — und beides steht
|
||||||
.font(.flixyCaption)
|
// still.**
|
||||||
.foregroundStyle(Color.flixyTextMuted)
|
//
|
||||||
|
// 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.
|
/// Ob ein Antippen dort weitermacht, wo es aufgehört hat.
|
||||||
///
|
///
|
||||||
/// Ein angehaltener Download hat seinen Wiederaufnahme-Datensatz immer;
|
/// Ein angehaltener Download hat seinen Wiederaufnahme-Datensatz immer;
|
||||||
@@ -276,13 +306,39 @@ private struct DownloadRow: View {
|
|||||||
item.state == .paused || item.resumeData != nil
|
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.
|
/// Wie schnell es gerade geht — `nil`, solange nichts gemessen ist.
|
||||||
private var speed: String? {
|
private var speed: String? {
|
||||||
guard let perSecond = downloads.rate(forRatingKey: item.ratingKey)?.bytesPerSecond,
|
guard let perSecond = downloads.rate(forRatingKey: item.ratingKey)?.bytesPerSecond,
|
||||||
perSecond > 0
|
perSecond > 0
|
||||||
else { return nil }
|
else { return nil }
|
||||||
let menge = Int64(perSecond).formatted(.byteCount(style: .file))
|
return String(localized: "downloads.perSecond \(size(Int64(perSecond)))")
|
||||||
return String(localized: "downloads.perSecond \(menge)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wie lange es bei diesem Tempo noch dauert.
|
/// Wie lange es bei diesem Tempo noch dauert.
|
||||||
@@ -301,17 +357,19 @@ private struct DownloadRow: View {
|
|||||||
private var statusLine: String {
|
private var statusLine: String {
|
||||||
switch item.state {
|
switch item.state {
|
||||||
case .downloading where item.totalBytes > 0:
|
case .downloading where item.totalBytes > 0:
|
||||||
[
|
// **Jede Zahl in ihrem eigenen Mass.** Beide auf das des Ziels zu
|
||||||
"\(item.receivedBytes.formatted(.byteCount(style: .file))) / \(item.totalBytes.formatted(.byteCount(style: .file)))",
|
// zwingen war der naheliegende Weg gegen den Einheitenwechsel —
|
||||||
speed,
|
// und er machte den Anfang unlesbar: bei einem Ziel von 2 GB
|
||||||
remaining,
|
// stand dort „0,03 GB / 2 GB". Gegen das Zappeln hilft die feste
|
||||||
].compactMap(\.self).joined(separator: " · ")
|
// 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:
|
case .downloading:
|
||||||
// Ohne bekannte Gesamtgröße gibt es keine Restzeit — das Tempo
|
// Ohne bekannte Gesamtgröße gibt es keine Restzeit — das Tempo
|
||||||
// aber schon, und es ist genau dort die einzige Auskunft darüber,
|
// aber schon, und es ist genau dort die einzige Auskunft darüber,
|
||||||
// ob überhaupt etwas durchgeht.
|
// ob überhaupt etwas durchgeht.
|
||||||
[item.receivedBytes.formatted(.byteCount(style: .file)), speed]
|
[size(item.receivedBytes), speed].compactMap(\.self).joined(separator: " · ")
|
||||||
.compactMap(\.self).joined(separator: " · ")
|
|
||||||
case .paused:
|
case .paused:
|
||||||
String(localized: "downloads.paused")
|
String(localized: "downloads.paused")
|
||||||
case .queued, .waiting:
|
case .queued, .waiting:
|
||||||
|
|||||||
Reference in New Issue
Block a user