Update the share extension view to newer design
This commit is contained in:
@ -1,12 +1,13 @@
|
||||
import CoreData
|
||||
import Models
|
||||
import Services
|
||||
import SwiftUI
|
||||
import Utils
|
||||
import Views
|
||||
|
||||
public class ShareExtensionViewModel: ObservableObject {
|
||||
@Published public var status: ShareExtensionStatus = .processing
|
||||
@Published public var title: String?
|
||||
@Published public var title: String = ""
|
||||
@Published public var url: String?
|
||||
@Published public var iconURL: String?
|
||||
@Published public var linkedItem: LinkedItem?
|
||||
@ -42,6 +43,22 @@ public class ShareExtensionViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func setLinkArchived(dataService: DataService, objectID: NSManagedObjectID, archived: Bool) {
|
||||
dataService.archiveLink(objectID: objectID, archived: archived)
|
||||
}
|
||||
|
||||
func removeLink(dataService: DataService, objectID: NSManagedObjectID) {
|
||||
dataService.removeLink(objectID: objectID)
|
||||
}
|
||||
|
||||
func submitTitleEdit(dataService: DataService, itemID: String, title: String, description: String) {
|
||||
dataService.updateLinkedItemTitleAndDescription(
|
||||
itemID: itemID,
|
||||
title: title,
|
||||
description: description
|
||||
)
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
func queueSaveOperation(_ payload: PageScrapePayload) {
|
||||
ProcessInfo().performExpiringActivity(withReason: "app.omnivore.SaveActivity") { [self] expiring in
|
||||
@ -72,7 +89,7 @@ public class ShareExtensionViewModel: ObservableObject {
|
||||
|
||||
switch payload.contentType {
|
||||
case let .html(html: _, title: title, iconURL: iconURL):
|
||||
self.title = title
|
||||
self.title = title ?? ""
|
||||
self.iconURL = iconURL
|
||||
self.url = hostname
|
||||
case .none:
|
||||
|
||||
@ -6,10 +6,22 @@ import Views
|
||||
|
||||
public struct ShareExtensionView: View {
|
||||
let extensionContext: NSExtensionContext?
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@StateObject var labelsViewModel = LabelsViewModel()
|
||||
@StateObject private var viewModel = ShareExtensionViewModel()
|
||||
|
||||
@State var reminderTime: ReminderTime?
|
||||
@State var hideUntilReminded = false
|
||||
@State var editingTitle = false
|
||||
@State var editingLabels = false
|
||||
@State var previousLabels: [LinkedItemLabel]?
|
||||
@State var messageText: String?
|
||||
|
||||
enum FocusField: Hashable {
|
||||
case titleEditor
|
||||
}
|
||||
|
||||
@FocusState private var focusedField: FocusField?
|
||||
|
||||
private func handleReminderTimeSelection(_ selectedTime: ReminderTime) {
|
||||
if selectedTime == reminderTime {
|
||||
@ -134,61 +146,332 @@ public struct ShareExtensionView: View {
|
||||
.cornerRadius(8)
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text(titleText)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.font(Font.system(size: 17, weight: .semibold))
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.padding(.top, 23)
|
||||
.padding(.bottom, 12)
|
||||
var isSynced: Bool {
|
||||
switch viewModel.status {
|
||||
case .synced:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle()
|
||||
.foregroundColor(.appGrayText)
|
||||
.frame(maxWidth: .infinity, maxHeight: 1)
|
||||
.opacity(0.06)
|
||||
.padding(.top, 0)
|
||||
.padding(.bottom, 18)
|
||||
var titleBar: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
previewCard
|
||||
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
|
||||
Image(systemName: "checkmark.circle")
|
||||
.frame(width: 15, height: 15)
|
||||
.foregroundColor(.appGreenSuccess)
|
||||
.opacity(isSynced ? 1.0 : 0.0)
|
||||
|
||||
if let item = viewModel.linkedItem {
|
||||
ApplyLabelsListView(linkedItem: item)
|
||||
Text(messageText ?? titleText)
|
||||
.font(.appSubheadline)
|
||||
.foregroundColor(isSynced ? .appGreenSuccess : .appGrayText)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
public var titleBox: some View {
|
||||
VStack(alignment: .trailing) {
|
||||
Button(action: {}, label: {
|
||||
Text("Edit")
|
||||
.font(.appFootnote)
|
||||
.padding(.trailing, 8)
|
||||
.onTapGesture {
|
||||
editingTitle = true
|
||||
}
|
||||
})
|
||||
// Disabling this button temporarily
|
||||
.disabled(editingTitle)
|
||||
.opacity(editingTitle ? 0.0 : 1.0)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
if !editingTitle {
|
||||
Text(self.viewModel.title)
|
||||
.font(.appSubheadline)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(self.viewModel.url ?? "")
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: 60)
|
||||
.padding()
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(Color.appGrayBorder, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var labelsSection: some View {
|
||||
HStack {
|
||||
if !editingLabels {
|
||||
ZStack {
|
||||
Circle()
|
||||
.foregroundColor(Color.blue)
|
||||
.frame(width: 34, height: 34)
|
||||
|
||||
Image(systemName: "tag")
|
||||
.font(.appCallout)
|
||||
.frame(width: 34, height: 34)
|
||||
}
|
||||
.padding(.trailing, 8)
|
||||
|
||||
VStack {
|
||||
Text("Labels")
|
||||
.font(.appSubheadline)
|
||||
.foregroundColor(Color.appGrayTextContrast)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
let labelCount = labelsViewModel.selectedLabels.count
|
||||
Text(labelCount > 0 ?
|
||||
"\(labelCount) label\(labelCount > 1 ? "s" : "") selected"
|
||||
: "Add labels to your saved link")
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(Color.appGrayText)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.appCallout)
|
||||
} else {
|
||||
ScrollView {
|
||||
LabelsMasonaryView(labels: labelsViewModel.labels,
|
||||
selectedLabels: labelsViewModel.selectedLabels,
|
||||
onLabelTap: onLabelTap)
|
||||
}.background(Color.appButtonBackground)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, maxHeight: self.editingLabels ? .infinity : 60)
|
||||
.background(Color.appButtonBackground)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
|
||||
func onLabelTap(label: LinkedItemLabel, textChip _: TextChip) {
|
||||
if let selectedIndex = labelsViewModel.selectedLabels.firstIndex(of: label) {
|
||||
labelsViewModel.selectedLabels.remove(at: selectedIndex)
|
||||
} else {
|
||||
labelsViewModel.selectedLabels.append(label)
|
||||
}
|
||||
|
||||
if let linkedItem = viewModel.linkedItem {
|
||||
labelsViewModel.saveItemLabelChanges(itemID: linkedItem.unwrappedID, dataService: viewModel.services.dataService)
|
||||
}
|
||||
}
|
||||
|
||||
var primaryButtons: some View {
|
||||
HStack {
|
||||
Button(
|
||||
action: { viewModel.handleReadNowAction(extensionContext: extensionContext) },
|
||||
label: {
|
||||
Label("Read Now", systemImage: "book")
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
)
|
||||
.foregroundColor(.white)
|
||||
.background(Color.appButtonBackground)
|
||||
.frame(height: 52)
|
||||
.cornerRadius(8)
|
||||
|
||||
Spacer(minLength: 8)
|
||||
|
||||
Button(
|
||||
action: {
|
||||
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||
},
|
||||
label: {
|
||||
Label("Read Later", systemImage: "text.book.closed.fill")
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
)
|
||||
.foregroundColor(.black)
|
||||
.background(Color.appBackground)
|
||||
.frame(height: 52)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
|
||||
var moreActionsMenu: some View {
|
||||
Menu {
|
||||
Button(
|
||||
action: {},
|
||||
label: {
|
||||
Button(action: {}, label: { Label("Dismiss", systemImage: "arrow.down.to.line") })
|
||||
}
|
||||
)
|
||||
Button(action: {
|
||||
if let linkedItem = self.viewModel.linkedItem {
|
||||
self.viewModel.setLinkArchived(dataService: self.viewModel.services.dataService,
|
||||
objectID: linkedItem.objectID,
|
||||
archived: true)
|
||||
messageText = "Link Archived"
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
|
||||
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||
}
|
||||
}
|
||||
}, label: {
|
||||
Label(
|
||||
"Archive",
|
||||
systemImage: "archivebox"
|
||||
)
|
||||
})
|
||||
Button(
|
||||
action: {
|
||||
if let linkedItem = self.viewModel.linkedItem {
|
||||
self.viewModel.removeLink(dataService: self.viewModel.services.dataService, objectID: linkedItem.objectID)
|
||||
messageText = "Link Removed"
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
|
||||
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||
}
|
||||
}
|
||||
},
|
||||
label: {
|
||||
Label("Remove", systemImage: "trash")
|
||||
}
|
||||
)
|
||||
} label: {
|
||||
Text("More Actions")
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(Color.blue)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(8)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(alignment: .center) {
|
||||
Capsule()
|
||||
.fill(.gray)
|
||||
.frame(width: 60, height: 4)
|
||||
.padding(.top, 10)
|
||||
|
||||
if !editingLabels, !editingTitle {
|
||||
titleBar
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 12)
|
||||
} else {
|
||||
ZStack {
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
if editingLabels {
|
||||
if let linkedItem = self.viewModel.linkedItem {
|
||||
self.labelsViewModel.selectedLabels = previousLabels ?? []
|
||||
self.labelsViewModel.saveItemLabelChanges(itemID: linkedItem.unwrappedID,
|
||||
dataService: self.viewModel.services.dataService)
|
||||
}
|
||||
}
|
||||
editingTitle = false
|
||||
editingLabels = false
|
||||
}
|
||||
}, label: { Text("Cancel") })
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
Text(editingTitle ? "Edit Title" : "Labels").bold()
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
|
||||
Button(action: {
|
||||
withAnimation {
|
||||
editingTitle = false
|
||||
editingLabels = false
|
||||
|
||||
if editingTitle {
|
||||
if let linkedItem = self.viewModel.linkedItem {
|
||||
viewModel.submitTitleEdit(dataService: self.viewModel.services.dataService,
|
||||
itemID: linkedItem.unwrappedID,
|
||||
title: self.viewModel.title,
|
||||
description: linkedItem.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, label: { Text("Done").bold() })
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
}
|
||||
.padding(8)
|
||||
.padding(.bottom, 4)
|
||||
}
|
||||
|
||||
if !editingLabels, !editingTitle {
|
||||
titleBox
|
||||
}
|
||||
|
||||
if editingTitle {
|
||||
ScrollView(showsIndicators: false) {
|
||||
VStack(alignment: .center, spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
TextEditor(text: $viewModel.title)
|
||||
.lineSpacing(6)
|
||||
.accentColor(.appGraySolid)
|
||||
.foregroundColor(.appGrayTextContrast)
|
||||
.font(.appSubheadline)
|
||||
.padding(8)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.strokeBorder(Color.appGrayBorder, lineWidth: 1)
|
||||
.background(RoundedRectangle(cornerRadius: 8).fill(Color.systemBackground))
|
||||
)
|
||||
.frame(height: 100)
|
||||
.focused($focusedField, equals: .titleEditor)
|
||||
.task {
|
||||
self.focusedField = .titleEditor
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(8)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack {
|
||||
Button(
|
||||
action: { viewModel.handleReadNowAction(extensionContext: extensionContext) },
|
||||
label: { Text("Read Now").frame(maxWidth: .infinity) }
|
||||
)
|
||||
.buttonStyle(RoundedRectButtonStyle())
|
||||
|
||||
Button(
|
||||
action: {
|
||||
extensionContext?.completeRequest(returningItems: [], completionHandler: nil)
|
||||
},
|
||||
label: {
|
||||
Text("Read Later")
|
||||
.frame(maxWidth: .infinity)
|
||||
if !editingTitle {
|
||||
labelsSection
|
||||
.padding(.top, 12)
|
||||
.onTapGesture {
|
||||
withAnimation {
|
||||
previousLabels = self.labelsViewModel.selectedLabels
|
||||
editingLabels = true
|
||||
}
|
||||
}
|
||||
)
|
||||
.buttonStyle(RoundedRectButtonStyle())
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.bottom)
|
||||
|
||||
Spacer()
|
||||
|
||||
if !editingLabels, !editingTitle {
|
||||
Divider()
|
||||
.padding(.bottom, 20)
|
||||
|
||||
primaryButtons
|
||||
|
||||
moreActionsMenu
|
||||
}
|
||||
}
|
||||
.frame(
|
||||
maxWidth: .infinity,
|
||||
maxHeight: .infinity,
|
||||
alignment: .topLeading
|
||||
)
|
||||
.padding(.horizontal, 16)
|
||||
.onAppear {
|
||||
viewModel.savePage(extensionContext: extensionContext)
|
||||
}
|
||||
.environmentObject(viewModel.services.dataService)
|
||||
.task {
|
||||
await labelsViewModel.loadLabelsFromStore(dataService: viewModel.services.dataService)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
//
|
||||
// LabelsMasonaryView.swift
|
||||
//
|
||||
//
|
||||
// Created by Jackson Harper on 11/9/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
import Models
|
||||
import Views
|
||||
|
||||
struct LabelsMasonaryView: View {
|
||||
// var allLabels: [LinkedItemLabel]
|
||||
// var selectedLabels: [LinkedItemLabel]
|
||||
var onLabelTap: (LinkedItemLabel, TextChip) -> Void
|
||||
|
||||
var iteration = UUID().uuidString
|
||||
|
||||
@State private var totalHeight = CGFloat.zero
|
||||
private var labelItems: [(label: LinkedItemLabel, selected: Bool)]
|
||||
|
||||
init(labels allLabels: [LinkedItemLabel],
|
||||
selectedLabels: [LinkedItemLabel],
|
||||
onLabelTap: @escaping (LinkedItemLabel, TextChip) -> Void)
|
||||
{
|
||||
self.onLabelTap = onLabelTap
|
||||
|
||||
let selected = selectedLabels.map { (label: $0, selected: true) }
|
||||
let unselected = allLabels.filter { !selectedLabels.contains($0) }.map { (label: $0, selected: false) }
|
||||
labelItems = (selected + unselected).sorted(by: { left, right in
|
||||
(left.label.name ?? "") < (right.label.name ?? "")
|
||||
})
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
GeometryReader { geometry in
|
||||
self.generateContent(in: geometry)
|
||||
}
|
||||
}
|
||||
.frame(height: totalHeight)
|
||||
}
|
||||
|
||||
private func generateContent(in geom: GeometryProxy) -> some View {
|
||||
var width = CGFloat.zero
|
||||
var height = CGFloat.zero
|
||||
|
||||
return ZStack(alignment: .topLeading) {
|
||||
ForEach(self.labelItems, id: \.label.self) { label in
|
||||
self.item(for: label)
|
||||
.padding([.horizontal, .vertical], 4)
|
||||
.alignmentGuide(.leading, computeValue: { dim in
|
||||
if abs(width - dim.width) > geom.size.width {
|
||||
width = 0
|
||||
height -= dim.height
|
||||
}
|
||||
let result = width
|
||||
if label == self.labelItems.last! {
|
||||
width = 0 // last item
|
||||
} else {
|
||||
width -= dim.width
|
||||
}
|
||||
return result
|
||||
})
|
||||
.alignmentGuide(.top, computeValue: { _ in
|
||||
let result = height
|
||||
if label == self.labelItems.last! {
|
||||
height = 0 // last item
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
.background(viewHeightReader($totalHeight))
|
||||
}
|
||||
|
||||
private func item(for item: (label: LinkedItemLabel, selected: Bool)) -> some View {
|
||||
if item.selected {
|
||||
print(" -- SELECTED LABEL", item.label.name)
|
||||
}
|
||||
print("GETTING ITERATION", iteration)
|
||||
let chip = TextChip(feedItemLabel: item.label, negated: false, checked: item.selected) { chip in
|
||||
onLabelTap(item.label, chip)
|
||||
}
|
||||
return chip
|
||||
}
|
||||
|
||||
private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
|
||||
GeometryReader { geometry -> Color in
|
||||
let rect = geometry.frame(in: .local)
|
||||
DispatchQueue.main.async {
|
||||
binding.wrappedValue = rect.size.height
|
||||
}
|
||||
return .clear
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,6 @@
|
||||
import Foundation
|
||||
import Models
|
||||
import Utils
|
||||
import Views
|
||||
|
||||
final class PrefetchSpeechItemOperation: Operation, URLSessionDelegate {
|
||||
let speechItem: SpeechItem
|
||||
|
||||
@ -3,6 +3,7 @@ import SwiftUI
|
||||
public extension Color {
|
||||
static var appBackground: Color { Color("_background", bundle: .module) }
|
||||
static var appDeepBackground: Color { Color("_deepBackground", bundle: .module) }
|
||||
static var appGreenSuccess: Color { Color("_appGreenSuccess", bundle: .module) }
|
||||
|
||||
// GrayScale -- adapted from Radix Colors
|
||||
static var appGrayBorder: Color { Color("_grayBorder", bundle: .module) }
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.294",
|
||||
"green" : "0.843",
|
||||
"red" : "0.196"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.294",
|
||||
"green" : "0.843",
|
||||
"red" : "0.196"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,14 @@ import Utils
|
||||
public struct TextChip: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
let checked: Bool
|
||||
var onTap: ((TextChip) -> Void)?
|
||||
|
||||
public init(text: String, color: Color, negated: Bool = false) {
|
||||
self.text = text
|
||||
self.color = color
|
||||
self.negated = negated
|
||||
self.checked = false
|
||||
}
|
||||
|
||||
public init?(feedItemLabel: LinkedItemLabel, negated: Bool = false) {
|
||||
@ -17,9 +21,24 @@ public struct TextChip: View {
|
||||
self.text = feedItemLabel.name ?? ""
|
||||
self.color = color
|
||||
self.negated = negated
|
||||
self.checked = false
|
||||
}
|
||||
|
||||
let text: String
|
||||
public init?(feedItemLabel: LinkedItemLabel, negated: Bool = false, checked: Bool = false, onTap: ((TextChip) -> Void)?) {
|
||||
guard let color = Color(hex: feedItemLabel.color ?? "") else {
|
||||
print("RETURNING NUL!")
|
||||
return nil
|
||||
}
|
||||
|
||||
print("TEXT CHIP", feedItemLabel.name, checked)
|
||||
self.text = feedItemLabel.name ?? ""
|
||||
self.color = color
|
||||
self.negated = negated
|
||||
self.onTap = onTap
|
||||
self.checked = checked
|
||||
}
|
||||
|
||||
public let text: String
|
||||
let color: Color
|
||||
let negated: Bool
|
||||
|
||||
@ -53,16 +72,31 @@ public struct TextChip: View {
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Text(text)
|
||||
.strikethrough(color: negated ? textColor : .clear)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 5)
|
||||
.font(.appCaptionBold)
|
||||
.foregroundColor(textColor)
|
||||
.lineLimit(1)
|
||||
.background(Capsule().fill(backgroundColor))
|
||||
.overlay(Capsule().stroke(borderColor, lineWidth: 1))
|
||||
.padding(1)
|
||||
ZStack(alignment: .topTrailing) {
|
||||
Text(text)
|
||||
.strikethrough(color: negated ? textColor : .clear)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 5)
|
||||
.font(.appCaptionBold)
|
||||
.foregroundColor(textColor)
|
||||
.lineLimit(1)
|
||||
.background(Capsule().fill(backgroundColor))
|
||||
.overlay(Capsule().stroke(borderColor, lineWidth: 1))
|
||||
.padding(1)
|
||||
.overlay(alignment: .topTrailing) {
|
||||
if checked {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.appBody)
|
||||
.symbolVariant(.circle.fill)
|
||||
.foregroundStyle(Color.appBackground, Color.appGreenSuccess)
|
||||
.padding([.top, .trailing], -6)
|
||||
}
|
||||
}
|
||||
}.onTapGesture {
|
||||
if let onTap = onTap {
|
||||
onTap(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user