Merge pull request #3222 from omnivore-app/fix/ios-searchbar

iOS searchbar and visual fixes
This commit is contained in:
Jackson Harper 2023-12-11 10:01:31 +08:00 committed by GitHub
commit fbc9f04e3b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 559 additions and 123 deletions

View file

@ -29,7 +29,6 @@ struct AnimatingCellHeight: AnimatableModifier {
@State var hasHighlightMutations = false
@State var searchPresented = false
@State var addLinkPresented = false
@State var settingsPresented = false
@State var isListScrolled = false
@State var listTitle = ""
@State var isEditMode: EditMode = .inactive
@ -54,7 +53,8 @@ struct AnimatingCellHeight: AnimatableModifier {
}
var showFeatureCards: Bool {
viewModel.listConfig.hasFeatureCards &&
isEditMode == .inactive &&
viewModel.listConfig.hasFeatureCards &&
!viewModel.hideFeatureSection &&
viewModel.fetcher.items.count > 0 &&
viewModel.searchTerm.isEmpty &&
@ -179,11 +179,6 @@ struct AnimatingCellHeight: AnimatableModifier {
LibraryAddLinkView()
}
}
.sheet(isPresented: $settingsPresented) {
NavigationView {
ProfileView()
}
}
.task {
if viewModel.fetcher.items.isEmpty {
loadItems(isRefresh: false)
@ -210,14 +205,6 @@ struct AnimatingCellHeight: AnimatableModifier {
}
.frame(maxWidth: .infinity, alignment: .bottomLeading)
}
ToolbarItem(placement: .barTrailing) {
if UIDevice.isIPad, viewModel.folder == "inbox" {
Button(action: { addLinkPresented = true }, label: {
Label("Add Link", systemImage: "plus")
})
}
}
ToolbarItem(placement: UIDevice.isIPhone ? .barLeading : .barTrailing) {
if enableGrid {
Button(
@ -232,29 +219,31 @@ struct AnimatingCellHeight: AnimatableModifier {
Button(
action: { searchPresented = true },
label: {
Image(systemName: "magnifyingglass")
Image.magnifyingGlass
.foregroundColor(Color.appGrayTextContrast)
}
)
}
ToolbarItem(placement: .barTrailing) {
if UIDevice.isIPhone {
Menu(content: {
Button(action: {
isEditMode = isEditMode == .inactive ? .active : .inactive
}, label: {
Text(isEditMode == .inactive ? "Select Multiple" : "End Multiselect")
})
Button(action: { addLinkPresented = true }, label: {
Label("Add Link", systemImage: "plus.circle")
})
Button(action: { settingsPresented = true }, label: {
Label(LocalText.genericProfile, systemImage: "person.circle")
})
}, label: {
Image.utilityMenu
})
.foregroundColor(.appGrayTextContrast)
Button(
action: { isEditMode = isEditMode == .active ? .inactive : .active },
label: {
Image.selectMultiple
.foregroundColor(Color.appGrayTextContrast)
}
)
}
ToolbarItem(placement: .barTrailing) {
if viewModel.folder == "inbox" {
Button(
action: { addLinkPresented = true },
label: {
Image.addLink
.foregroundColor(Color.appGrayTextContrast)
}
)
} else {
EmptyView()
}
}
ToolbarItemGroup(placement: .bottomBar) {
@ -368,7 +357,9 @@ struct AnimatingCellHeight: AnimatableModifier {
@Binding var isListScrolled: Bool
@Binding var prefersListLayout: Bool
@Binding var isEditMode: EditMode
@State private var showAddFeedView = false
@State private var showHideFeatureAlert = false
@State private var showHideFollowingAlert = false
@Binding var selection: Set<String>
@ObservedObject var viewModel: HomeFeedViewModel
@ -379,23 +370,6 @@ struct AnimatingCellHeight: AnimatableModifier {
@State var topItem: Models.LibraryItem?
@ObservedObject var networkMonitor = NetworkMonitor()
// init(listTitle: Binding<String>,
// isListScrolled: Binding<Bool>,
// prefersListLayout: Binding<Bool>,
// isEditMode: Binding<EditMode>,
// selection: Binding<Set<String>>,
// viewModel: HomeFeedViewModel,
// showFeatureCards: Bool)
// {
// self._listTitle = listTitle
// self._isListScrolled = isListScrolled
// self._prefersListLayout = prefersListLayout
// self._isEditMode = isEditMode
// self._selection = selection
// self.viewModel = viewModel
// self.showFeatureCards = showFeatureCards
// }
var filtersHeader: some View {
GeometryReader { reader in
ScrollView(.horizontal, showsIndicators: false) {
@ -603,8 +577,94 @@ struct AnimatingCellHeight: AnimatableModifier {
}
}
var redactedItems: some View {
ForEach(Array(fakeLibraryItems(dataService: dataService).enumerated()), id: \.1.unwrappedID) { _, item in
let horizontalInset = CGFloat(UIDevice.isIPad ? 20 : 10)
LibraryItemCard(item: item, viewer: dataService.currentViewer)
.listRowSeparatorTint(Color.thBorderColor)
.listRowInsets(.init(top: 0, leading: horizontalInset, bottom: 10, trailing: horizontalInset))
}.redacted(reason: .placeholder)
}
var emptyState: some View {
if viewModel.folder == "following" {
return AnyView(
VStack(alignment: .center, spacing: 20) {
Text("You don't have any Feed items.")
.font(Font.system(size: 18, weight: .bold))
Text("Add an RSS/Atom feed")
.foregroundColor(Color.blue)
.onTapGesture {
showAddFeedView = true
}
Text("Hide the Following tab")
.foregroundColor(Color.blue)
.onTapGesture {
showHideFollowingAlert = true
}
}
.frame(minHeight: 400)
.frame(maxWidth: .infinity)
.padding()
)
} else {
return AnyView(Group {
Spacer()
VStack(alignment: .center, spacing: 20) {
Text("No results found for this query")
.font(Font.system(size: 18, weight: .bold))
}
.frame(minHeight: 400)
.frame(maxWidth: .infinity)
.padding()
Spacer()
})
}
}
var listItems: some View {
ForEach(Array(viewModel.fetcher.items.enumerated()), id: \.1.unwrappedID) { _, item in
let horizontalInset = CGFloat(UIDevice.isIPad ? 20 : 10)
FeedCardNavigationLink(
item: item,
isInMultiSelectMode: viewModel.isInMultiSelectMode,
viewModel: viewModel
)
.background(GeometryReader { geometry in
Color.clear
.preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin)
})
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
if value.y < 100, value.y > 0 {
if item.savedAt != nil, topItem != item {
setTopItem(item)
}
}
}
.listRowSeparatorTint(Color.thBorderColor)
.listRowInsets(.init(top: 0, leading: horizontalInset, bottom: 10, trailing: horizontalInset))
.contextMenu {
menuItems(for: item)
}
.swipeActions(edge: .leading, allowsFullSwipe: true) {
ForEach(viewModel.listConfig.leadingSwipeActions, id: \.self) { action in
swipeActionButton(action: action, item: item)
}
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
ForEach(viewModel.listConfig.trailingSwipeActions, id: \.self) { action in
swipeActionButton(action: action, item: item)
}
}
}
}
var body: some View {
let horizontalInset = CGFloat(UIDevice.isIPad ? 20 : 10)
VStack(spacing: 0) {
Color.systemBackground.frame(height: 1)
ScrollViewReader { reader in
@ -643,38 +703,13 @@ struct AnimatingCellHeight: AnimatableModifier {
}
}
ForEach(Array(viewModel.fetcher.items.enumerated()), id: \.1.unwrappedID) { _, item in
FeedCardNavigationLink(
item: item,
isInMultiSelectMode: viewModel.isInMultiSelectMode,
viewModel: viewModel
)
.background(GeometryReader { geometry in
Color.clear
.preference(key: ScrollOffsetPreferenceKey.self, value: geometry.frame(in: .named("scroll")).origin)
})
.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
if value.y < 100, value.y > 0 {
if item.savedAt != nil, topItem != item {
setTopItem(item)
}
}
}
.listRowSeparatorTint(Color.thBorderColor)
.listRowInsets(.init(top: 0, leading: horizontalInset, bottom: 10, trailing: horizontalInset))
.contextMenu {
menuItems(for: item)
}
.swipeActions(edge: .leading, allowsFullSwipe: true) {
ForEach(viewModel.listConfig.leadingSwipeActions, id: \.self) { action in
swipeActionButton(action: action, item: item)
}
}
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
ForEach(viewModel.listConfig.trailingSwipeActions, id: \.self) { action in
swipeActionButton(action: action, item: item)
}
}
if viewModel.showLoadingBar {
redactedItems
} else if viewModel.fetcher.items.isEmpty {
emptyState
.listRowSeparator(.hidden, edges: .all)
} else {
listItems
}
}
}, header: {
@ -698,13 +733,26 @@ struct AnimatingCellHeight: AnimatableModifier {
shouldScrollToTop = true
}
}
.sheet(isPresented: $showAddFeedView) {
NavigationView {
LibraryAddFeedView()
}
}
.alert("The Feature Section will be removed from your library. You can add it back from the filter settings in your profile.",
isPresented: $showHideFeatureAlert) {
Button("OK", role: .destructive) {
viewModel.hideFeatureSection = true
}
Button(LocalText.cancelGeneric, role: .cancel) { self.showHideFeatureAlert = false }
}.introspectNavigationController { nav in
}
.alert("The Following tab will be hidden. You can add it back from the filter settings in your profile.",
isPresented: $showHideFollowingAlert) {
Button("OK", role: .destructive) {
viewModel.hideFollowingTab = true
}
Button(LocalText.cancelGeneric, role: .cancel) { self.showHideFollowingAlert = false }
}
.introspectNavigationController { nav in
nav.navigationBar.shadowImage = UIImage()
nav.navigationBar.setBackgroundImage(UIImage(), for: .default)
}
@ -863,13 +911,24 @@ struct AnimatingCellHeight: AnimatableModifier {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 325, maximum: 400), spacing: 16)], alignment: .center, spacing: 30) {
ForEach(viewModel.fetcher.items) { item in
GridCardNavigationLink(
item: item,
actionHandler: { contextMenuActionHandler(item: item, action: $0) },
isContextMenuOpen: $isContextMenuOpen,
viewModel: viewModel
)
if viewModel.showLoadingBar {
ForEach(fakeLibraryItems(dataService: dataService)) { item in
GridCardNavigationLink(
item: item,
actionHandler: { contextMenuActionHandler(item: item, action: $0) },
isContextMenuOpen: $isContextMenuOpen,
viewModel: viewModel
)
}.redacted(reason: .placeholder)
} else {
ForEach(viewModel.fetcher.items) { item in
GridCardNavigationLink(
item: item,
actionHandler: { contextMenuActionHandler(item: item, action: $0) },
isContextMenuOpen: $isContextMenuOpen,
viewModel: viewModel
)
}
}
Spacer()
}
@ -949,3 +1008,21 @@ struct LinkDestination: View {
}
}
}
func fakeLibraryItems(dataService: DataService) -> [Models.LibraryItem] {
let temp = Models.LibraryItem(context: dataService.viewContext)
temp.id = UUID().uuidString
temp.wordsCount = 100
temp.author = "the author"
temp.siteName = "omnivore dot app"
temp.title = "This is a temporary title for a fake item"
temp.highlights = []
temp.imageURLString = "https://localhost/"
return Array(
repeatElement(temp, count: 20)
.map { item in
item.id = UUID().uuidString
return item
}
)
}

View file

@ -44,6 +44,8 @@ import Views
@AppStorage(UserDefaultKey.hideFeatureSection.rawValue) var hideFeatureSection = false
@AppStorage(UserDefaultKey.lastSelectedFeaturedItemFilter.rawValue) var featureFilter = FeaturedItemFilter.continueReading.rawValue
@AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false
@Published var appliedFilter: InternalFilter? {
didSet {
let filterKey = UserDefaults.standard.string(forKey: "lastSelected-\(folder)-filter") ?? folder
@ -152,10 +154,9 @@ import Views
func loadItems(dataService: DataService, isRefresh: Bool) async {
isLoading = true
showLoadingBar = true
showLoadingBar = isRefresh
await fetcher.loadItems(dataService: dataService, filterState: filterState, isRefresh: isRefresh)
updateFeatureFilter(context: dataService.viewContext, filter: FeaturedItemFilter(rawValue: featureFilter))
isLoading = false
@ -164,12 +165,10 @@ import Views
func loadMoreItems(dataService: DataService, filterState: FetcherFilterState, isRefresh: Bool) async {
isLoading = true
showLoadingBar = true
await fetcher.loadMoreItems(dataService: dataService, filterState: filterState, isRefresh: isRefresh)
isLoading = false
showLoadingBar = false
}
func loadFeatureItems(context: NSManagedObjectContext, predicate: NSPredicate, sort: NSSortDescriptor) async -> [Models.LibraryItem] {

View file

@ -0,0 +1,156 @@
import Introspect
import Models
import Services
import SwiftUI
import Views
@MainActor final class LibraryAddFeedViewModel: NSObject, ObservableObject {
@Published var isLoading = false
@Published var errorMessage: String = ""
@Published var showErrorMessage: Bool = false
@Environment(\.dismiss) private var dismiss
func addLink(dataService: DataService, newLinkURL: String, dismiss: DismissAction) {
isLoading = true
Task {
if URL(string: newLinkURL) == nil {
error("Invalid link")
} else {
let result = try? await dataService.saveURL(id: UUID().uuidString, url: newLinkURL)
if result == nil {
error("Error adding link")
} else {
dismiss()
}
}
isLoading = false
}
}
func error(_ msg: String) {
errorMessage = msg
showErrorMessage = true
isLoading = false
}
}
struct LibraryAddFeedView: View {
@StateObject var viewModel = LibraryAddFeedViewModel()
@State var newLinkURL: String = ""
@EnvironmentObject var dataService: DataService
@Environment(\.dismiss) private var dismiss
enum FocusField: Hashable {
case addLinkEditor
}
@FocusState private var focusedField: FocusField?
var body: some View {
Group {
#if os(iOS)
Form {
innerBody
.navigationTitle("Add Link")
.navigationBarTitleDisplayMode(.inline)
}
#else
innerBody
#endif
}
#if os(macOS)
.padding()
#endif
.onAppear {
focusedField = .addLinkEditor
}
.navigationTitle("Add Link")
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
dismissButton
}
ToolbarItem(placement: .navigationBarTrailing) {
viewModel.isLoading ? AnyView(ProgressView()) : AnyView(addButton)
}
}
#endif
.alert(viewModel.errorMessage,
isPresented: $viewModel.showErrorMessage) {
Button(LocalText.genericOk, role: .cancel) { viewModel.showErrorMessage = false }
}
}
var cancelButton: some View {
Button(
action: { dismiss() },
label: { Text(LocalText.cancelGeneric).foregroundColor(.appGrayTextContrast) }
)
}
var pasteboardString: String? {
#if os(iOS)
UIPasteboard.general.url?.absoluteString
#else
NSPasteboard.general.string(forType: NSPasteboard.PasteboardType.URL)
#endif
}
var innerBody: some View {
Group {
TextField("Add Link", text: $newLinkURL)
#if os(iOS)
.keyboardType(.URL)
#endif
.autocorrectionDisabled(true)
.textFieldStyle(StandardTextFieldStyle())
.focused($focusedField, equals: .addLinkEditor)
Button(action: {
if let url = pasteboardString {
newLinkURL = url
} else {
viewModel.error("No URL on pasteboard")
}
}, label: {
Text("Get from pasteboard")
})
#if os(macOS)
Spacer()
HStack {
cancelButton
Spacer()
addButton
}
.frame(maxWidth: .infinity)
#endif
}
}
var addButton: some View {
Button(
action: {
viewModel.addLink(dataService: dataService, newLinkURL: newLinkURL, dismiss: dismiss)
},
label: { Text("Add").bold() }
)
.keyboardShortcut(.defaultAction)
.onSubmit {
viewModel.addLink(dataService: dataService, newLinkURL: newLinkURL, dismiss: dismiss)
}
.disabled(viewModel.isLoading)
}
var dismissButton: some View {
Button(
action: { dismiss() },
label: { Text(LocalText.genericClose) }
)
.disabled(viewModel.isLoading)
}
}

View file

@ -33,11 +33,11 @@
performTypeahead(searchTerm)
}
func performSearch(_: String) {
// let term = searchTerm.trimmingCharacters(in: Foundation.CharacterSet.whitespacesAndNewlines)
// viewModel.saveRecentSearch(dataService: dataService, searchTerm: term)
// recents = viewModel.recentSearches(dataService: dataService)
// homeFeedViewModel.searchTerm = term
func performSearch(_ searchTerm: String) {
let term = searchTerm.trimmingCharacters(in: Foundation.CharacterSet.whitespacesAndNewlines)
viewModel.saveRecentSearch(dataService: dataService, searchTerm: term)
recents = viewModel.recentSearches(dataService: dataService)
homeFeedViewModel.searchTerm = term
dismiss()
}

View file

@ -19,6 +19,7 @@ struct LibraryTabView: View {
@EnvironmentObject var dataService: DataService
@EnvironmentObject var audioController: AudioController
@AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false
@AppStorage(UserDefaultKey.lastSelectedTabItem.rawValue) var selectedTab = "inbox"
@State var showExpandedAudioPlayer = false
@ -53,11 +54,13 @@ struct LibraryTabView: View {
var body: some View {
VStack(spacing: 0) {
TabView(selection: $selectedTab) {
NavigationView {
HomeFeedContainerView(viewModel: followingViewModel)
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack)
}.tag("following")
if !hideFollowingTab {
NavigationView {
HomeFeedContainerView(viewModel: followingViewModel)
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack)
}.tag("following")
}
NavigationView {
HomeFeedContainerView(viewModel: libraryViewModel)
@ -80,7 +83,7 @@ struct LibraryTabView: View {
.frame(height: 1)
.frame(maxWidth: .infinity)
}
CustomTabBar(selectedTab: $selectedTab)
CustomTabBar(selectedTab: $selectedTab, hideFollowingTab: hideFollowingTab)
.padding(0)
}
.fullScreenCover(isPresented: $showExpandedAudioPlayer) {

View file

@ -11,6 +11,7 @@ import Views
@Published var networkError = false
@Published var libraryFilters = [InternalFilter]()
@AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false
@AppStorage(UserDefaultKey.hideFeatureSection.rawValue) var hideFeatureSection = false
func loadFilters(dataService: DataService) async {
@ -50,7 +51,8 @@ struct FiltersView: View {
private var innerBody: some View {
List {
Section {
Toggle("Hide Feature Section", isOn: $viewModel.hideFeatureSection)
Toggle("Hide following tab", isOn: $viewModel.hideFollowingTab)
Toggle("Hide feature section", isOn: $viewModel.hideFeatureSection)
}
Section(header: Text("Saved Searches")) {

View file

@ -3,9 +3,13 @@ import SwiftUI
struct CustomTabBar: View {
@Binding var selectedTab: String
let hideFollowingTab: Bool
var body: some View {
HStack(spacing: 0) {
TabBarButton(key: "following", image: Image.tabFollowing, selectedTab: $selectedTab)
if !hideFollowingTab {
TabBarButton(key: "following", image: Image.tabFollowing, selectedTab: $selectedTab)
}
TabBarButton(key: "inbox", image: Image.tabLibrary, selectedTab: $selectedTab)
TabBarButton(key: "profile", image: Image.tabProfile, selectedTab: $selectedTab)
}

View file

@ -0,0 +1,31 @@
//
// File.swift
//
//
// Created by Jackson Harper on 12/8/23.
//
import Foundation
import NaturalLanguage
public func extractFirstFewWords(_ title: String) -> String {
let languageRecognizer = NLLanguageRecognizer()
languageRecognizer.processString(title)
let language = languageRecognizer.dominantLanguage ?? NLLanguage.english
let tokenizer = NLTokenizer(unit: .word)
tokenizer.setLanguage(language)
tokenizer.string = title
var words: [String] = []
tokenizer.enumerateTokens(in: title.startIndex ..< title.endIndex) { range, _ in
let word = String(title[range])
words.append(word)
return true
}
print("WORDS: ", words)
let truncatedTitle = words.prefix(2).joined(separator: " ")
return truncatedTitle
}

View file

@ -44,6 +44,7 @@ public extension Color {
static var themeSolidBackground: Color { Color("_themeSolidBackground", bundle: .module) }
static var thBorderColor: Color { Color("thBorderColor", bundle: .module) }
static var thLibrarySeparator: Color { Color("thLibrarySeparator", bundle: .module) }
static var thLightWhiteGrey: Color { Color("_themeLightWhiteGrey", bundle: .module) }
static var thFeatureSeparator: Color { Color("featureSeparator", bundle: .module) }

View file

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xED",
"green" : "0xED",
"red" : "0xED"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xED",
"green" : "0xED",
"red" : "0xED"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -107,18 +107,26 @@ public struct GridCard: View {
.cornerRadius(5)
}
var fallbackFont: Font {
if let uifont = UIFont(name: "Futura Bold", size: 16) {
return Font(uifont)
}
return Font.system(size: 16)
}
var fallbackImage: some View {
GeometryReader { geo in
HStack {
Text(item.unwrappedTitle.prefix(1))
.font(Font.system(size: 128, weight: .bold))
.offset(CGSize(width: -48, height: 12))
.frame(alignment: .bottomLeading)
.foregroundColor(Gradient.randomColor(str: item.unwrappedTitle, offset: 1))
Text(item.unwrappedTitle)
.font(fallbackFont)
.frame(alignment: .center)
.multilineTextAlignment(.leading)
.lineLimit(2)
.padding(10)
.foregroundColor(Color.themeMiddleGray)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Gradient.randomColor(str: item.unwrappedTitle, offset: 0))
.background(LinearGradient(gradient: Gradient(fromStr: item.unwrappedTitle)!, startPoint: .top, endPoint: .bottom))
.background(Color.thLightWhiteGrey)
.frame(width: geo.size.width, height: geo.size.height)
}
}

View file

@ -66,17 +66,25 @@ public struct LibraryFeatureCard: View {
.cornerRadius(5)
}
var fallbackFont: Font {
if let uifont = UIFont(name: "Futura Bold", size: 16) {
return Font(uifont)
}
return Font.system(size: 16)
}
var fallbackImage: some View {
HStack {
Text(item.unwrappedTitle.prefix(1))
.font(Font.system(size: 128, weight: .bold))
.offset(CGSize(width: -48, height: 12))
.frame(alignment: .bottomLeading)
.foregroundColor(Gradient.randomColor(str: item.unwrappedTitle, offset: 1))
Text(item.unwrappedTitle)
.font(fallbackFont)
.frame(alignment: .center)
.multilineTextAlignment(.leading)
.lineLimit(2)
.padding(10)
.foregroundColor(Color.themeMiddleGray)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Gradient.randomColor(str: item.unwrappedTitle, offset: 0))
.background(LinearGradient(gradient: Gradient(fromStr: item.unwrappedTitle)!, startPoint: .top, endPoint: .bottom))
.background(Color.thLightWhiteGrey)
.frame(width: 146, height: 90)
}

View file

@ -21,6 +21,10 @@ public extension Image {
static var readerSettings: Image { Image("reader-settings", bundle: .module) }
static var utilityMenu: Image { Image("utility-menu", bundle: .module) }
static var addLink: Image { Image("add-link", bundle: .module) }
static var selectMultiple: Image { Image("select-multiple", bundle: .module) }
static var magnifyingGlass: Image { Image("magnifying-glass", bundle: .module) }
static var archive: Image { Image("archive", bundle: .module) }
static var unarchive: Image { Image("unarchive", bundle: .module) }
static var remove: Image { Image("remove", bundle: .module) }

View file

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "add-link.svg",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View file

@ -0,0 +1,11 @@
<svg width="26" height="25" viewBox="0 0 26 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6802_32635)">
<path d="M13.3398 5.20801V19.7913" stroke="#EDEDED" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.0481 12.5H20.6314" stroke="#EDEDED" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_6802_32635">
<rect width="25" height="25" fill="white" transform="translate(0.839844)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 519 B

View file

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "magnifying-glass.svg",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View file

@ -0,0 +1,11 @@
<svg width="26" height="25" viewBox="0 0 26 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6802_32619)">
<path d="M5.08984 10.6667C5.08984 11.5093 5.25582 12.3437 5.57828 13.1222C5.90075 13.9007 6.3734 14.6081 6.96924 15.2039C7.56508 15.7998 8.27245 16.2724 9.05096 16.5949C9.82946 16.9174 10.6639 17.0833 11.5065 17.0833C12.3492 17.0833 13.1836 16.9174 13.9621 16.5949C14.7406 16.2724 15.4479 15.7998 16.0438 15.2039C16.6396 14.6081 17.1123 13.9007 17.4347 13.1222C17.7572 12.3437 17.9232 11.5093 17.9232 10.6667C17.9232 9.82402 17.7572 8.98962 17.4347 8.21111C17.1123 7.43261 16.6396 6.72524 16.0438 6.1294C15.4479 5.53356 14.7406 5.06091 13.9621 4.73844C13.1836 4.41597 12.3492 4.25 11.5065 4.25C10.6639 4.25 9.82946 4.41597 9.05096 4.73844C8.27245 5.06091 7.56508 5.53356 6.96924 6.1294C6.3734 6.72524 5.90075 7.43261 5.57828 8.21111C5.25582 8.98962 5.08984 9.82402 5.08984 10.6667Z" stroke="#EDEDED" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.5898 20.75L16.0898 15.25" stroke="#EDEDED" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_6802_32619">
<rect width="25" height="25" fill="white" transform="translate(0.839844)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,24 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "multiple-select.svg",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

View file

@ -0,0 +1,11 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_6802_32631)">
<path d="M10.3398 12.2402L13.3398 15.2402L21.3398 7.24023" stroke="#EDEDED" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.3398 13.2402V19.2402C21.3398 19.7707 21.1291 20.2794 20.7541 20.6544C20.379 21.0295 19.8703 21.2402 19.3398 21.2402H7.33984C6.80941 21.2402 6.3007 21.0295 5.92563 20.6544C5.55056 20.2794 5.33984 19.7707 5.33984 19.2402V7.24023C5.33984 6.7098 5.55056 6.20109 5.92563 5.82602C6.3007 5.45095 6.80941 5.24023 7.33984 5.24023H16.3398" stroke="#EDEDED" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_6802_32631">
<rect width="25" height="25" fill="white" transform="translate(0.839844 0.740234)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 864 B