~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/knotless

git that lives in your Atmosphere account

knotless.dev
1 collaborator

knotless 0.13.0 Latest 2 packages, 1 image

1
10

The iOS app's spaces, radii, icons and avatars are tokens

Merged chadtmiller.com asked to merge into main from chadtmiller.com/knotless:ios/tokens
ios/Knotless/Support/Markdown.swift+7−7
@@ -20,7 +20,7 @@ struct MarkdownView: View {
var body: some View {
let colours = html.map(MarkdownFences.ranges) ?? [:]
VStack(alignment: .leading, spacing: 10) {
VStack(alignment: .leading, spacing: Space.s2_5) {
ForEach(Array(Self.parse(source).enumerated()), id: \.offset) { _, block in
view(block, colours)
}
@@ -31,22 +31,22 @@ struct MarkdownView: View {
@ViewBuilder private func view(_ block: Block, _ colours: [String: String]) -> some View {
switch block {
case .heading(let level, let text):
inline(text).font(level == 1 ? .title2.bold() : level == 2 ? .title3.bold() : .headline).padding(.top, 4)
inline(text).font(level == 1 ? .title2.bold() : level == 2 ? .title3.bold() : .headline).padding(.top, Space.s1)
case .paragraph(let text):
rich(text)
case .bullet(let text, let marker, let depth):
HStack(alignment: .firstTextBaseline, spacing: 8) {
HStack(alignment: .firstTextBaseline, spacing: Space.s2) {
Text(marker).foregroundStyle(.secondary)
rich(text)
}
.padding(.leading, CGFloat(depth) * 16)
case .quote(let text):
inline(text).foregroundStyle(.secondary).padding(.leading, 12)
inline(text).foregroundStyle(.secondary).padding(.leading, Space.s3)
case .code(let text):
ScrollView(.horizontal, showsIndicators: false) {
Text(Self.painted(text, colours[text])).font(.footnote.monospaced()).textSelection(.enabled).padding(10)
Text(Self.painted(text, colours[text])).font(.footnote.monospaced()).textSelection(.enabled).padding(Space.s2_5)
}
.background(Color(.tertiarySystemFill), in: RoundedRectangle(cornerRadius: 8))
.background(Color(.tertiarySystemFill), in: RoundedRectangle(cornerRadius: Radius.medium))
case .rule:
Divider()
}
@@ -68,7 +68,7 @@ struct MarkdownView: View {
if pieces.count == 1, case .text(let only) = pieces[0] {
inline(only)
} else {
VStack(alignment: .leading, spacing: 8) {
VStack(alignment: .leading, spacing: Space.s2) {
ForEach(Array(pieces.enumerated()), id: \.offset) { _, piece in
switch piece {
case .text(let words):
ios/Knotless/Support/Theme.swift+56−0
@@ -25,6 +25,62 @@ extension Color {
}
}
/// Spaces, on the scale of `--space-*` in `web/src/tokens.css`: `s2` is its `--space-2`.
enum Space {
static let s0_5: CGFloat = 2
static let s1: CGFloat = 4
static let s1_5: CGFloat = 6
static let s2: CGFloat = 8
static let s2_5: CGFloat = 10
static let s3: CGFloat = 12
static let s3_5: CGFloat = 14
static let s4: CGFloat = 16
static let s5: CGFloat = 20
static let s6: CGFloat = 24
static let s7: CGFloat = 28
static let s9: CGFloat = 36
}
enum Radius {
static let small: CGFloat = 6
static let medium: CGFloat = 8
static let large: CGFloat = 12
/// A card of rows, or a sheet's box.
static let card: CGFloat = 20
}
/// An Octicon's size.
enum Icon {
static let xs: CGFloat = 12
static let sm: CGFloat = 14
static let md: CGFloat = 16
static let lg: CGFloat = 18
static let xl: CGFloat = 20
static let xxl: CGFloat = 24
}
enum Size {
enum Avatar {
static let tiny: CGFloat = 16
static let xsmall: CGFloat = 20
static let small: CGFloat = 24
static let regular: CGFloat = 30
static let medium: CGFloat = 32
static let large: CGFloat = 44
static let xlarge: CGFloat = 64
}
/// The least height of a row that opens a screen.
static let row: CGFloat = 56
static let fileHeader: CGFloat = 52
/// Room under a page for the button floating over its end.
static let floatingClearance: CGFloat = 60
/// Where a row's text starts, past its padding and a regular avatar or
/// icon square, for the divider under it to line up with.
static let rowInset: CGFloat = Space.s4 + Avatar.regular + Space.s3
static let smallRowInset: CGFloat = Space.s4 + Avatar.small + Space.s3
}
extension UIColor {
convenience init(hex: UInt32) {
self.init(red: CGFloat(hex >> 16 & 255) / 255, green: CGFloat(hex >> 8 & 255) / 255, blue: CGFloat(hex & 255) / 255, alpha: 1)
ios/Knotless/Views/CodeViews.swift+4−4
@@ -69,19 +69,19 @@ private struct CodeLines: View {
var body: some View {
ScrollView {
HStack(alignment: .top, spacing: 8) {
HStack(alignment: .top, spacing: Space.s2) {
column(widest: "\(painted.count)", alignment: .trailing) { i in
Text("\(i + 1)").foregroundStyle(.secondary)
}
.padding(.leading, 8)
.padding(.leading, Space.s2)
ScrollView(.horizontal) {
column(widest: lines.max { $0.count < $1.count } ?? "", alignment: .leading) { i in
Text(painted[i])
}
.padding(.trailing, 16)
.padding(.trailing, Space.s4)
}
}
.padding(.vertical, 8)
.padding(.vertical, Space.s2)
}
}
ios/Knotless/Views/CommitView.swift+17−17
@@ -6,8 +6,8 @@ struct CommitRow: View {
let commit: Commit
var body: some View {
VStack(alignment: .leading, spacing: 5) {
HStack(alignment: .firstTextBaseline, spacing: 10) {
VStack(alignment: .leading, spacing: Space.s1_5) {
HStack(alignment: .firstTextBaseline, spacing: Space.s2_5) {
Text(commit.subject).font(.body.weight(.semibold)).lineLimit(1)
Spacer(minLength: 0)
Text(When.short(commit.when)).font(.subheadline).foregroundStyle(.secondary).fixedSize()
@@ -48,10 +48,10 @@ private struct CommitPeople: View {
var body: some View {
let authors = commit.authors
HStack(spacing: 8) {
HStack(spacing: Space.s2) {
HStack(spacing: -6) {
ForEach(Array(authors.prefix(2).enumerated()), id: \.offset) { _, person in
Avatar(url: person.actor?.avatar, name: person.label, did: person.actor?.did, face: person.face, size: 22)
Avatar(url: person.actor?.avatar, name: person.label, did: person.actor?.did, face: person.face, size: Size.Avatar.small)
}
}
if whole, let committer = commit.otherCommitter {
@@ -108,8 +108,8 @@ private struct CommitChanges: View {
var body: some View {
ScrollViewReader { reader in
ScrollView {
VStack(alignment: .leading, spacing: 12) {
HStack(alignment: .firstTextBaseline, spacing: 10) {
VStack(alignment: .leading, spacing: Space.s3) {
HStack(alignment: .firstTextBaseline, spacing: Space.s2_5) {
CommitPeople(commit: commit, whole: true)
Spacer(minLength: 0)
Text(When.short(commit.when)).font(.subheadline).foregroundStyle(.secondary).fixedSize()
@@ -118,14 +118,14 @@ private struct CommitChanges: View {
if !commit.details.isEmpty { MarkdownView(source: commit.details, place: MarkdownPlace(repo: repo)) }
Text(summary).font(.footnote).foregroundStyle(.secondary)
}
.padding(16)
.padding(Space.s4)
.frame(maxWidth: .infinity, alignment: .leading)
LazyVStack(alignment: .leading, spacing: 0, pinnedViews: [.sectionHeaders]) {
ForEach(answer.files) { file in
FileSection(repo: repo, file: file).id(file.id)
}
}
.padding(.bottom, 60)
.padding(.bottom, Size.floatingClearance)
}
.overlay(alignment: .bottomTrailing) {
if answer.files.count > 1 {
@@ -137,7 +137,7 @@ private struct CommitChanges: View {
SwiftUI.Label { Text("Jump to file") } icon: { Oct(name: "file-moved") }.labelStyle(TightLabel())
}
.floating()
.padding(16)
.padding(Space.s4)
}
}
}
@@ -161,14 +161,14 @@ private struct CommitDetails: View {
var body: some View {
List {
Section("Commit") {
HStack(spacing: 10) {
HStack(spacing: Space.s2_5) {
Text(commit.sha).font(.body.monospaced()).lineLimit(1).truncationMode(.tail)
Spacer(minLength: 0)
Button {
UIPasteboard.general.string = commit.sha
copied = true
} label: {
Oct(name: copied ? "check" : "copy", size: 20).foregroundStyle(copied ? Color.success : Color.accent)
Oct(name: copied ? "check" : "copy", size: Icon.xl).foregroundStyle(copied ? Color.success : Color.accent)
}
.buttonStyle(.borderless)
.accessibilityLabel(copied ? "Copied" : "Copy")
@@ -187,8 +187,8 @@ private struct CommitDetails: View {
Section(parents.count == 1 ? "Parent" : "Parents") {
ForEach(parents, id: \.sha) { parent in
NavigationLink(value: Route.commit(repo, sha: parent.sha)) {
HStack(spacing: 10) {
Oct(name: "git-commit", size: 20).foregroundStyle(.secondary)
HStack(spacing: Space.s2_5) {
Oct(name: "git-commit", size: Icon.xl).foregroundStyle(.secondary)
Text(parent.sha).font(.body.monospaced()).lineLimit(1).truncationMode(.tail)
}
}
@@ -198,8 +198,8 @@ private struct CommitDetails: View {
if let pull = commit.pull {
Section("Pull") {
NavigationLink(value: Route.thread(repo, uri: pull.uri)) {
HStack(spacing: 10) {
Oct(name: "git-pull-request", size: 20).foregroundStyle(.secondary)
HStack(spacing: Space.s2_5) {
Oct(name: "git-pull-request", size: Icon.xl).foregroundStyle(.secondary)
Text(pull.title ?? "This commit's pull").lineLimit(2)
}
}
@@ -216,8 +216,8 @@ private struct CommitDetails: View {
}
private func person(_ person: CommitAuthor, _ role: String) -> some View {
HStack(spacing: 12) {
Avatar(url: person.actor?.avatar, name: person.label, did: person.actor?.did, face: person.face, size: 32)
HStack(spacing: Space.s3) {
Avatar(url: person.actor?.avatar, name: person.label, did: person.actor?.did, face: person.face, size: Size.Avatar.medium)
Text(person.label).lineLimit(1)
Spacer(minLength: 8)
Text(role).foregroundStyle(.secondary)
ios/Knotless/Views/ExploreView.swift+4−4
@@ -68,9 +68,9 @@ struct EventRow: View {
let event: FeedEvent
var body: some View {
HStack(alignment: .top, spacing: 12) {
Avatar(url: event.actor.avatar, name: event.actor.handle, did: event.actor.did, size: 32)
VStack(alignment: .leading, spacing: 3) {
HStack(alignment: .top, spacing: Space.s3) {
Avatar(url: event.actor.avatar, name: event.actor.handle, did: event.actor.did, size: Size.Avatar.medium)
VStack(alignment: .leading, spacing: Space.s1) {
HStack {
Text(event.repo.label).lineLimit(1).foregroundStyle(.secondary)
Spacer()
@@ -84,6 +84,6 @@ struct EventRow: View {
}
}
}
.padding(.vertical, 3)
.padding(.vertical, Space.s1)
}
}
ios/Knotless/Views/FilesView.swift+9−9
@@ -25,7 +25,7 @@ struct FilesView: View {
}
.safeAreaInset(edge: .bottom) {
if account.signedIn, thread.item.state == "open" {
ReviewButton { reviewing = true }.padding(.bottom, 8)
ReviewButton { reviewing = true }.padding(.bottom, Space.s2)
}
}
.sheet(isPresented: $reviewing) {
@@ -42,7 +42,7 @@ private extension View {
func navigationSubtitleCompat(added: Int, removed: Int) -> some View {
toolbar {
ToolbarItem(placement: .topBarTrailing) {
HStack(spacing: 4) {
HStack(spacing: Space.s1) {
Text("+\(added)").foregroundStyle(Color.success)
Text("-\(removed)").foregroundStyle(Color.danger)
}
@@ -85,8 +85,8 @@ struct FileSection: View {
}
} header: {
Button { open.toggle() } label: {
HStack(spacing: 12) {
Oct(name: "chevron-down", size: 16)
HStack(spacing: Space.s3) {
Oct(name: "chevron-down", size: Icon.md)
.rotationEffect(open ? .zero : .degrees(-90))
.foregroundStyle(.secondary)
Text(file.path).font(.subheadline.monospaced()).lineLimit(1).truncationMode(.head)
@@ -96,8 +96,8 @@ struct FileSection: View {
Text("-\(file.removed)").foregroundStyle(Color.danger)
}
.font(.footnote.monospacedDigit())
.padding(.horizontal, 16)
.frame(minHeight: 52)
.padding(.horizontal, Space.s4)
.frame(minHeight: Size.fileHeader)
.background(Color(.secondarySystemBackground))
.overlay(alignment: .top) { Color(.separator).frame(height: 0.5) }
.overlay(alignment: .bottom) { Color(.separator).frame(height: 0.5) }
@@ -159,7 +159,7 @@ private struct DiffLine: View {
}
var body: some View {
HStack(alignment: .firstTextBaseline, spacing: 6) {
HStack(alignment: .firstTextBaseline, spacing: Space.s1_5) {
if line.row.line {
Text(number).frame(width: 34, alignment: .trailing).foregroundStyle(.secondary)
Text(marker).foregroundStyle(line.row.kind == "a" ? Color.success : Color.danger)
@@ -171,8 +171,8 @@ private struct DiffLine: View {
.padding(.leading, line.row.line ? 0 : 12)
}
.font(.caption.monospaced())
.padding(.horizontal, 6)
.padding(.vertical, line.row.line ? 1.5 : 5)
.padding(.horizontal, Space.s1_5)
.padding(.vertical, line.row.line ? Space.s0_5 : Space.s1_5)
.background(tint)
}
ios/Knotless/Views/HomeView.swift+33−33
@@ -62,18 +62,18 @@ private struct HomeScreen: View {
row(.starred(person.handle), "Starred", "star", .attention)
}
Heading("Favorites") {
Button { editing = true } label: { Oct(name: "kebab-horizontal", size: 20).foregroundStyle(.secondary) }
Button { editing = true } label: { Oct(name: "kebab-horizontal", size: Icon.xl).foregroundStyle(.secondary) }
.buttonStyle(.plain)
.accessibilityLabel("Edit favorites")
}
Card {
if favorites.isEmpty {
VStack(spacing: 10) {
VStack(spacing: Space.s2_5) {
Text("Keep repositories you visit a lot here.").foregroundStyle(.secondary).multilineTextAlignment(.center)
Button("Add favorites") { editing = true }.fontWeight(.medium)
}
.frame(maxWidth: .infinity)
.padding(20)
.padding(Space.s5)
}
ForEach(Array(favorites.enumerated()), id: \.element) { at, favorite in
if at > 0 { Separator() }
@@ -93,21 +93,21 @@ private struct HomeScreen: View {
}
}
}
.padding(.horizontal, 16)
.padding(.bottom, 24)
.padding(.horizontal, Space.s4)
.padding(.bottom, Space.s6)
}
.background(Color(.systemGroupedBackground))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
NavigationLink(value: Route.profile(person.handle)) {
Avatar(url: person.avatar, name: person.handle, did: person.did, size: 32)
Avatar(url: person.avatar, name: person.handle, did: person.did, size: Size.Avatar.medium)
}
.accessibilityLabel("Your profile")
}
ToolbarItemGroup(placement: .topBarTrailing) {
Button { writing = true } label: { Oct(name: "plus", size: 24) }.tint(.primary).accessibilityLabel("New issue")
NavigationLink(value: Route.search) { Oct(name: "search", size: 24) }.tint(.primary).accessibilityLabel("Search")
Button { writing = true } label: { Oct(name: "plus", size: Icon.xxl) }.tint(.primary).accessibilityLabel("New issue")
NavigationLink(value: Route.search) { Oct(name: "search", size: Icon.xxl) }.tint(.primary).accessibilityLabel("Search")
}
}
.sheet(isPresented: $editing) { EditFavorites(person: person, stored: $stored) }
@@ -142,8 +142,8 @@ private struct Heading<Trailing: View>: View {
Spacer()
trailing
}
.padding(.top, 24)
.padding(.bottom, 10)
.padding(.top, Space.s6)
.padding(.bottom, Space.s2_5)
}
}
@@ -152,21 +152,21 @@ private struct Card<Content: View>: View {
var body: some View {
VStack(spacing: 0) { content }
.background(Color(.secondarySystemGroupedBackground), in: RoundedRectangle(cornerRadius: 20))
.clipShape(RoundedRectangle(cornerRadius: 20))
.background(Color(.secondarySystemGroupedBackground), in: RoundedRectangle(cornerRadius: Radius.card))
.clipShape(RoundedRectangle(cornerRadius: Radius.card))
}
}
private struct Separator: View {
var body: some View { Divider().padding(.leading, 58) }
var body: some View { Divider().padding(.leading, Size.rowInset) }
}
private struct FavoriteRow: View {
let favorite: Favorite
var body: some View {
HStack(spacing: 12) {
Avatar(url: favorite.avatar, name: favorite.handle, did: favorite.did, size: 30)
HStack(spacing: Space.s3) {
Avatar(url: favorite.avatar, name: favorite.handle, did: favorite.did, size: Size.Avatar.regular)
VStack(alignment: .leading, spacing: 0) {
Text(favorite.handle).font(.footnote).foregroundStyle(.secondary).lineLimit(1)
Text(favorite.name).font(.body.weight(.medium)).lineLimit(1)
@@ -174,8 +174,8 @@ private struct FavoriteRow: View {
Spacer()
RowChevron(name: "chevron-right")
}
.padding(.horizontal, 16)
.padding(.vertical, 12)
.padding(.horizontal, Space.s4)
.padding(.vertical, Space.s3)
.contentShape(Rectangle())
}
}
@@ -185,12 +185,12 @@ private struct RecentRow: View {
var body: some View {
let style = StateStyle(kind: kind(ofURI: event.action.subject), state: event.action.state)
HStack(alignment: .top, spacing: 12) {
Oct(name: String(style.icon.dropFirst(4)), size: 20)
HStack(alignment: .top, spacing: Space.s3) {
Oct(name: String(style.icon.dropFirst(4)), size: Icon.xl)
.foregroundStyle(event.action.state == nil ? Color.neutral : style.color)
.frame(width: 30)
.padding(.top, 2)
VStack(alignment: .leading, spacing: 3) {
.padding(.top, Space.s0_5)
VStack(alignment: .leading, spacing: Space.s1) {
HStack(alignment: .firstTextBaseline) {
Text(event.repo.label).font(.subheadline).foregroundStyle(.secondary).lineLimit(1)
Spacer()
@@ -200,8 +200,8 @@ private struct RecentRow: View {
Text(event.action.yours).font(.subheadline).foregroundStyle(.secondary)
}
}
.padding(.horizontal, 16)
.padding(.vertical, 10)
.padding(.horizontal, Space.s4)
.padding(.vertical, Space.s2_5)
.contentShape(Rectangle())
}
}
@@ -319,7 +319,7 @@ struct MyItemsView: View {
var body: some View {
VStack(spacing: 0) {
HStack(spacing: 8) {
HStack(spacing: Space.s2) {
Menu {
Picker("State", selection: $state) {
Text("Open").tag("open")
@@ -335,8 +335,8 @@ struct MyItemsView: View {
.tint(.primary)
Spacer()
}
.padding(.horizontal, 16)
.padding(.vertical, 8)
.padding(.horizontal, Space.s4)
.padding(.vertical, Space.s2)
if let did = account.person?.did {
PagedList<ActorItem, MineRow>(empty: kind == "pull" ? "No pull requests here." : "No issues here.", fetch: { [kind, state, relation] cursor in
let answer: ActorItems = try await Client.shared.query("listActorItems", [
@@ -352,14 +352,14 @@ struct MyItemsView: View {
}
private func chip(_ title: String) -> some View {
HStack(spacing: 4) {
HStack(spacing: Space.s1) {
Text(title)
Oct(name: "chevron-down", size: 12)
Oct(name: "chevron-down", size: Icon.xs)
}
.font(.subheadline)
.foregroundStyle(.primary)
.padding(.horizontal, 12)
.padding(.vertical, 6)
.padding(.horizontal, Space.s3)
.padding(.vertical, Space.s1_5)
.background(Color(.tertiarySystemFill), in: Capsule())
}
}
@@ -369,7 +369,7 @@ private struct MineRow: View {
var body: some View {
NavigationLink(value: Route.thread(found.repo, uri: found.item.uri)) {
VStack(alignment: .leading, spacing: 3) {
VStack(alignment: .leading, spacing: Space.s1) {
Text(found.repo.label).font(.footnote).foregroundStyle(.secondary).lineLimit(1)
ItemRow(item: found.item)
}
@@ -409,8 +409,8 @@ struct OrganizationsView: View {
} else {
List(orgs, id: \.actor.did) { org in
NavigationLink(value: Route.profile(org.actor.handle)) {
HStack(spacing: 12) {
Avatar(url: org.actor.avatar, name: org.actor.handle, did: org.actor.did, size: 32)
HStack(spacing: Space.s3) {
Avatar(url: org.actor.avatar, name: org.actor.handle, did: org.actor.did, size: Size.Avatar.medium)
Text(org.actor.handle).lineLimit(1)
}
}
ios/Knotless/Views/InboxView.swift+11−11
@@ -29,18 +29,18 @@ struct InboxView: View {
private var chips: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
HStack(spacing: Space.s2) {
ForEach(Self.filters, id: \.name) { option in
Button(option.name) { filter = option.kind }
.font(.subheadline.weight(.medium))
.foregroundStyle(filter == option.kind ? Color.accent : .primary)
.padding(.horizontal, 12)
.padding(.vertical, 7)
.padding(.horizontal, Space.s3)
.padding(.vertical, Space.s2)
.background(filter == option.kind ? Color.accentSoft : Color(.secondarySystemBackground), in: Capsule())
}
}
.padding(.horizontal)
.padding(.vertical, 8)
.padding(.vertical, Space.s2)
}
}
@@ -136,19 +136,19 @@ struct InboxRow: View {
private var style: StateStyle { StateStyle(kind: kind(ofURI: item.reasonSubject), state: item.subjectState) }
var body: some View {
HStack(alignment: .top, spacing: 12) {
VStack(spacing: 8) {
HStack(alignment: .top, spacing: Space.s3) {
VStack(spacing: Space.s2) {
if kind(ofURI: item.reasonSubject) == nil {
Image(systemName: other).font(.title3).foregroundStyle(Color.neutral)
} else {
StateIcon(style: style, size: 22)
StateIcon(style: style, size: Icon.xxl)
}
if !item.isRead {
Circle().fill(Color.accent).frame(width: 9, height: 9).accessibilityLabel("Unread")
}
}
.frame(width: 28)
VStack(alignment: .leading, spacing: 3) {
VStack(alignment: .leading, spacing: Space.s1) {
HStack {
Text(item.repo?.label ?? item.author.handle).lineLimit(1).foregroundStyle(.secondary)
Spacer()
@@ -156,13 +156,13 @@ struct InboxRow: View {
}
.font(.subheadline)
Text(item.title ?? what).font(.body.weight(.medium)).lineLimit(2)
HStack(spacing: 6) {
Avatar(url: item.author.avatar, name: item.author.handle, did: item.author.did, size: 18)
HStack(spacing: Space.s1_5) {
Avatar(url: item.author.avatar, name: item.author.handle, did: item.author.did, size: Size.Avatar.xsmall)
Text(MarkdownView.plain(preview)).font(.subheadline).foregroundStyle(.secondary).lineLimit(1)
}
}
}
.padding(.vertical, 3)
.padding(.vertical, Space.s1)
}
private var preview: String {
ios/Knotless/Views/MarkdownImage.swift+1−1
@@ -26,7 +26,7 @@ struct MarkdownImage: View {
if failed {
if !alt.isEmpty { Text(alt).foregroundStyle(.secondary) }
} else {
RoundedRectangle(cornerRadius: 6).fill(Color(.secondarySystemBackground)).frame(width: 120, height: 24)
RoundedRectangle(cornerRadius: Radius.small).fill(Color(.secondarySystemBackground)).frame(width: 120, height: 24)
}
}
}
ios/Knotless/Views/ProfileView.swift+27−27
@@ -51,10 +51,10 @@ private struct ProfilePage: View {
let p = profile
ScrollView {
VStack(alignment: .leading, spacing: 0) {
VStack(alignment: .leading, spacing: 14) {
HStack(spacing: 14) {
Avatar(url: p.avatar, name: p.handle, did: p.did, size: 64)
VStack(alignment: .leading, spacing: 2) {
VStack(alignment: .leading, spacing: Space.s3_5) {
HStack(spacing: Space.s3_5) {
Avatar(url: p.avatar, name: p.handle, did: p.did, size: Size.Avatar.xlarge)
VStack(alignment: .leading, spacing: Space.s0_5) {
Text(p.displayName ?? p.handle).font(.title2.bold())
Text(p.handle).foregroundStyle(.secondary)
if let pronouns = p.pronouns { Text(pronouns).font(.footnote).foregroundStyle(.secondary) }
@@ -62,7 +62,7 @@ private struct ProfilePage: View {
}
if let about = p.description, !about.isEmpty { Text(about) }
if let link = p.links?.first { LinkLine(link: link) }
HStack(spacing: 6) {
HStack(spacing: Space.s1_5) {
Oct(name: "person").foregroundStyle(.secondary)
Text("\(Text("\(p.followersCount ?? 0)").fontWeight(.semibold)) \(Text((p.followersCount ?? 0) == 1 ? "follower" : "followers").foregroundStyle(.secondary)) · \(Text("\(p.followsCount ?? 0)").fontWeight(.semibold)) \(Text("following").foregroundStyle(.secondary))")
}
@@ -71,9 +71,9 @@ private struct ProfilePage: View {
FollowButton(did: p.did, following: p.viewer?.following != nil)
}
}
.padding(.horizontal, 16)
.padding(.top, 8)
.padding(.bottom, 20)
.padding(.horizontal, Space.s4)
.padding(.top, Space.s2)
.padding(.bottom, Space.s5)
.frame(maxWidth: .infinity, alignment: .leading)
Sections {
@@ -85,15 +85,15 @@ private struct ProfilePage: View {
ForEach(Array(orgs.enumerated()), id: \.element.actor.did) { index, org in
if index > 0 { RowDivider() }
NavigationLink(value: Route.profile(org.actor.handle)) {
HStack(spacing: 12) {
Avatar(url: org.actor.avatar, name: org.actor.handle, did: org.actor.did, size: 30)
HStack(spacing: Space.s3) {
Avatar(url: org.actor.avatar, name: org.actor.handle, did: org.actor.did, size: Size.Avatar.regular)
Text(org.actor.handle)
Spacer()
RowChevron(name: "chevron-right")
}
.padding(.horizontal, 16)
.padding(.vertical, 8)
.frame(minHeight: 56)
.padding(.horizontal, Space.s4)
.padding(.vertical, Space.s2)
.frame(minHeight: Size.row)
.contentShape(Rectangle())
}
.buttonStyle(RowPress())
@@ -103,15 +103,15 @@ private struct ProfilePage: View {
if !repos.isEmpty {
SectionTitle(icon: "clock", title: "Recent") { EmptyView() }
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 12) {
HStack(alignment: .top, spacing: Space.s3) {
ForEach(repos.prefix(6)) { RepoCard(repo: $0) }
}
.fixedSize(horizontal: false, vertical: true)
.padding(.horizontal, 16)
.padding(.horizontal, Space.s4)
}
}
}
.padding(.bottom, 24)
.padding(.bottom, Space.s6)
}
.background(Color(.systemGroupedBackground))
}
@@ -127,7 +127,7 @@ private struct FollowButton: View {
Button {
Task { await toggle() }
} label: {
HStack(spacing: 6) {
HStack(spacing: Space.s1_5) {
if !following { Image(systemName: "plus") }
Text(following ? "Following" : "Follow").fontWeight(.semibold)
}
@@ -158,15 +158,15 @@ private struct RepoStats: View {
let repo: Repo
var body: some View {
HStack(spacing: 12) {
HStack(spacing: Space.s3) {
if let stars = repo.starCount {
HStack(spacing: 4) {
HStack(spacing: Space.s1) {
Oct(name: "star")
Text("\(stars)")
}
}
if let language = repo.language {
HStack(spacing: 5) {
HStack(spacing: Space.s1_5) {
Circle().fill(Color.hex(language.color) ?? .neutral).frame(width: 10, height: 10)
Text(language.name)
}
@@ -182,9 +182,9 @@ private struct RepoCard: View {
var body: some View {
NavigationLink(value: Route.repo(repo.ref)) {
VStack(alignment: .leading, spacing: 6) {
HStack(spacing: 6) {
Avatar(url: repo.owner.avatar, name: repo.owner.handle, did: repo.owner.did, size: 20)
VStack(alignment: .leading, spacing: Space.s1_5) {
HStack(spacing: Space.s1_5) {
Avatar(url: repo.owner.avatar, name: repo.owner.handle, did: repo.owner.did, size: Size.Avatar.xsmall)
Text(repo.owner.handle).font(.subheadline).foregroundStyle(.secondary).lineLimit(1)
}
Text(repo.name).font(.headline).lineLimit(1)
@@ -192,10 +192,10 @@ private struct RepoCard: View {
Spacer(minLength: 4)
RepoStats(repo: repo)
}
.padding(14)
.padding(Space.s3_5)
.frame(width: 280, alignment: .leading)
.frame(maxHeight: .infinity, alignment: .topLeading)
.background(Color(.secondarySystemGroupedBackground), in: RoundedRectangle(cornerRadius: 14))
.background(Color(.secondarySystemGroupedBackground), in: RoundedRectangle(cornerRadius: Radius.large))
}
.buttonStyle(.plain)
}
@@ -211,8 +211,8 @@ struct RepositoriesView: View {
}) { repos in
List(repos) { repo in
NavigationLink(value: Route.repo(repo.ref)) {
VStack(alignment: .leading, spacing: 5) {
HStack(spacing: 5) {
VStack(alignment: .leading, spacing: Space.s1_5) {
HStack(spacing: Space.s1_5) {
Text(repo.name).font(.body.weight(.semibold)).lineLimit(1)
if repo.private == true {
Image(systemName: "lock.fill").font(.caption2).foregroundStyle(.secondary).accessibilityLabel("Private")
ios/Knotless/Views/RepoView.swift+56−56
ios/Knotless/Views/SearchView.swift+33−33
ios/Knotless/Views/Shared.swift+13−13
ios/Knotless/Views/ThreadView.swift+41−41
ios/Knotless/Views/Writing.swift+32−32
ios/README.md+6−0