diff --git a/AGENTS.md b/AGENTS.md
index 57846e0..affd06f 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -29,7 +29,8 @@ BalanceWatch/
├── README.md
├── .github/workflows/build.yml # CI: сборка для симулятора и устройства
├── docs/CONTRACT_V2.md # общий контракт экосистемы v2 (копия)
-├── branding/ # аватар репозитория, не входит в бандл
+├── branding/ # icon.svg, avatar, banner, render.sh; не входит в бандл
+├── docs/ # CONTRACT_V2.md, скриншоты для баннера
├── BalanceWatch.xcodeproj/ # один таргет и одна схема — BalanceWatch
└── BalanceWatch/
├── App/
@@ -210,3 +211,11 @@ SwiftData/SwiftUI, а `FinanceCalculations` работает через прот
- [ ] Секреты и токены не попали в репозиторий и логи.
- [ ] CI собирает проект для симулятора и для устройства.
- [ ] README/AGENTS.md обновлены, если поменялась структура или сборка.
+
+## Оформление
+
+- Цвета интерфейса — только из `BalancePalette` (`Shared/AppTheme.swift`),
+ палитра «Лес», общая с BalanceFlutter. Не возвращай indigo/purple.
+- Иконка, аватар и баннер рендерятся из SVG: `bash branding/render.sh`
+ (нужен `resvg`). PNG в `AppIcon.appiconset` руками не правь.
+- Аватар репозитория в Gitea обновляется из `branding/avatar.png`.
diff --git a/BalanceWatch/App/BalanceWatchApp.swift b/BalanceWatch/App/BalanceWatchApp.swift
index 2d32453..e1aae93 100644
--- a/BalanceWatch/App/BalanceWatchApp.swift
+++ b/BalanceWatch/App/BalanceWatchApp.swift
@@ -11,7 +11,7 @@ struct BalanceWatchApp: App {
WindowGroup {
WatchRootView()
.id(ledgerScope)
- .tint(.indigo)
+ .tint(BalancePalette.accent)
.preferredColorScheme(AppTheme(rawValue: appTheme)?.colorScheme)
.modelContainer(containers.container(for: ledgerScope))
}
diff --git a/BalanceWatch/App/WatchAccountsView.swift b/BalanceWatch/App/WatchAccountsView.swift
index 5af6157..feb0796 100644
--- a/BalanceWatch/App/WatchAccountsView.swift
+++ b/BalanceWatch/App/WatchAccountsView.swift
@@ -80,7 +80,7 @@ struct WatchAccountsView: View {
VStack(alignment: .leading) {
Text(title).lineLimit(1)
Text(Money.double(cents).formattedCurrency(code: code))
- .font(.caption2).foregroundStyle(cents < 0 ? .red : .secondary)
+ .font(.caption2).foregroundStyle(cents < 0 ? BalancePalette.expense : .secondary)
}
}
}
diff --git a/BalanceWatch/App/WatchFeatures.swift b/BalanceWatch/App/WatchFeatures.swift
index f6389f6..33053f9 100644
--- a/BalanceWatch/App/WatchFeatures.swift
+++ b/BalanceWatch/App/WatchFeatures.swift
@@ -55,8 +55,8 @@ struct WatchAnalyticsView: View {
}
Section("Итоги") {
- metric("Доходы", summary.income, .green)
- metric("Расходы", summary.expenses, .red)
+ metric("Доходы", summary.income, BalancePalette.income)
+ metric("Расходы", summary.expenses, BalancePalette.expense)
LabeledContent("Результат", value: summary.balance.formattedCurrency(code: currencyCode))
LabeledContent(
"Сбережения",
@@ -100,7 +100,7 @@ struct WatchAnalyticsView: View {
private func tint(for item: CategorySpending) -> Color {
item.colorName == "auto"
- ? FinanceCategory.find(named: item.name)?.tint ?? .indigo
+ ? FinanceCategory.find(named: item.name)?.tint ?? BalancePalette.forest
: FinanceCategory.color(named: item.colorName)
}
}
@@ -173,7 +173,7 @@ struct WatchTransactionsView: View {
private func tint(_ transaction: FinanceTransaction) -> Color {
transaction.categoryColorName == "auto"
- ? FinanceCategory.find(named: transaction.categoryName)?.tint ?? .indigo
+ ? FinanceCategory.find(named: transaction.categoryName)?.tint ?? BalancePalette.forest
: FinanceCategory.color(named: transaction.categoryColorName)
}
}
diff --git a/BalanceWatch/App/WatchRootView.swift b/BalanceWatch/App/WatchRootView.swift
index 433c71a..39ce147 100644
--- a/BalanceWatch/App/WatchRootView.swift
+++ b/BalanceWatch/App/WatchRootView.swift
@@ -33,9 +33,13 @@ private struct WatchHomeView: View {
ScrollView {
VStack(alignment: .leading, spacing: 12) {
VStack(alignment: .leading, spacing: 3) {
- Text("Общий баланс")
- .font(.caption)
- .foregroundStyle(.secondary)
+ HStack(spacing: 5) {
+ Image(systemName: "wallet.bifold")
+ .font(.caption2.weight(.semibold))
+ Text("Общий баланс")
+ .font(.caption)
+ }
+ .foregroundStyle(BalancePalette.mint.opacity(0.85))
Text(total.value.formattedCurrency(code: currencyCode))
.font(.title3.bold())
.minimumScaleFactor(0.7)
@@ -49,7 +53,7 @@ private struct WatchHomeView: View {
.padding(12)
.background(
LinearGradient(
- colors: [.indigo, .purple],
+ colors: [BalancePalette.cardTop, BalancePalette.cardBottom],
startPoint: .topLeading,
endPoint: .bottomTrailing
),
@@ -60,13 +64,15 @@ private struct WatchHomeView: View {
HStack(spacing: 8) {
watchMetric(
title: "Доходы",
+ symbol: "arrow.down.left",
amount: monthSummary.income,
- tint: .green
+ tint: BalancePalette.income
)
watchMetric(
title: "Расходы",
+ symbol: "arrow.up.right",
amount: monthSummary.expenses,
- tint: .red
+ tint: BalancePalette.expense
)
}
@@ -77,6 +83,7 @@ private struct WatchHomeView: View {
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
+ .tint(BalancePalette.forest)
VStack(spacing: 4) {
if account.supportsV2 {
@@ -137,7 +144,7 @@ private struct WatchHomeView: View {
Text((transaction.kind == .income ? "+" : "−")
+ transaction.amount.formattedCurrency(code: currency.currency(of: transaction)))
.font(.caption2.weight(.semibold))
- .foregroundStyle(transaction.kind == .income ? .green : .secondary)
+ .foregroundStyle(transaction.kind == .income ? BalancePalette.income : .secondary)
}
Spacer()
}
@@ -158,25 +165,38 @@ private struct WatchHomeView: View {
}
}
- private func watchMetric(title: String, amount: Double, tint: Color) -> some View {
+ private func watchMetric(title: String, symbol: String, amount: Double, tint: Color) -> some View {
VStack(alignment: .leading, spacing: 3) {
- Text(title)
+ Label(title, systemImage: symbol)
.font(.caption2)
.foregroundStyle(.secondary)
+ .labelStyle(TintedIconLabelStyle(tint: tint))
Text(amount.formattedCurrency(code: currencyCode))
.font(.caption.bold())
+ .foregroundStyle(tint)
.lineLimit(1)
.minimumScaleFactor(0.65)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(9)
- .background(tint.opacity(0.12), in: RoundedRectangle(cornerRadius: 12))
+ .background(tint.opacity(0.14), in: RoundedRectangle(cornerRadius: 12))
}
private func transactionTint(_ transaction: FinanceTransaction) -> Color {
if transaction.categoryColorName == "auto" {
- return FinanceCategory.find(named: transaction.categoryName)?.tint ?? .indigo
+ return FinanceCategory.find(named: transaction.categoryName)?.tint ?? BalancePalette.forest
}
return FinanceCategory.color(named: transaction.categoryColorName)
}
}
+
+/// Иконка метрики в цвете метрики, подпись — вторичным цветом.
+private struct TintedIconLabelStyle: LabelStyle {
+ let tint: Color
+ func makeBody(configuration: Configuration) -> some View {
+ HStack(spacing: 3) {
+ configuration.icon.foregroundStyle(tint)
+ configuration.title
+ }
+ }
+}
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-100.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-100.png
index 5375cd4..94e3168 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-100.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-100.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-102.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-102.png
index bd70565..f764bdf 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-102.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-102.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-1024.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-1024.png
index 2c212db..52ce2df 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-1024.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-1024.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-108.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-108.png
index 6c375f4..64d25e6 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-108.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-108.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-172.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-172.png
index 043000f..68243a6 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-172.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-172.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-196.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-196.png
index 3983c3d..7862670 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-196.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-196.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-216.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-216.png
index 2f00659..e14c6b2 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-216.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-216.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-234.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-234.png
index 7dc91af..5bcb394 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-234.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-234.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-258.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-258.png
index 6be4a87..62d4619 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-258.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-258.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-48.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-48.png
index 990b9a8..7bd2d90 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-48.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-48.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-55.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-55.png
index 9abc1e5..4f7132f 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-55.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-55.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-58.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-58.png
index 726cb0d..fe17a43 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-58.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-58.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-80.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-80.png
index 73f3023..f83d323 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-80.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-80.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-87.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-87.png
index 4608e52..5f5fb77 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-87.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-87.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-88.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-88.png
index b81ecc8..be8d9a6 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-88.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-88.png differ
diff --git a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-92.png b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-92.png
index 4a41132..b280e52 100644
Binary files a/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-92.png and b/BalanceWatch/Assets.xcassets/AppIcon.appiconset/BalanceIcon-92.png differ
diff --git a/BalanceWatch/Shared/AppTheme.swift b/BalanceWatch/Shared/AppTheme.swift
index 56347e6..3358d86 100644
--- a/BalanceWatch/Shared/AppTheme.swift
+++ b/BalanceWatch/Shared/AppTheme.swift
@@ -23,3 +23,20 @@ enum AppTheme: String, CaseIterable, Identifiable {
}
}
}
+
+/// Палитра «Лес» — та же, что у пресета по умолчанию во Flutter-клиенте и у
+/// иконки приложения.
+enum BalancePalette {
+ /// Акцент интерфейса (кнопки, ссылки, выбор) — мятный, читается на чёрном.
+ static let accent = Color(red: 0x7f / 255, green: 0xd6 / 255, blue: 0xb9 / 255)
+ /// Основной цвет пресета «Лес».
+ static let forest = Color(red: 0x35 / 255, green: 0x68 / 255, blue: 0x59 / 255)
+ /// Градиент карточки общего баланса (как фон иконки).
+ static let cardTop = Color(red: 0x3f / 255, green: 0x7d / 255, blue: 0x6b / 255)
+ static let cardBottom = Color(red: 0x1a / 255, green: 0x4a / 255, blue: 0x3e / 255)
+ /// Текст на карточке баланса.
+ static let mint = Color(red: 0xbd / 255, green: 0xf7 / 255, blue: 0xe3 / 255)
+ /// Доходы — teal, расходы — deep orange, как во Flutter.
+ static let income = Color(red: 0x26 / 255, green: 0xb3 / 255, blue: 0xa3 / 255)
+ static let expense = Color(red: 0xff / 255, green: 0x6e / 255, blue: 0x40 / 255)
+}
diff --git a/BalanceWatch/Utilities/CurrencyFormatting.swift b/BalanceWatch/Utilities/CurrencyFormatting.swift
index 04b78d8..7eeed58 100644
--- a/BalanceWatch/Utilities/CurrencyFormatting.swift
+++ b/BalanceWatch/Utilities/CurrencyFormatting.swift
@@ -10,10 +10,13 @@ enum SupportedCurrency: String, CaseIterable, Identifiable {
}
extension Double {
+ /// Целые суммы без копеек, остальные — ровно с двумя знаками («8 480,50 ₽»),
+ /// как во Flutter-клиенте.
func formattedCurrency(code: String) -> String {
- formatted(
+ let whole = (self * 100).rounded().truncatingRemainder(dividingBy: 100) == 0
+ return formatted(
.currency(code: code)
- .precision(.fractionLength(0 ... 2))
+ .precision(.fractionLength(whole ? 0 : 2))
.presentation(.narrow)
)
}
diff --git a/README.md b/README.md
index 4fddfc8..54675e7 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
# BalanceWatch
@@ -13,7 +13,8 @@
```text
BalanceWatch/
-├── branding/ # аватар и баннер для README (PNG + SVG-исходники)
+├── branding/ # иконка, аватар и баннер (SVG-исходники, PNG, render.sh)
+├── docs/ # скриншоты для баннера
├── docs/CONTRACT_V2.md # общий контракт экосистемы v2 (копия, одинаковая во всех репозиториях)
├── BalanceWatch.xcodeproj/
└── BalanceWatch/
@@ -75,3 +76,15 @@ Simulator и watchOS device (без подписи). Запускается на
кода из монорепозитория. Протокол синхронизации и правила расчётов должны
совпадать с остальными клиентами Balance: при изменении контракта на
сервере переносите правки и сюда.
+
+## Оформление
+
+Палитра «Лес» — та же, что у пресета по умолчанию в BalanceFlutter: карточка
+баланса в зелёном градиенте, доходы бирюзовые (↙), расходы оранжевые (↗),
+мятный акцент (`BalancePalette` в `Shared/AppTheme.swift`). Иконка — тот же
+рисунок, что у Flutter-приложения (`branding/icon.svg`).
+
+После правки SVG или скриншотов в `docs/` выполни `bash branding/render.sh`
+(нужен `resvg`): скрипт перерисует все размеры иконки в
+`Assets.xcassets/AppIcon.appiconset`, `branding/avatar.png` (512×512) и
+`branding/banner.png` (1280×640). Шрифты Roboto лежат в `branding/fonts`.
diff --git a/branding/avatar.png b/branding/avatar.png
index d82c6c6..ca42d70 100644
Binary files a/branding/avatar.png and b/branding/avatar.png differ
diff --git a/branding/avatar.svg b/branding/avatar.svg
index 5549534..1220ded 100644
--- a/branding/avatar.svg
+++ b/branding/avatar.svg
@@ -1,60 +1,30 @@
-