FTStore now holds both halves of the product:
ios/ the swift app — the fork of claration/Feather, unchanged apart
from its new home
web/ FTStore Admin, formerly its own repository. serves the
dashboard at /admin, the ftstore.json feed and IPA downloads
app-repo.json and Images/ stay at the repo root on purpose: the former is
a published AltStore source whose raw.githubusercontent URL is what
installed copies resolve updates through, so moving it would break every
subscriber.
The admin server is renamed throughout: package ftstore-admin, images
ghcr.io/<owner>/ftstore-admin-{app,worker}, postgres role/db ftstore,
session cookie ftstore_session, temp dir /tmp/ftstore, backup type
ftstore-settings, outbound user-agent FTStoreAdmin. backups written under the old
name still import, and compose is pinned to name: ftstore so the
volume prefix does not follow the directory name.
apply-rebrand.sh gains a relocation step: upstream's tree is rooted at
the repo root while ours lives in ios/, so a merge drops Feather's files
next to ios/ and the script folds them in before renaming. it never
touches web/. still idempotent.
release tags are namespaced — v* for the app, web-v* for the server —
so a server release no longer triggers a macOS build or rewrites
app-repo.json. workflows are prefixed ios-/web- and path-filtered.
113 lines
2.2 KiB
Swift
113 lines
2.2 KiB
Swift
//
|
|
// NBSheetButton.swift
|
|
// NimbleKit
|
|
//
|
|
// Created by samara on 8.05.2025.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
public struct NBSheetButton: View {
|
|
public enum NSSheetButtonStyle {
|
|
case prominent
|
|
case standard
|
|
}
|
|
|
|
@Environment(\.isEnabled) private var isEnabled
|
|
|
|
private var _title: String
|
|
private var _systemImage: String?
|
|
private var _style: NSSheetButtonStyle
|
|
|
|
public init(
|
|
title: String,
|
|
systemImage: String? = nil,
|
|
style: NSSheetButtonStyle = .standard
|
|
) {
|
|
self._title = title
|
|
self._systemImage = systemImage
|
|
self._style = style
|
|
}
|
|
|
|
public var body: some View {
|
|
ZStack {
|
|
if isEnabled {
|
|
Label {
|
|
Text(_title)
|
|
} icon: {
|
|
if let image = _systemImage {
|
|
Image(systemName: image)
|
|
}
|
|
}
|
|
.labelStyle(.titleAndIcon)
|
|
} else {
|
|
ProgressView()
|
|
.progressViewStyle(.circular)
|
|
.opacity(1)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
.background {
|
|
adaptiveGlassBackground(
|
|
tintColor: isEnabled ? backgroundColor : Color(uiColor: .quaternarySystemFill),
|
|
fallback: isEnabled ? backgroundColor : Color(uiColor: .quaternarySystemFill)
|
|
)
|
|
}
|
|
.foregroundColor(foregroundColor)
|
|
.clipShape(RoundedRectangle(cornerRadius: _cornerRadius, style: .continuous))
|
|
.contentShape(RoundedRectangle(cornerRadius: _cornerRadius, style: .continuous))
|
|
.fontWeight(.semibold)
|
|
.frame(height: 50)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var content: some View {
|
|
Label {
|
|
Text(_title)
|
|
} icon: {
|
|
if let image = _systemImage {
|
|
Image(systemName: image)
|
|
}
|
|
}
|
|
.labelStyle(.titleAndIcon)
|
|
}
|
|
|
|
|
|
private var backgroundColor: Color {
|
|
switch _style {
|
|
case .prominent: .accentColor
|
|
case .standard: Color(uiColor: .quaternarySystemFill)
|
|
}
|
|
}
|
|
|
|
private var foregroundColor: Color {
|
|
switch _style {
|
|
case .prominent: .white
|
|
case .standard: .accentColor
|
|
}
|
|
}
|
|
|
|
private var _cornerRadius: CGFloat {
|
|
if #available(iOS 26.0, *) {
|
|
return 28.0
|
|
} else {
|
|
return 12.0
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension View {
|
|
@ViewBuilder
|
|
func adaptiveGlassBackground(
|
|
tintColor: Color,
|
|
fallback: Color
|
|
) -> some View {
|
|
if #available(iOS 26.0, *) {
|
|
Color.clear
|
|
.glassEffect(.regular.tint(tintColor), in: .rect(cornerRadius: 28, style: .continuous))
|
|
} else {
|
|
fallback
|
|
}
|
|
}
|
|
}
|