Update iconography and listview for ios, bump android
@ -75,6 +75,7 @@ android {
|
|||||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
namespace 'app.omnivore.omnivore'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
package="app.omnivore.omnivore">
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
|
|||||||
@ -3,13 +3,18 @@ buildscript {
|
|||||||
compose_version = '1.3.1'
|
compose_version = '1.3.1'
|
||||||
lifecycle_version = '2.5.1'
|
lifecycle_version = '2.5.1'
|
||||||
hilt_version = '2.44.2'
|
hilt_version = '2.44.2'
|
||||||
gradle_plugin_version = '7.3.1'
|
gradle_plugin_version = '7.4.2'
|
||||||
room_version = '2.4.3'
|
room_version = '2.4.3'
|
||||||
|
kotlin_version = '1.9.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
||||||
classpath "com.android.tools.build:gradle:$gradle_plugin_version"
|
classpath "com.android.tools.build:gradle:$gradle_plugin_version"
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -144,6 +144,15 @@
|
|||||||
"version" : "2.30908.0"
|
"version" : "2.30908.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "popupview",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/exyte/PopupView.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "68349a0ae704b9a7041f756f3f4f460ddbf7ba8d",
|
||||||
|
"version" : "2.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "promises",
|
"identity" : "promises",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|||||||
@ -25,7 +25,8 @@ let package = Package(
|
|||||||
dependencies: [
|
dependencies: [
|
||||||
"Models",
|
"Models",
|
||||||
.product(name: "Introspect", package: "SwiftUI-Introspect"),
|
.product(name: "Introspect", package: "SwiftUI-Introspect"),
|
||||||
.product(name: "MarkdownUI", package: "swift-markdown-ui")
|
.product(name: "MarkdownUI", package: "swift-markdown-ui"),
|
||||||
|
.productItem(name: "PopupView", package: "PopupView")
|
||||||
],
|
],
|
||||||
resources: [.process("Resources")]
|
resources: [.process("Resources")]
|
||||||
),
|
),
|
||||||
@ -68,7 +69,8 @@ var dependencies: [Package.Dependency] {
|
|||||||
.package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4"),
|
.package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4"),
|
||||||
.package(url: "https://github.com/segmentio/analytics-swift.git", .upToNextMajor(from: "1.0.0")),
|
.package(url: "https://github.com/segmentio/analytics-swift.git", .upToNextMajor(from: "1.0.0")),
|
||||||
.package(url: "https://github.com/google/GoogleSignIn-iOS", from: "6.2.2"),
|
.package(url: "https://github.com/google/GoogleSignIn-iOS", from: "6.2.2"),
|
||||||
.package(url: "https://github.com/gonzalezreal/swift-markdown-ui", from: "2.0.0")
|
.package(url: "https://github.com/gonzalezreal/swift-markdown-ui", from: "2.0.0"),
|
||||||
|
.package(url: "https://github.com/exyte/PopupView.git", from: "2.6.0")
|
||||||
]
|
]
|
||||||
// Comment out following line for macOS build
|
// Comment out following line for macOS build
|
||||||
deps.append(.package(url: "https://github.com/PSPDFKit/PSPDFKit-SP", from: "12.0.1"))
|
deps.append(.package(url: "https://github.com/PSPDFKit/PSPDFKit-SP", from: "12.0.1"))
|
||||||
|
|||||||
@ -111,7 +111,7 @@
|
|||||||
.frame(width: dim, height: dim)
|
.frame(width: dim, height: dim)
|
||||||
.cornerRadius(6)
|
.cornerRadius(6)
|
||||||
|
|
||||||
Image(systemName: "headphones")
|
Image.headphones
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
.frame(width: dim / 2, height: dim / 2)
|
.frame(width: dim / 2, height: dim / 2)
|
||||||
|
|||||||
@ -29,6 +29,8 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
@State var searchPresented = false
|
@State var searchPresented = false
|
||||||
@State var addLinkPresented = false
|
@State var addLinkPresented = false
|
||||||
@State var settingsPresented = false
|
@State var settingsPresented = false
|
||||||
|
@State var isListScrolled = false
|
||||||
|
@State var listTitle = ""
|
||||||
|
|
||||||
@EnvironmentObject var dataService: DataService
|
@EnvironmentObject var dataService: DataService
|
||||||
@EnvironmentObject var audioController: AudioController
|
@EnvironmentObject var audioController: AudioController
|
||||||
@ -53,6 +55,8 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
HomeFeedView(
|
HomeFeedView(
|
||||||
|
listTitle: $listTitle,
|
||||||
|
isListScrolled: $isListScrolled,
|
||||||
prefersListLayout: $prefersListLayout,
|
prefersListLayout: $prefersListLayout,
|
||||||
viewModel: viewModel
|
viewModel: viewModel
|
||||||
)
|
)
|
||||||
@ -93,17 +97,18 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .barLeading) {
|
ToolbarItem(placement: .barLeading) {
|
||||||
// Button(action: {
|
VStack(alignment: .leading) {
|
||||||
// viewModel.showFiltersModal = true
|
|
||||||
// }, label: {
|
|
||||||
HStack(alignment: .center) {
|
|
||||||
let title = (LinkedItemFilter(rawValue: viewModel.appliedFilter) ?? LinkedItemFilter.inbox).displayName
|
let title = (LinkedItemFilter(rawValue: viewModel.appliedFilter) ?? LinkedItemFilter.inbox).displayName
|
||||||
|
|
||||||
Text(title)
|
Text(title)
|
||||||
.font(Font.system(size: 18, weight: .semibold))
|
.font(Font.system(size: isListScrolled ? 10 : 18, weight: .semibold))
|
||||||
// Image(systemName: "chevron.down")
|
|
||||||
// .font(Font.system(size: 13, weight: .regular))
|
if isListScrolled {
|
||||||
|
Text(listTitle)
|
||||||
|
.font(Font.system(size: 15, weight: .regular))
|
||||||
|
.foregroundColor(Color.appGrayText)
|
||||||
|
}
|
||||||
}.frame(maxWidth: .infinity, alignment: .leading)
|
}.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .barTrailing) {
|
ToolbarItem(placement: .barTrailing) {
|
||||||
Button("", action: {})
|
Button("", action: {})
|
||||||
@ -148,9 +153,7 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
Label("Add Link", systemImage: "plus.square")
|
Label("Add Link", systemImage: "plus.square")
|
||||||
})
|
})
|
||||||
}, label: {
|
}, label: {
|
||||||
Image(systemName: "ellipsis")
|
Image.utilityMenu
|
||||||
.foregroundColor(.appGrayTextContrast)
|
|
||||||
.frame(width: 24, height: 24)
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
@ -232,13 +235,19 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
@MainActor
|
@MainActor
|
||||||
struct HomeFeedView: View {
|
struct HomeFeedView: View {
|
||||||
@EnvironmentObject var dataService: DataService
|
@EnvironmentObject var dataService: DataService
|
||||||
|
|
||||||
|
@Binding var listTitle: String
|
||||||
|
@Binding var isListScrolled: Bool
|
||||||
@Binding var prefersListLayout: Bool
|
@Binding var prefersListLayout: Bool
|
||||||
@ObservedObject var viewModel: HomeFeedViewModel
|
@ObservedObject var viewModel: HomeFeedViewModel
|
||||||
|
|
||||||
|
@State var showSnackbar = false
|
||||||
|
@State var snackbarOperation: SnackbarOperation?
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
if prefersListLayout || !enableGrid {
|
if prefersListLayout || !enableGrid {
|
||||||
HomeFeedListView(prefersListLayout: $prefersListLayout, viewModel: viewModel)
|
HomeFeedListView(listTitle: $listTitle, isListScrolled: $isListScrolled, prefersListLayout: $prefersListLayout, viewModel: viewModel)
|
||||||
} else {
|
} else {
|
||||||
HomeFeedGridView(viewModel: viewModel)
|
HomeFeedGridView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
@ -251,6 +260,33 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
self.viewModel.negatedLabels = $1
|
self.viewModel.negatedLabels = $1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.popup(isPresented: $showSnackbar) {
|
||||||
|
if let operation = snackbarOperation {
|
||||||
|
Snackbar(isShowing: $showSnackbar, operation: operation)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
} customize: {
|
||||||
|
$0
|
||||||
|
.type(.toast)
|
||||||
|
.autohideIn(2)
|
||||||
|
.position(.bottom)
|
||||||
|
.animation(.spring())
|
||||||
|
.closeOnTapOutside(true)
|
||||||
|
}
|
||||||
|
.onReceive(NSNotification.operationSuccessPublisher) { notification in
|
||||||
|
if let message = notification.userInfo?["message"] as? String {
|
||||||
|
snackbarOperation = SnackbarOperation(message: message,
|
||||||
|
undoAction: notification.userInfo?["undoAction"] as? SnackbarUndoAction)
|
||||||
|
showSnackbar = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onReceive(NSNotification.operationFailedPublisher) { notification in
|
||||||
|
if let message = notification.userInfo?["message"] as? String {
|
||||||
|
showSnackbar = true
|
||||||
|
snackbarOperation = SnackbarOperation(message: message, undoAction: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,6 +294,8 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
@EnvironmentObject var dataService: DataService
|
@EnvironmentObject var dataService: DataService
|
||||||
@EnvironmentObject var audioController: AudioController
|
@EnvironmentObject var audioController: AudioController
|
||||||
|
|
||||||
|
@Binding var listTitle: String
|
||||||
|
@Binding var isListScrolled: Bool
|
||||||
@Binding var prefersListLayout: Bool
|
@Binding var prefersListLayout: Bool
|
||||||
@State private var showHideFeatureAlert = false
|
@State private var showHideFeatureAlert = false
|
||||||
|
|
||||||
@ -417,6 +455,48 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ScrollOffsetPreferenceKey: PreferenceKey {
|
||||||
|
static var defaultValue: CGPoint = .zero
|
||||||
|
|
||||||
|
static func reduce(value _: inout CGPoint, nextValue _: () -> CGPoint) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@State var topItem: LinkedItem?
|
||||||
|
|
||||||
|
func setTopItem(_ item: LinkedItem) {
|
||||||
|
if let date = item.savedAt, let daysAgo = Calendar.current.dateComponents([.day], from: date, to: Date()).day {
|
||||||
|
if daysAgo < 1 {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.timeStyle = .none
|
||||||
|
formatter.dateStyle = .long
|
||||||
|
formatter.doesRelativeDateFormatting = true
|
||||||
|
if let str = formatter.string(for: date) {
|
||||||
|
listTitle = str.capitalized
|
||||||
|
}
|
||||||
|
} else if daysAgo < 2 {
|
||||||
|
let formatter = RelativeDateTimeFormatter()
|
||||||
|
formatter.dateTimeStyle = .named
|
||||||
|
if let str = formatter.string(for: date) {
|
||||||
|
listTitle = str.capitalized
|
||||||
|
}
|
||||||
|
} else if daysAgo < 5 {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateFormat = "EEEE"
|
||||||
|
if let str = formatter.string(for: date) {
|
||||||
|
listTitle = str
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .medium
|
||||||
|
formatter.timeStyle = .none
|
||||||
|
if let str = formatter.string(for: date) {
|
||||||
|
listTitle = str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
topItem = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
let horizontalInset = CGFloat(UIDevice.isIPad ? 20 : 10)
|
let horizontalInset = CGFloat(UIDevice.isIPad ? 20 : 10)
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@ -443,6 +523,16 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
|
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||||
.listRowSeparator(.hidden, edges: .all)
|
.listRowSeparator(.hidden, edges: .all)
|
||||||
.modifier(AnimatingCellHeight(height: 190 + (Color.isDarkMode ? 13 : 13)))
|
.modifier(AnimatingCellHeight(height: 190 + (Color.isDarkMode ? 13 : 13)))
|
||||||
|
.onDisappear {
|
||||||
|
withAnimation {
|
||||||
|
isListScrolled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
withAnimation {
|
||||||
|
isListScrolled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach(viewModel.items) { item in
|
ForEach(viewModel.items) { item in
|
||||||
@ -450,6 +540,19 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
item: item,
|
item: item,
|
||||||
viewModel: viewModel
|
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 let date = item.savedAt {
|
||||||
|
if topItem != item {
|
||||||
|
setTopItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.listRowSeparatorTint(Color.thBorderColor)
|
.listRowSeparatorTint(Color.thBorderColor)
|
||||||
.listRowInsets(.init(top: 0, leading: horizontalInset, bottom: 10, trailing: horizontalInset))
|
.listRowInsets(.init(top: 0, leading: horizontalInset, bottom: 10, trailing: horizontalInset))
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
@ -470,6 +573,7 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||||||
.padding(0)
|
.padding(0)
|
||||||
.listStyle(PlainListStyle())
|
.listStyle(PlainListStyle())
|
||||||
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
|
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||||
|
.coordinateSpace(name: "scroll")
|
||||||
}
|
}
|
||||||
.alert("The Feature Section will be removed from your library. You can add it back from the filter settings in your profile.",
|
.alert("The Feature Section will be removed from your library. You can add it back from the filter settings in your profile.",
|
||||||
isPresented: $showHideFeatureAlert) {
|
isPresented: $showHideFeatureAlert) {
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import Views
|
|||||||
@MainActor
|
@MainActor
|
||||||
struct HomeView: View {
|
struct HomeView: View {
|
||||||
@State private var viewModel: HomeFeedViewModel
|
@State private var viewModel: HomeFeedViewModel
|
||||||
|
@State var showSnackbar = false
|
||||||
|
@State var snackbarOperation: SnackbarOperation?
|
||||||
|
|
||||||
init(viewModel: HomeFeedViewModel) {
|
init(viewModel: HomeFeedViewModel) {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
|
|||||||
@ -7,8 +7,10 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Models
|
import Models
|
||||||
|
import PopupView
|
||||||
import Services
|
import Services
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Views
|
||||||
|
|
||||||
struct LibraryTabView: View {
|
struct LibraryTabView: View {
|
||||||
@EnvironmentObject var dataService: DataService
|
@EnvironmentObject var dataService: DataService
|
||||||
|
|||||||
@ -10,15 +10,18 @@ import Views
|
|||||||
]
|
]
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
|
innerBody
|
||||||
|
}
|
||||||
|
|
||||||
|
public var innerBody: some View {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if UIDevice.isIPad {
|
if UIDevice.isIPad {
|
||||||
splitView
|
return AnyView(splitView)
|
||||||
} else {
|
} else {
|
||||||
// HomeView()
|
return AnyView(LibraryTabView())
|
||||||
LibraryTabView()
|
|
||||||
}
|
}
|
||||||
#elseif os(macOS)
|
#else
|
||||||
splitView
|
return AnyView(splitView)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import Models
|
import Models
|
||||||
|
import PopupView
|
||||||
import Services
|
import Services
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Views
|
import Views
|
||||||
@ -38,6 +39,14 @@ struct NewsletterEmailsView: View {
|
|||||||
@EnvironmentObject var dataService: DataService
|
@EnvironmentObject var dataService: DataService
|
||||||
@StateObject var viewModel = NewsletterEmailsViewModel()
|
@StateObject var viewModel = NewsletterEmailsViewModel()
|
||||||
|
|
||||||
|
@State var showSnackbar = false
|
||||||
|
@State var snackbarOperation: SnackbarOperation?
|
||||||
|
|
||||||
|
func snackbar(message: String) {
|
||||||
|
snackbarOperation = SnackbarOperation(message: message, undoAction: nil)
|
||||||
|
showSnackbar = true
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -52,6 +61,20 @@ struct NewsletterEmailsView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.task { await viewModel.loadEmails(dataService: dataService) }
|
.task { await viewModel.loadEmails(dataService: dataService) }
|
||||||
|
.popup(isPresented: $showSnackbar) {
|
||||||
|
if let operation = snackbarOperation {
|
||||||
|
Snackbar(isShowing: $showSnackbar, operation: operation)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
} customize: {
|
||||||
|
$0
|
||||||
|
.type(.toast)
|
||||||
|
.autohideIn(2)
|
||||||
|
.position(.bottom)
|
||||||
|
.animation(.spring())
|
||||||
|
.closeOnTapOutside(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var innerBody: some View {
|
private var innerBody: some View {
|
||||||
@ -87,7 +110,7 @@ struct NewsletterEmailsView: View {
|
|||||||
pasteBoard.writeObjects([newsletterEmail.unwrappedEmail as NSString])
|
pasteBoard.writeObjects([newsletterEmail.unwrappedEmail as NSString])
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Snackbar.show(message: "Email copied")
|
snackbar(message: "Email copied")
|
||||||
},
|
},
|
||||||
label: { Text(newsletterEmail.unwrappedEmail) }
|
label: { Text(newsletterEmail.unwrappedEmail) }
|
||||||
)
|
)
|
||||||
@ -95,6 +118,7 @@ struct NewsletterEmailsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigationTitle(LocalText.emailsGeneric)
|
.navigationTitle(LocalText.emailsGeneric)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
try await dataService.leaveGroup(groupID: recommendationGroup.id)
|
try await dataService.leaveGroup(groupID: recommendationGroup.id)
|
||||||
Snackbar.show(message: "You have left the club.")
|
// Snackbar.show(message: "You have left the club.")
|
||||||
} catch {
|
} catch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -182,7 +182,7 @@
|
|||||||
pasteBoard.writeObjects([highlightParams.quote as NSString])
|
pasteBoard.writeObjects([highlightParams.quote as NSString])
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Snackbar.show(message: "Invite link copied")
|
// Snackbar.show(message: "Invite link copied")
|
||||||
}, label: {
|
}, label: {
|
||||||
Text("[\(viewModel.recommendationGroup.inviteUrl)](\(viewModel.recommendationGroup.inviteUrl))")
|
Text("[\(viewModel.recommendationGroup.inviteUrl)](\(viewModel.recommendationGroup.inviteUrl))")
|
||||||
.font(.appCaption)
|
.font(.appCaption)
|
||||||
|
|||||||
@ -114,7 +114,7 @@ struct SubscriptionsView: View {
|
|||||||
Button("Yes", role: .destructive) {
|
Button("Yes", role: .destructive) {
|
||||||
Task {
|
Task {
|
||||||
let unsubscribed = await viewModel.cancelSubscription(dataService: dataService)
|
let unsubscribed = await viewModel.cancelSubscription(dataService: dataService)
|
||||||
Snackbar.show(message: unsubscribed ? "Subscription cancelled." : "Could not unsubscribe.")
|
// Snackbar.show(message: unsubscribed ? "Subscription cancelled." : "Could not unsubscribe.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button("No", role: .cancel) {
|
Button("No", role: .cancel) {
|
||||||
|
|||||||
@ -64,17 +64,6 @@ struct InnerRootView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
.snackBar(isShowing: $viewModel.showSnackbar, operation: viewModel.snackbarOperation)
|
|
||||||
// Schedule the dismissal every time we present the snackbar.
|
|
||||||
.onChange(of: viewModel.showSnackbar) { newValue in
|
|
||||||
if newValue {
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
|
|
||||||
withAnimation {
|
|
||||||
viewModel.showSnackbar = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
WelcomeView()
|
WelcomeView()
|
||||||
@ -92,21 +81,6 @@ struct InnerRootView: View {
|
|||||||
.frame(minWidth: 400, idealWidth: 1200, minHeight: 400, idealHeight: 1200)
|
.frame(minWidth: 400, idealWidth: 1200, minHeight: 400, idealHeight: 1200)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
|
||||||
.onReceive(NSNotification.operationSuccessPublisher) { notification in
|
|
||||||
if let message = notification.userInfo?["message"] as? String {
|
|
||||||
viewModel.snackbarOperation = SnackbarOperation(message: message,
|
|
||||||
undoAction: notification.userInfo?["undoAction"] as? SnackbarUndoAction)
|
|
||||||
viewModel.showSnackbar = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onReceive(NSNotification.operationFailedPublisher) { notification in
|
|
||||||
if let message = notification.userInfo?["message"] as? String {
|
|
||||||
viewModel.showSnackbar = true
|
|
||||||
viewModel.snackbarOperation = SnackbarOperation(message: message, undoAction: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
.onOpenURL { Authenticator.handleGoogleURL(url: $0) }
|
.onOpenURL { Authenticator.handleGoogleURL(url: $0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,8 +18,6 @@ public final class RootViewModel: ObservableObject {
|
|||||||
@AppStorage(UserDefaultKey.shouldShowNewFeaturePrimer.rawValue) var shouldShowNewFeaturePrimer = false
|
@AppStorage(UserDefaultKey.shouldShowNewFeaturePrimer.rawValue) var shouldShowNewFeaturePrimer = false
|
||||||
|
|
||||||
@Published var showMiniPlayer = false
|
@Published var showMiniPlayer = false
|
||||||
@Published var showSnackbar = false
|
|
||||||
@Published var snackbarOperation: SnackbarOperation?
|
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
registerFonts()
|
registerFonts()
|
||||||
|
|||||||
@ -12,7 +12,7 @@ struct WebReader: PlatformViewRepresentable {
|
|||||||
let tapHandler: () -> Void
|
let tapHandler: () -> Void
|
||||||
let scrollPercentHandler: (Int) -> Void
|
let scrollPercentHandler: (Int) -> Void
|
||||||
let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void
|
let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void
|
||||||
let navBarVisibilityRatioUpdater: (Double) -> Void
|
let navBarVisibilityUpdater: (Bool) -> Void
|
||||||
|
|
||||||
@Binding var readerSettingsChangedTransactionID: UUID?
|
@Binding var readerSettingsChangedTransactionID: UUID?
|
||||||
@Binding var annotationSaveTransactionID: UUID?
|
@Binding var annotationSaveTransactionID: UUID?
|
||||||
@ -82,7 +82,7 @@ struct WebReader: PlatformViewRepresentable {
|
|||||||
|
|
||||||
context.coordinator.linkHandler = openLinkAction
|
context.coordinator.linkHandler = openLinkAction
|
||||||
context.coordinator.webViewActionHandler = webViewActionHandler
|
context.coordinator.webViewActionHandler = webViewActionHandler
|
||||||
context.coordinator.updateNavBarVisibilityRatio = navBarVisibilityRatioUpdater
|
context.coordinator.updateNavBarVisibility = navBarVisibilityUpdater
|
||||||
context.coordinator.scrollPercentHandler = scrollPercentHandler
|
context.coordinator.scrollPercentHandler = scrollPercentHandler
|
||||||
context.coordinator.updateShowBottomBar = { newValue in
|
context.coordinator.updateShowBottomBar = { newValue in
|
||||||
self.showBottomBar = newValue
|
self.showBottomBar = newValue
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import AVFoundation
|
import AVFoundation
|
||||||
import Models
|
import Models
|
||||||
|
import PopupView
|
||||||
import Services
|
import Services
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Utils
|
import Utils
|
||||||
@ -18,7 +19,7 @@ struct WebReaderContainerView: View {
|
|||||||
@State private var showNotebookView = false
|
@State private var showNotebookView = false
|
||||||
@State private var hasPerformedHighlightMutations = false
|
@State private var hasPerformedHighlightMutations = false
|
||||||
@State var showHighlightAnnotationModal = false
|
@State var showHighlightAnnotationModal = false
|
||||||
@State private var navBarVisibilityRatio = 1.0
|
@State private var navBarVisible = true
|
||||||
@State private var progressViewOpacity = 0.0
|
@State private var progressViewOpacity = 0.0
|
||||||
@State var readerSettingsChangedTransactionID: UUID?
|
@State var readerSettingsChangedTransactionID: UUID?
|
||||||
@State var annotationSaveTransactionID: UUID?
|
@State var annotationSaveTransactionID: UUID?
|
||||||
@ -81,8 +82,8 @@ struct WebReaderContainerView: View {
|
|||||||
|
|
||||||
private func tapHandler() {
|
private func tapHandler() {
|
||||||
withAnimation(.easeIn(duration: 0.08)) {
|
withAnimation(.easeIn(duration: 0.08)) {
|
||||||
navBarVisibilityRatio = navBarVisibilityRatio == 1 ? 0 : 1
|
navBarVisible = !navBarVisible
|
||||||
showBottomBar = navBarVisibilityRatio == 1
|
showBottomBar = navBarVisible
|
||||||
showNavBarActionID = UUID()
|
showNavBarActionID = UUID()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,8 +106,8 @@ struct WebReaderContainerView: View {
|
|||||||
showHighlightLabelsModal = true
|
showHighlightLabelsModal = true
|
||||||
case "pageTapped":
|
case "pageTapped":
|
||||||
withAnimation {
|
withAnimation {
|
||||||
navBarVisibilityRatio = navBarVisibilityRatio == 1 ? 0 : 1
|
navBarVisible = !navBarVisible
|
||||||
showBottomBar = navBarVisibilityRatio == 1
|
showBottomBar = navBarVisible
|
||||||
showNavBarActionID = UUID()
|
showNavBarActionID = UUID()
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -118,8 +119,7 @@ struct WebReaderContainerView: View {
|
|||||||
var audioNavbarItem: some View {
|
var audioNavbarItem: some View {
|
||||||
if audioController.isLoadingItem(itemID: item.unwrappedID) {
|
if audioController.isLoadingItem(itemID: item.unwrappedID) {
|
||||||
return AnyView(ProgressView()
|
return AnyView(ProgressView()
|
||||||
.padding(.horizontal)
|
.padding(.horizontal))
|
||||||
.scaleEffect(navBarVisibilityRatio))
|
|
||||||
} else {
|
} else {
|
||||||
return AnyView(Button(
|
return AnyView(Button(
|
||||||
action: {
|
action: {
|
||||||
@ -143,36 +143,34 @@ struct WebReaderContainerView: View {
|
|||||||
label: {
|
label: {
|
||||||
textToSpeechButtonImage
|
textToSpeechButtonImage
|
||||||
}
|
}
|
||||||
)
|
))
|
||||||
.padding(.horizontal, 5)
|
|
||||||
.scaleEffect(navBarVisibilityRatio))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var textToSpeechButtonImage: some View {
|
var textToSpeechButtonImage: some View {
|
||||||
if audioController.state == .stopped || audioController.itemAudioProperties?.itemID != self.item.id {
|
if audioController.state == .stopped || audioController.itemAudioProperties?.itemID != self.item.id {
|
||||||
return Image(systemName: "headphones").font(.appTitleThree)
|
return AnyView(Image.headphones)
|
||||||
}
|
}
|
||||||
let name = audioController.isPlayingItem(itemID: item.unwrappedID) ? "pause.circle" : "play.circle"
|
let name = audioController.isPlayingItem(itemID: item.unwrappedID) ? "pause.circle" : "play.circle"
|
||||||
return Image(systemName: name).font(.appNavbarIcon)
|
return AnyView(Image(systemName: name).font(.appNavbarIcon))
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var bottomButtons: some View {
|
var bottomButtons: some View {
|
||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
Button(action: archive, label: {
|
Button(action: archive, label: {
|
||||||
Image(systemName: item.isArchived ? "tray.and.arrow.down" : "archivebox")
|
item.isArchived ? Image.unarchive : Image.archive
|
||||||
}).frame(width: 48, height: 48)
|
}).frame(width: 48, height: 48)
|
||||||
.padding(.leading, 8)
|
.padding(.leading, 8)
|
||||||
Divider().opacity(0.8)
|
Divider().opacity(0.8)
|
||||||
|
|
||||||
Button(action: delete, label: {
|
Button(action: delete, label: {
|
||||||
Image(systemName: "trash")
|
Image.remove
|
||||||
}).frame(width: 48, height: 48)
|
}).frame(width: 48, height: 48)
|
||||||
Divider().opacity(0.8)
|
Divider().opacity(0.8)
|
||||||
|
|
||||||
Button(action: editLabels, label: {
|
Button(action: editLabels, label: {
|
||||||
Image(systemName: "tag")
|
Image.label
|
||||||
}).frame(width: 48, height: 48)
|
}).frame(width: 48, height: 48)
|
||||||
Divider().opacity(0.8)
|
Divider().opacity(0.8)
|
||||||
|
|
||||||
@ -261,30 +259,29 @@ struct WebReaderContainerView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let navBarOffset = 100
|
||||||
|
|
||||||
var navBar: some View {
|
var navBar: some View {
|
||||||
HStack(alignment: .center, spacing: 15) {
|
HStack(alignment: .center, spacing: 10) {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
Button(
|
Button(
|
||||||
action: { self.presentationMode.wrappedValue.dismiss() },
|
action: { self.presentationMode.wrappedValue.dismiss() },
|
||||||
label: {
|
label: {
|
||||||
Image(systemName: "chevron.backward")
|
Image.chevronRight
|
||||||
.font(.appNavbarIcon)
|
.padding(.horizontal, 10)
|
||||||
// .foregroundColor(.appGrayTextContrast)
|
.padding(.vertical)
|
||||||
.padding()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.scaleEffect(navBarVisibilityRatio)
|
|
||||||
Spacer()
|
Spacer()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
action: { showNotebookView = true },
|
action: { showNotebookView = true },
|
||||||
label: {
|
label: {
|
||||||
Image("notebook", bundle: Bundle(url: ViewsPackage.bundleURL))
|
Image.notebook
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.padding(.horizontal, 5)
|
.padding(.trailing, 4)
|
||||||
.scaleEffect(navBarVisibilityRatio)
|
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
audioNavbarItem
|
audioNavbarItem
|
||||||
@ -298,12 +295,10 @@ struct WebReaderContainerView: View {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
Image(systemName: "textformat.size")
|
Image.readerSettings
|
||||||
.font(.appNavbarIcon)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.padding(.horizontal, 5)
|
.padding(.horizontal, 5)
|
||||||
.scaleEffect(navBarVisibilityRatio)
|
|
||||||
.popover(isPresented: $showPreferencesPopover) {
|
.popover(isPresented: $showPreferencesPopover) {
|
||||||
webPreferencesPopoverView
|
webPreferencesPopoverView
|
||||||
.frame(maxWidth: 400, maxHeight: 475)
|
.frame(maxWidth: 400, maxHeight: 475)
|
||||||
@ -322,13 +317,8 @@ struct WebReaderContainerView: View {
|
|||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
Image(systemName: "ellipsis")
|
Image.utilityMenu
|
||||||
.resizable(resizingMode: Image.ResizingMode.stretch)
|
|
||||||
.aspectRatio(contentMode: .fit)
|
|
||||||
// .foregroundColor(.appGrayTextContrast)
|
|
||||||
.frame(width: 20, height: 20)
|
|
||||||
.scaleEffect(navBarVisibilityRatio)
|
|
||||||
.padding()
|
|
||||||
#else
|
#else
|
||||||
Text(LocalText.genericOptions)
|
Text(LocalText.genericOptions)
|
||||||
#endif
|
#endif
|
||||||
@ -338,12 +328,12 @@ struct WebReaderContainerView: View {
|
|||||||
.frame(maxWidth: 100)
|
.frame(maxWidth: 100)
|
||||||
.padding(.trailing, 16)
|
.padding(.trailing, 16)
|
||||||
#else
|
#else
|
||||||
.padding(.trailing, 3)
|
.padding(.trailing, 16)
|
||||||
.padding(.bottom, 10)
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
.frame(height: readerViewNavBarHeight * navBarVisibilityRatio)
|
.tint(Color(hex: "#2A2A2A"))
|
||||||
.opacity(navBarVisibilityRatio)
|
.frame(height: readerViewNavBarHeight)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
.foregroundColor(ThemeManager.currentTheme.isDark ? .white : .black)
|
.foregroundColor(ThemeManager.currentTheme.isDark ? .white : .black)
|
||||||
.background(ThemeManager.currentBgColor)
|
.background(ThemeManager.currentBgColor)
|
||||||
.sheet(isPresented: $showLabelsModal) {
|
.sheet(isPresented: $showLabelsModal) {
|
||||||
@ -403,8 +393,10 @@ struct WebReaderContainerView: View {
|
|||||||
tapHandler: tapHandler,
|
tapHandler: tapHandler,
|
||||||
scrollPercentHandler: scrollPercentHandler,
|
scrollPercentHandler: scrollPercentHandler,
|
||||||
webViewActionHandler: webViewActionHandler,
|
webViewActionHandler: webViewActionHandler,
|
||||||
navBarVisibilityRatioUpdater: {
|
navBarVisibilityUpdater: { visible in
|
||||||
navBarVisibilityRatio = $0
|
withAnimation {
|
||||||
|
navBarVisible = visible
|
||||||
|
}
|
||||||
},
|
},
|
||||||
readerSettingsChangedTransactionID: $readerSettingsChangedTransactionID,
|
readerSettingsChangedTransactionID: $readerSettingsChangedTransactionID,
|
||||||
annotationSaveTransactionID: $annotationSaveTransactionID,
|
annotationSaveTransactionID: $annotationSaveTransactionID,
|
||||||
@ -505,7 +497,7 @@ struct WebReaderContainerView: View {
|
|||||||
self.isRecovering = true
|
self.isRecovering = true
|
||||||
Task {
|
Task {
|
||||||
if !(await dataService.recoverItem(itemID: item.unwrappedID)) {
|
if !(await dataService.recoverItem(itemID: item.unwrappedID)) {
|
||||||
Snackbar.show(message: "Error recovering item")
|
viewModel.snackbar(message: "Error recovering item")
|
||||||
} else {
|
} else {
|
||||||
await viewModel.loadContent(
|
await viewModel.loadContent(
|
||||||
dataService: dataService,
|
dataService: dataService,
|
||||||
@ -554,6 +546,8 @@ struct WebReaderContainerView: View {
|
|||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
navBar
|
navBar
|
||||||
|
.offset(y: navBarVisible ? 0 : -150)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
if showBottomBar {
|
if showBottomBar {
|
||||||
bottomButtons
|
bottomButtons
|
||||||
@ -585,6 +579,20 @@ struct WebReaderContainerView: View {
|
|||||||
// WebViewManager.shared().loadHTMLString("<html></html>", baseURL: nil)
|
// WebViewManager.shared().loadHTMLString("<html></html>", baseURL: nil)
|
||||||
WebViewManager.shared().loadHTMLString(WebReaderContent.emptyContent(isDark: Color.isDarkMode), baseURL: nil)
|
WebViewManager.shared().loadHTMLString(WebReaderContent.emptyContent(isDark: Color.isDarkMode), baseURL: nil)
|
||||||
}
|
}
|
||||||
|
.popup(isPresented: $viewModel.showSnackbar) {
|
||||||
|
if let operation = viewModel.snackbarOperation {
|
||||||
|
Snackbar(isShowing: $viewModel.showSnackbar, operation: operation)
|
||||||
|
} else {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
} customize: {
|
||||||
|
$0
|
||||||
|
.type(.toast)
|
||||||
|
.autohideIn(2)
|
||||||
|
.position(.bottom)
|
||||||
|
.animation(.spring())
|
||||||
|
.closeOnTapOutside(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func archive() {
|
func archive() {
|
||||||
@ -592,7 +600,7 @@ struct WebReaderContainerView: View {
|
|||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
presentationMode.wrappedValue.dismiss()
|
presentationMode.wrappedValue.dismiss()
|
||||||
#endif
|
#endif
|
||||||
Snackbar.show(message: !item.isArchived ? "Link archived" : "Link moved to Inbox")
|
viewModel.snackbar(message: !item.isArchived ? "Link archived" : "Link moved to Inbox")
|
||||||
}
|
}
|
||||||
|
|
||||||
func recommend() {
|
func recommend() {
|
||||||
|
|||||||
@ -19,7 +19,7 @@ final class WebReaderCoordinator: NSObject {
|
|||||||
var previousReaderSettingsChangedUUID: UUID?
|
var previousReaderSettingsChangedUUID: UUID?
|
||||||
var previousShowNavBarActionID: UUID?
|
var previousShowNavBarActionID: UUID?
|
||||||
var previousShareActionID: UUID?
|
var previousShareActionID: UUID?
|
||||||
var updateNavBarVisibilityRatio: (Double) -> Void = { _ in }
|
var updateNavBarVisibility: (Bool) -> Void = { _ in }
|
||||||
var updateShowBottomBar: (Bool) -> Void = { _ in }
|
var updateShowBottomBar: (Bool) -> Void = { _ in }
|
||||||
var articleContentID = UUID()
|
var articleContentID = UUID()
|
||||||
private var yOffsetAtStartOfDrag: Double?
|
private var yOffsetAtStartOfDrag: Double?
|
||||||
@ -31,10 +31,10 @@ final class WebReaderCoordinator: NSObject {
|
|||||||
super.init()
|
super.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
var navBarVisibilityRatio: Double = 1.0 {
|
var navBarVisible: Bool = true {
|
||||||
didSet {
|
didSet {
|
||||||
isNavBarHidden = navBarVisibilityRatio == 0
|
isNavBarHidden = !navBarVisible
|
||||||
updateNavBarVisibilityRatio(navBarVisibilityRatio)
|
updateNavBarVisibility(navBarVisible)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,30 +98,28 @@ extension WebReaderCoordinator: WKNavigationDelegate {
|
|||||||
|
|
||||||
if yOffset == 0 {
|
if yOffset == 0 {
|
||||||
scrollView.contentInset.top = readerViewNavBarHeight
|
scrollView.contentInset.top = readerViewNavBarHeight
|
||||||
navBarVisibilityRatio = 1
|
navBarVisible = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if yOffset < 0 {
|
if yOffset < 0 {
|
||||||
navBarVisibilityRatio = 1
|
navBarVisible = true
|
||||||
scrollView.contentInset.top = readerViewNavBarHeight
|
scrollView.contentInset.top = readerViewNavBarHeight
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if yOffset < readerViewNavBarHeight {
|
if yOffset < readerViewNavBarHeight {
|
||||||
let isScrollingUp = yOffsetAtStartOfDrag ?? 0 > yOffset
|
let isScrollingUp = yOffsetAtStartOfDrag ?? 0 > yOffset
|
||||||
navBarVisibilityRatio = isScrollingUp || yOffset < 0 ? 1 : min(1, 1 - (yOffset / readerViewNavBarHeight))
|
navBarVisible = isScrollingUp || yOffset < 0
|
||||||
scrollView.contentInset.top = navBarVisibilityRatio * readerViewNavBarHeight
|
scrollView.contentInset.top = navBarVisible ? readerViewNavBarHeight : 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let yOffsetAtStartOfDrag = yOffsetAtStartOfDrag else { return }
|
guard let yOffsetAtStartOfDrag = yOffsetAtStartOfDrag else { return }
|
||||||
|
|
||||||
if yOffset > yOffsetAtStartOfDrag, !isNavBarHidden {
|
if yOffset > yOffsetAtStartOfDrag, !isNavBarHidden {
|
||||||
let translation = yOffset - yOffsetAtStartOfDrag
|
navBarVisible = false
|
||||||
let ratio = translation < readerViewNavBarHeight ? 1 - (translation / readerViewNavBarHeight) : 0
|
scrollView.contentInset.top = navBarVisible ? readerViewNavBarHeight : 0
|
||||||
navBarVisibilityRatio = min(ratio, 1)
|
|
||||||
scrollView.contentInset.top = navBarVisibilityRatio * readerViewNavBarHeight
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if yOffset + scrollView.visibleSize.height > scrollView.contentSize.height - 140 {
|
if yOffset + scrollView.visibleSize.height > scrollView.contentSize.height - 140 {
|
||||||
@ -137,15 +135,15 @@ extension WebReaderCoordinator: WKNavigationDelegate {
|
|||||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||||
if decelerate, scrollView.contentOffset.y + scrollView.contentInset.top < (yOffsetAtStartOfDrag ?? 0) {
|
if decelerate, scrollView.contentOffset.y + scrollView.contentInset.top < (yOffsetAtStartOfDrag ?? 0) {
|
||||||
scrollView.contentInset.top = readerViewNavBarHeight
|
scrollView.contentInset.top = readerViewNavBarHeight
|
||||||
navBarVisibilityRatio = 1
|
navBarVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
|
func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
|
||||||
scrollView.contentInset.top = readerViewNavBarHeight
|
scrollView.contentInset.top = readerViewNavBarHeight
|
||||||
let navBarVisible = navBarVisibilityRatio == 1
|
let isVisible = navBarVisible
|
||||||
navBarVisibilityRatio = 1
|
navBarVisible = true
|
||||||
return navBarVisible
|
return isVisible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -16,6 +16,14 @@ struct SafariWebLink: Identifiable {
|
|||||||
@Published var isDownloadingAudio: Bool = false
|
@Published var isDownloadingAudio: Bool = false
|
||||||
@Published var audioDownloadTask: Task<Void, Error>?
|
@Published var audioDownloadTask: Task<Void, Error>?
|
||||||
|
|
||||||
|
@Published var showSnackbar: Bool = false
|
||||||
|
var snackbarOperation: SnackbarOperation?
|
||||||
|
|
||||||
|
func snackbar(message: String) {
|
||||||
|
snackbarOperation = SnackbarOperation(message: message, undoAction: nil)
|
||||||
|
showSnackbar = true
|
||||||
|
}
|
||||||
|
|
||||||
func hasOriginalUrl(_ item: LinkedItem) -> Bool {
|
func hasOriginalUrl(_ item: LinkedItem) -> Bool {
|
||||||
if let pageURLString = item.pageURLString, let host = URL(string: pageURLString)?.host {
|
if let pageURLString = item.pageURLString, let host = URL(string: pageURLString)?.host {
|
||||||
if host == "omnivore.app" {
|
if host == "omnivore.app" {
|
||||||
@ -27,7 +35,7 @@ struct SafariWebLink: Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func downloadAudio(audioController: AudioController, item: LinkedItem) {
|
func downloadAudio(audioController: AudioController, item: LinkedItem) {
|
||||||
Snackbar.show(message: "Downloading Offline Audio")
|
snackbar(message: "Downloading Offline Audio")
|
||||||
isDownloadingAudio = true
|
isDownloadingAudio = true
|
||||||
|
|
||||||
if let audioDownloadTask = audioDownloadTask {
|
if let audioDownloadTask = audioDownloadTask {
|
||||||
@ -41,7 +49,7 @@ struct SafariWebLink: Identifiable {
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.isDownloadingAudio = false
|
self.isDownloadingAudio = false
|
||||||
if !canceled {
|
if !canceled {
|
||||||
Snackbar.show(message: downloaded ? "Audio file downloaded" : "Error downloading audio")
|
self.snackbar(message: downloaded ? "Audio file downloaded" : "Error downloading audio")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,12 +210,12 @@ struct SafariWebLink: Identifiable {
|
|||||||
func saveLink(dataService: DataService, url: URL) {
|
func saveLink(dataService: DataService, url: URL) {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
Snackbar.show(message: "Saving link")
|
snackbar(message: "Saving link")
|
||||||
print("SAVING: ", url.absoluteString)
|
print("SAVING: ", url.absoluteString)
|
||||||
_ = try await dataService.createPageFromUrl(id: UUID().uuidString, url: url.absoluteString)
|
_ = try await dataService.createPageFromUrl(id: UUID().uuidString, url: url.absoluteString)
|
||||||
Snackbar.show(message: "Link saved")
|
snackbar(message: "Link saved")
|
||||||
} catch {
|
} catch {
|
||||||
Snackbar.show(message: "Error saving link")
|
snackbar(message: "Error saving link")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,14 +223,14 @@ struct SafariWebLink: Identifiable {
|
|||||||
func saveLinkAndFetch(dataService: DataService, username: String, url: URL) {
|
func saveLinkAndFetch(dataService: DataService, username: String, url: URL) {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
Snackbar.show(message: "Saving link")
|
snackbar(message: "Saving link")
|
||||||
let requestId = UUID().uuidString
|
let requestId = UUID().uuidString
|
||||||
_ = try await dataService.createPageFromUrl(id: requestId, url: url.absoluteString)
|
_ = try await dataService.createPageFromUrl(id: requestId, url: url.absoluteString)
|
||||||
Snackbar.show(message: "Link saved")
|
snackbar(message: "Link saved")
|
||||||
|
|
||||||
await loadContent(dataService: dataService, username: username, itemID: requestId, retryCount: 0)
|
await loadContent(dataService: dataService, username: username, itemID: requestId, retryCount: 0)
|
||||||
} catch {
|
} catch {
|
||||||
Snackbar.show(message: "Error saving link")
|
snackbar(message: "Error saving link")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,4 +17,15 @@ public extension Image {
|
|||||||
static var tabHighlights: Image { Image("_tab_highlights", bundle: .module).renderingMode(.template) }
|
static var tabHighlights: Image { Image("_tab_highlights", bundle: .module).renderingMode(.template) }
|
||||||
|
|
||||||
static var pinRotated: Image { Image("pin-rotated", bundle: .module) }
|
static var pinRotated: Image { Image("pin-rotated", bundle: .module) }
|
||||||
|
|
||||||
|
static var chevronRight: Image { Image("chevron-right", bundle: .module) }
|
||||||
|
static var notebook: Image { Image("notebook", bundle: .module) }
|
||||||
|
static var headphones: Image { Image("headphones", bundle: .module) }
|
||||||
|
static var readerSettings: Image { Image("reader-settings", bundle: .module) }
|
||||||
|
static var utilityMenu: Image { Image("utility-menu", 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) }
|
||||||
|
static var label: Image { Image("label", bundle: .module) }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "dots-three.svg",
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
},
|
|
||||||
"properties" : {
|
|
||||||
"template-rendering-intent" : "template"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="26" height="26" viewBox="0 0 26 26" version="1.1">
|
|
||||||
<g id="surface1">
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 14.21875 13 C 14.21875 13.671875 13.671875 14.21875 13 14.21875 C 12.328125 14.21875 11.78125 13.671875 11.78125 13 C 11.78125 12.328125 12.328125 11.78125 13 11.78125 C 13.671875 11.78125 14.21875 12.328125 14.21875 13 Z M 14.21875 13 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 20.71875 13 C 20.71875 13.671875 20.171875 14.21875 19.5 14.21875 C 18.828125 14.21875 18.28125 13.671875 18.28125 13 C 18.28125 12.328125 18.828125 11.78125 19.5 11.78125 C 20.171875 11.78125 20.71875 12.328125 20.71875 13 Z M 20.71875 13 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7.71875 13 C 7.71875 13.671875 7.171875 14.21875 6.5 14.21875 C 5.828125 14.21875 5.28125 13.671875 5.28125 13 C 5.28125 12.328125 5.828125 11.78125 6.5 11.78125 C 7.171875 11.78125 7.71875 12.328125 7.71875 13 Z M 7.71875 13 "/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -17,5 +17,8 @@
|
|||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"author" : "xcode",
|
||||||
"version" : 1
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,5 +17,8 @@
|
|||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"author" : "xcode",
|
||||||
"version" : 1
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,5 +17,8 @@
|
|||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"author" : "xcode",
|
||||||
"version" : 1
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,5 +17,8 @@
|
|||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"author" : "xcode",
|
||||||
"version" : 1
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/archive.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "archive.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "archive@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "archive@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/archive.imageset/archive.png
vendored
Normal file
|
After Width: | Height: | Size: 456 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/archive.imageset/archive@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 699 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/archive.imageset/archive@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1013 B |
26
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/chevron-right.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "chevron-left.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "chevron-left@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "chevron-left@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/chevron-right.imageset/chevron-left.png
vendored
Normal file
|
After Width: | Height: | Size: 307 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/chevron-right.imageset/chevron-left@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 439 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/chevron-right.imageset/chevron-left@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 572 B |
26
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/headphones.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "headphones.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "headphones@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "headphones@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/headphones.imageset/headphones.png
vendored
Normal file
|
After Width: | Height: | Size: 622 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/headphones.imageset/headphones@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/headphones.imageset/headphones@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
26
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/label.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "label.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "label@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "label@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/label.imageset/label.png
vendored
Normal file
|
After Width: | Height: | Size: 499 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/label.imageset/label@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 815 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/label.imageset/label@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@ -1,15 +1,17 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "notebook.svg",
|
"filename" : "notebooks.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "notebooks@2x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"filename" : "notebooks@3x.png",
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="#000000" viewBox="0 0 256 256"><path d="M184,112a8,8,0,0,1-8,8H112a8,8,0,0,1,0-16h64A8,8,0,0,1,184,112Zm-8,24H112a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16Zm48-88V208a16,16,0,0,1-16,16H48a16,16,0,0,1-16-16V48A16,16,0,0,1,48,32H208A16,16,0,0,1,224,48ZM48,208H72V48H48Zm160,0V48H88V208H208Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 363 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/notebook.imageset/notebooks.png
vendored
Normal file
|
After Width: | Height: | Size: 437 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/notebook.imageset/notebooks@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 660 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/notebook.imageset/notebooks@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 861 B |
@ -19,5 +19,8 @@
|
|||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"author" : "xcode",
|
||||||
"version" : 1
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/reader-settings.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "reader-settings.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "reader-settings@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "reader-settings@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/reader-settings.imageset/reader-settings.png
vendored
Normal file
|
After Width: | Height: | Size: 440 B |
|
After Width: | Height: | Size: 628 B |
|
After Width: | Height: | Size: 771 B |
26
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/remove.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "remove.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "remove@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "remove@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/remove.imageset/remove.png
vendored
Normal file
|
After Width: | Height: | Size: 545 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/remove.imageset/remove@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 875 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/remove.imageset/remove@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
26
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/unarchive.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "unarchive.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "unarchive@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "unarchive@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/unarchive.imageset/unarchive.png
vendored
Normal file
|
After Width: | Height: | Size: 549 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/unarchive.imageset/unarchive@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 960 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/unarchive.imageset/unarchive@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
26
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/utility-menu.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "utility-menu.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "utility-menu@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "utility-menu@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
},
|
||||||
|
"properties" : {
|
||||||
|
"template-rendering-intent" : "template"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/utility-menu.imageset/utility-menu.png
vendored
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/utility-menu.imageset/utility-menu@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/utility-menu.imageset/utility-menu@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
@ -14,59 +14,51 @@ public struct SnackbarOperation {
|
|||||||
|
|
||||||
public struct Snackbar: View {
|
public struct Snackbar: View {
|
||||||
@Binding var isShowing: Bool
|
@Binding var isShowing: Bool
|
||||||
private let presentingView: AnyView
|
|
||||||
private let operation: SnackbarOperation
|
private let operation: SnackbarOperation
|
||||||
|
|
||||||
@Environment(\.colorScheme) private var colorScheme: ColorScheme
|
@Environment(\.colorScheme) private var colorScheme: ColorScheme
|
||||||
|
|
||||||
init<PresentingView>(
|
public init(
|
||||||
isShowing: Binding<Bool>,
|
isShowing: Binding<Bool>,
|
||||||
presentingView: PresentingView,
|
|
||||||
operation: SnackbarOperation
|
operation: SnackbarOperation
|
||||||
) where PresentingView: View {
|
) {
|
||||||
self._isShowing = isShowing
|
self._isShowing = isShowing
|
||||||
self.presentingView = AnyView(presentingView)
|
|
||||||
self.operation = operation
|
self.operation = operation
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
GeometryReader { geometry in
|
VStack(alignment: .center) {
|
||||||
ZStack(alignment: .center) {
|
HStack {
|
||||||
presentingView
|
Text(operation.message)
|
||||||
VStack {
|
.font(.appCallout)
|
||||||
Spacer()
|
.foregroundColor(self.colorScheme == .light ? .white : .appTextDefault)
|
||||||
if isShowing {
|
Spacer()
|
||||||
HStack {
|
if let undoAction = operation.undoAction {
|
||||||
Text(operation.message)
|
Button("Undo", action: {
|
||||||
.font(.appCallout)
|
isShowing = false
|
||||||
.foregroundColor(self.colorScheme == .light ? .white : .appTextDefault)
|
undoAction()
|
||||||
Spacer()
|
})
|
||||||
if let undoAction = operation.undoAction {
|
.font(.system(size: 16, weight: .bold))
|
||||||
Button("Undo", action: {
|
|
||||||
isShowing = false
|
|
||||||
undoAction()
|
|
||||||
})
|
|
||||||
.font(.system(size: 16, weight: .bold))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.frame(width: min(380, geometry.size.width * 0.96), height: 44)
|
|
||||||
.background(self.colorScheme == .light ? Color.black : Color.white)
|
|
||||||
.cornerRadius(5)
|
|
||||||
.offset(x: 0, y: -8)
|
|
||||||
.shadow(color: .gray, radius: 2)
|
|
||||||
.animation(.spring(), value: true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: 380)
|
||||||
|
.frame(height: 44)
|
||||||
|
.padding(.horizontal, 15)
|
||||||
|
.background(self.colorScheme == .light ? Color.black : Color.white)
|
||||||
|
.cornerRadius(5)
|
||||||
|
.clipped()
|
||||||
|
|
||||||
|
Spacer(minLength: 20)
|
||||||
}
|
}
|
||||||
|
.background(Color.clear)
|
||||||
|
.frame(height: 44 + 22)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension View {
|
public extension View {
|
||||||
func snackBar(isShowing: Binding<Bool>, operation: SnackbarOperation?) -> some View {
|
func snackBar(isShowing: Binding<Bool>, operation: SnackbarOperation?) -> some View {
|
||||||
if let operation = operation {
|
if let operation = operation {
|
||||||
return AnyView(Snackbar(isShowing: isShowing, presentingView: self, operation: operation))
|
return AnyView(Snackbar(isShowing: isShowing, operation: operation))
|
||||||
} else {
|
} else {
|
||||||
return AnyView(self)
|
return AnyView(self)
|
||||||
}
|
}
|
||||||
|
|||||||