Version 1.4 - Translations, Like toasts Queue redesign.

This commit is contained in:
2026-04-09 16:54:41 +02:00
parent ec1ffcb0b1
commit 5f3902cb54
30 changed files with 3472 additions and 654 deletions
+66 -52
View File
@@ -10,32 +10,34 @@ import StoreKit
struct MainTabView: View {
@Environment(MAService.self) private var service
@State private var selectedTab: String = "library"
var body: some View {
TabView {
Tab("Library", systemImage: "music.note.list") {
TabView(selection: $selectedTab) {
Tab("Library", systemImage: "music.note.list", value: "library") {
LibraryView()
}
Tab("Favorites", systemImage: "heart.fill") {
Tab("Favorites", systemImage: "heart.fill", value: "favorites") {
FavoritesView()
}
Tab("Search", systemImage: "magnifyingglass") {
Tab("Search", systemImage: "magnifyingglass", value: "search") {
NavigationStack {
SearchView()
.withMANavigation()
}
}
Tab("Players", systemImage: "speaker.wave.2.fill") {
Tab("Players", systemImage: "speaker.wave.2.fill", value: "players") {
PlayerListView()
}
Tab("Settings", systemImage: "gear") {
Tab("Settings", systemImage: "gear", value: "settings") {
SettingsView()
}
}
.withToast()
.task {
// Start listening to player events and load players when main view appears
service.playerManager.startListening()
@@ -386,8 +388,9 @@ struct PlayerRow: View {
struct SettingsView: View {
@Environment(MAService.self) private var service
@Environment(MAStoreManager.self) private var storeManager
@Environment(\.themeManager) private var themeManager
@Environment(\.localeManager) private var localeManager
@Environment(MAStoreManager.self) private var storeManager
@State private var showThankYou = false
var body: some View {
@@ -435,6 +438,24 @@ struct SettingsView: View {
Text("Choose how the app looks. System follows your device settings.")
}
// Language Section
Section {
Picker("Language", selection: Binding(
get: { localeManager.selectedLanguageCode ?? "system" },
set: { localeManager.selectedLanguageCode = $0 == "system" ? nil : $0 }
)) {
Text("System").tag("system")
ForEach(SupportedLanguage.allCases) { lang in
Text(verbatim: lang.endonym).tag(lang.rawValue)
}
}
.pickerStyle(.menu)
} header: {
Text("Language")
} footer: {
Text("Choose the app language. System uses your device language.")
}
// Connection Section
Section {
if let serverURL = service.authManager.serverURL {
@@ -462,74 +483,67 @@ struct SettingsView: View {
Label("Disconnect", systemImage: "arrow.right.square")
}
}
// Support Development Section
Section {
if storeManager.products.isEmpty {
HStack {
if let loadError = storeManager.loadError {
Label(loadError, systemImage: "exclamationmark.triangle")
.font(.caption)
.foregroundStyle(.secondary)
}
ForEach(storeManager.products, id: \.id) { product in
HStack(spacing: 12) {
Image(systemName: storeManager.iconName(for: product))
.font(.title2)
.foregroundStyle(.orange)
.frame(width: 32)
VStack(alignment: .leading, spacing: 2) {
Text(storeManager.tierName(for: product))
.font(.body)
.foregroundStyle(.primary)
Text(product.displayPrice)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
ProgressView()
Spacer()
}
} else {
ForEach(storeManager.products, id: \.id) { product in
Button {
Task {
await storeManager.purchase(product)
}
Task { await storeManager.purchase(product) }
} label: {
HStack(spacing: 12) {
Image(systemName: storeManager.iconName(for: product.id))
.font(.title2)
.foregroundStyle(.orange)
.frame(width: 32)
VStack(alignment: .leading, spacing: 2) {
Text(storeManager.tierName(for: product.id))
.font(.body)
.foregroundStyle(.primary)
Text(product.description)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
Text(product.displayPrice)
.font(.subheadline.weight(.semibold))
.foregroundStyle(.orange)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(
Capsule()
.fill(.orange.opacity(0.15))
)
}
.padding(.vertical, 4)
Text(product.displayPrice)
.font(.subheadline.weight(.medium))
.padding(.horizontal, 12)
.padding(.vertical, 6)
.background(Color.orange.opacity(0.15))
.foregroundStyle(.orange)
.clipShape(Capsule())
}
.buttonStyle(.plain)
.disabled(storeManager.isPurchasing)
}
.padding(.vertical, 4)
}
} header: {
Text("Support Development")
} footer: {
Text("Donations help keep this app updated and ad-free. Thank you!")
Text("Do you find this app useful? Support the development by buying the developer a virtual record.")
}
}
.navigationTitle("Settings")
.task {
await storeManager.loadProducts()
}
.alert("Thank You!", isPresented: $showThankYou) {
Button("OK", role: .cancel) { }
Button("You're welcome!", role: .cancel) { }
} message: {
Text("Your support means a lot and helps keep this app alive!")
Text("Your support means a lot and helps keep Mobile Music Assistant alive.")
}
.onChange(of: storeManager.purchaseResult) {
if case .success = storeManager.purchaseResult {
.onChange(of: storeManager.purchaseResult) { _, result in
if case .success = result {
showThankYou = true
storeManager.purchaseResult = nil
}
}
}