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.
84 lines
1.8 KiB
Swift
84 lines
1.8 KiB
Swift
//
|
|
// ToolbarButtonWrapper.swift
|
|
// Stars
|
|
//
|
|
// Created by samara on 7.04.2025.
|
|
//
|
|
|
|
import SwiftUI
|
|
import NimbleExtensions
|
|
|
|
public struct NBToolbarButton: ToolbarContent {
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
private var _title: String
|
|
private var _icon: String
|
|
private var _style: NBToolbarMenuStyle
|
|
private var _placement: ToolbarItemPlacement
|
|
private var _isDisabled: Bool
|
|
private var _action: () -> Void
|
|
private var _role: NBToolbarButtonRole?
|
|
|
|
public init(
|
|
_ title: String = "",
|
|
systemImage: String = "",
|
|
style: NBToolbarMenuStyle = .icon,
|
|
placement: ToolbarItemPlacement = .automatic,
|
|
isDisabled: Bool = false,
|
|
action: @escaping () -> Void
|
|
) {
|
|
self._title = title
|
|
self._icon = systemImage
|
|
self._style = style
|
|
self._placement = placement
|
|
self._isDisabled = isDisabled
|
|
self._action = action
|
|
}
|
|
|
|
public init(
|
|
role: NBToolbarButtonRole,
|
|
placement: ToolbarItemPlacement = .cancellationAction
|
|
) {
|
|
self._role = role
|
|
self._placement = placement
|
|
self._isDisabled = false
|
|
self._action = {}
|
|
|
|
switch role {
|
|
case .cancel:
|
|
self._title = .localized("Cancel")
|
|
self._icon = "xmark"
|
|
self._style = .icon
|
|
case .dismiss:
|
|
self._title = .localized("Dismiss")
|
|
self._icon = "chevron.left"
|
|
self._style = .icon
|
|
case .close:
|
|
self._title = .localized("Close")
|
|
self._icon = "xmark"
|
|
self._style = .icon
|
|
self._placement = .topBarTrailing
|
|
}
|
|
}
|
|
|
|
public var body: some ToolbarContent {
|
|
ToolbarItem(placement: _placement) {
|
|
Button {
|
|
switch _role {
|
|
case .cancel, .dismiss, .close:
|
|
dismiss()
|
|
default:
|
|
_action()
|
|
}
|
|
} label: {
|
|
if _style == .icon {
|
|
Image(systemName: _icon)
|
|
} else {
|
|
Label(_title, systemImage: _icon).labelStyle(.titleOnly)
|
|
}
|
|
}
|
|
.disabled(_isDisabled)
|
|
}
|
|
}
|
|
}
|