Compare commits
5 Commits
f40c5597f3
...
534ceddad4
| Author | SHA1 | Date | |
|---|---|---|---|
| 534ceddad4 | |||
| dcbe0515fc | |||
| ba74430d10 | |||
| fbf840888a | |||
| c13fc107b1 |
@ -8,6 +8,8 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionActivationRule</key>
|
<key>NSExtensionActivationRule</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>NSExtensionActivationSupportsText</key>
|
||||||
|
<true/>
|
||||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@ -67,8 +67,22 @@ class ShareBookmarkViewModel: ObservableObject {
|
|||||||
logger.warning("No extension context available for content extraction")
|
logger.warning("No extension context available for content extraction")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var extractedUrl: String?
|
||||||
|
var extractedTitle: String?
|
||||||
|
|
||||||
for item in extensionContext.inputItems {
|
for item in extensionContext.inputItems {
|
||||||
guard let inputItem = item as? NSExtensionItem else { continue }
|
guard let inputItem = item as? NSExtensionItem else { continue }
|
||||||
|
|
||||||
|
// Use the inputItem's attributedTitle or attributedContentText as potential title
|
||||||
|
if let attributedTitle = inputItem.attributedTitle?.string, !attributedTitle.isEmpty {
|
||||||
|
extractedTitle = attributedTitle
|
||||||
|
logger.info("Extracted title from input item: \(attributedTitle)")
|
||||||
|
} else if let attributedContent = inputItem.attributedContentText?.string, !attributedContent.isEmpty {
|
||||||
|
extractedTitle = attributedContent
|
||||||
|
logger.info("Extracted title from content text: \(attributedContent)")
|
||||||
|
}
|
||||||
|
|
||||||
for attachment in inputItem.attachments ?? [] {
|
for attachment in inputItem.attachments ?? [] {
|
||||||
if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
|
if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
|
||||||
attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] (url, error) in
|
attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] (url, error) in
|
||||||
@ -76,6 +90,12 @@ class ShareBookmarkViewModel: ObservableObject {
|
|||||||
if let url = url as? URL {
|
if let url = url as? URL {
|
||||||
self?.url = url.absoluteString
|
self?.url = url.absoluteString
|
||||||
self?.logger.info("Extracted URL from shared content: \(url.absoluteString)")
|
self?.logger.info("Extracted URL from shared content: \(url.absoluteString)")
|
||||||
|
|
||||||
|
// Set title if we extracted one and current title is empty
|
||||||
|
if let title = extractedTitle, self?.title.isEmpty == true {
|
||||||
|
self?.title = title
|
||||||
|
self?.logger.info("Set title from shared content: \(title)")
|
||||||
|
}
|
||||||
} else if let error = error {
|
} else if let error = error {
|
||||||
self?.logger.error("Failed to extract URL: \(error.localizedDescription)")
|
self?.logger.error("Failed to extract URL: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
@ -85,9 +105,18 @@ class ShareBookmarkViewModel: ObservableObject {
|
|||||||
if attachment.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) {
|
if attachment.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) {
|
||||||
attachment.loadItem(forTypeIdentifier: UTType.plainText.identifier, options: nil) { [weak self] (text, error) in
|
attachment.loadItem(forTypeIdentifier: UTType.plainText.identifier, options: nil) { [weak self] (text, error) in
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
if let text = text as? String, let url = URL(string: text) {
|
if let text = text as? String {
|
||||||
self?.url = url.absoluteString
|
// Only treat as URL if it's a valid URL and we don't have one yet
|
||||||
self?.logger.info("Extracted URL from shared text: \(url.absoluteString)")
|
if self?.url == nil, let url = URL(string: text), url.scheme != nil {
|
||||||
|
self?.url = url.absoluteString
|
||||||
|
self?.logger.info("Extracted URL from shared text: \(url.absoluteString)")
|
||||||
|
} else {
|
||||||
|
// If not a valid URL or we already have a URL, treat as potential title
|
||||||
|
if self?.title.isEmpty == true {
|
||||||
|
self?.title = text
|
||||||
|
self?.logger.info("Set title from shared text: \(text)")
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if let error = error {
|
} else if let error = error {
|
||||||
self?.logger.error("Failed to extract text: \(error.localizedDescription)")
|
self?.logger.error("Failed to extract text: \(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,9 +15,9 @@
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
UI["UI Layer\n(View, ViewModel)"]
|
UI["UI Layer (View, ViewModel)"]
|
||||||
Domain["Domain Layer\n(Use Cases, Models, Repository Protocols)"]
|
Domain["Domain Layer (Use Cases, Models, Repository Protocols)"]
|
||||||
Data["Data Layer\n(Repository implementations, Database, Entities, API)"]
|
Data["Data Layer (Repository implementations, Database, Entities, API)"]
|
||||||
UI --> Domain
|
UI --> Domain
|
||||||
Domain --> Data
|
Domain --> Data
|
||||||
```
|
```
|
||||||
|
|||||||
@ -436,7 +436,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = URLShare/URLShare.entitlements;
|
CODE_SIGN_ENTITLEMENTS = URLShare/URLShare.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 22;
|
CURRENT_PROJECT_VERSION = 24;
|
||||||
DEVELOPMENT_TEAM = 8J69P655GN;
|
DEVELOPMENT_TEAM = 8J69P655GN;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = URLShare/Info.plist;
|
INFOPLIST_FILE = URLShare/Info.plist;
|
||||||
@ -469,7 +469,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_ENTITLEMENTS = URLShare/URLShare.entitlements;
|
CODE_SIGN_ENTITLEMENTS = URLShare/URLShare.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 22;
|
CURRENT_PROJECT_VERSION = 24;
|
||||||
DEVELOPMENT_TEAM = 8J69P655GN;
|
DEVELOPMENT_TEAM = 8J69P655GN;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = URLShare/Info.plist;
|
INFOPLIST_FILE = URLShare/Info.plist;
|
||||||
@ -624,7 +624,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
|
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 22;
|
CURRENT_PROJECT_VERSION = 24;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 8J69P655GN;
|
DEVELOPMENT_TEAM = 8J69P655GN;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@ -668,7 +668,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
|
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 22;
|
CURRENT_PROJECT_VERSION = 24;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 8J69P655GN;
|
DEVELOPMENT_TEAM = 8J69P655GN;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
|||||||
@ -73,10 +73,15 @@ class BookmarkLabelsViewModel {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func addLabel(to bookmarkId: String, label: String) async {
|
func addLabel(to bookmarkId: String, label: String) async {
|
||||||
let trimmedLabel = label.trimmingCharacters(in: .whitespacesAndNewlines)
|
let individualLabels = label
|
||||||
guard !trimmedLabel.isEmpty else { return }
|
.components(separatedBy: " ")
|
||||||
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||||
|
.filter { !$0.isEmpty }
|
||||||
|
.filter { !currentLabels.contains($0) }
|
||||||
|
|
||||||
await addLabels(to: bookmarkId, labels: [trimmedLabel])
|
guard !individualLabels.isEmpty else { return }
|
||||||
|
|
||||||
|
await addLabels(to: bookmarkId, labels: individualLabels)
|
||||||
newLabelText = ""
|
newLabelText = ""
|
||||||
searchText = ""
|
searchText = ""
|
||||||
}
|
}
|
||||||
|
|||||||
@ -275,8 +275,9 @@ struct BookmarkCardView: View {
|
|||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
CachedAsyncImage(url: imageURL)
|
CachedAsyncImage(url: imageURL)
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(minHeight: 180)
|
.frame(width: UIScreen.main.bounds.width - 32)
|
||||||
|
.clipped()
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||||
|
|
||||||
if bookmark.readProgress > 0 && bookmark.isArchived == false && bookmark.isMarked == false {
|
if bookmark.readProgress > 0 && bookmark.isArchived == false && bookmark.isMarked == false {
|
||||||
|
|||||||
@ -133,6 +133,7 @@ struct TagManagementView: View {
|
|||||||
.textFieldStyle(CustomTextFieldStyle())
|
.textFieldStyle(CustomTextFieldStyle())
|
||||||
.keyboardType(.default)
|
.keyboardType(.default)
|
||||||
.autocorrectionDisabled(true)
|
.autocorrectionDisabled(true)
|
||||||
|
.autocapitalization(.none)
|
||||||
.onSubmit {
|
.onSubmit {
|
||||||
onAddCustomTag()
|
onAddCustomTag()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ struct SearchBookmarksView: View {
|
|||||||
@Binding var selectedBookmark: Bookmark?
|
@Binding var selectedBookmark: Bookmark?
|
||||||
@Namespace private var namespace
|
@Namespace private var namespace
|
||||||
@State private var isFirstAppearance = true
|
@State private var isFirstAppearance = true
|
||||||
|
@State private var cardLayoutStyle: CardLayoutStyle = .magazine
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
@ -61,10 +62,22 @@ struct SearchBookmarksView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
BookmarkCardView(bookmark: bookmark, currentState: .all, onArchive: {_ in }, onDelete: {_ in }, onToggleFavorite: {_ in })
|
BookmarkCardView(
|
||||||
|
bookmark: bookmark,
|
||||||
|
currentState: .all,
|
||||||
|
layout: cardLayoutStyle,
|
||||||
|
onArchive: {_ in },
|
||||||
|
onDelete: {_ in },
|
||||||
|
onToggleFavorite: {_ in }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
.listRowInsets(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
|
.listRowInsets(EdgeInsets(
|
||||||
|
top: cardLayoutStyle == .compact ? 8 : 12,
|
||||||
|
leading: 16,
|
||||||
|
bottom: cardLayoutStyle == .compact ? 8 : 12,
|
||||||
|
trailing: 16
|
||||||
|
))
|
||||||
.listRowSeparator(.hidden)
|
.listRowSeparator(.hidden)
|
||||||
.listRowBackground(Color(R.color.bookmark_list_bg))
|
.listRowBackground(Color(R.color.bookmark_list_bg))
|
||||||
}
|
}
|
||||||
@ -98,6 +111,22 @@ struct SearchBookmarksView: View {
|
|||||||
searchFieldIsFocused = true
|
searchFieldIsFocused = true
|
||||||
isFirstAppearance = false
|
isFirstAppearance = false
|
||||||
}
|
}
|
||||||
|
loadCardLayoutStyle()
|
||||||
|
}
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: .cardLayoutChanged)) { notification in
|
||||||
|
if let layout = notification.object as? CardLayoutStyle {
|
||||||
|
cardLayoutStyle = layout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadCardLayoutStyle() {
|
||||||
|
Task {
|
||||||
|
let loadCardLayoutUseCase = DefaultUseCaseFactory.shared.makeLoadCardLayoutUseCase()
|
||||||
|
let layout = await loadCardLayoutUseCase.execute()
|
||||||
|
await MainActor.run {
|
||||||
|
cardLayoutStyle = layout
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,10 +6,12 @@ struct AppearanceSettingsView: View {
|
|||||||
|
|
||||||
private let loadCardLayoutUseCase: PLoadCardLayoutUseCase
|
private let loadCardLayoutUseCase: PLoadCardLayoutUseCase
|
||||||
private let saveCardLayoutUseCase: PSaveCardLayoutUseCase
|
private let saveCardLayoutUseCase: PSaveCardLayoutUseCase
|
||||||
|
private let settingsRepository: PSettingsRepository
|
||||||
|
|
||||||
init(factory: UseCaseFactory = DefaultUseCaseFactory.shared) {
|
init(factory: UseCaseFactory = DefaultUseCaseFactory.shared) {
|
||||||
self.loadCardLayoutUseCase = factory.makeLoadCardLayoutUseCase()
|
self.loadCardLayoutUseCase = factory.makeLoadCardLayoutUseCase()
|
||||||
self.saveCardLayoutUseCase = factory.makeSaveCardLayoutUseCase()
|
self.saveCardLayoutUseCase = factory.makeSaveCardLayoutUseCase()
|
||||||
|
self.settingsRepository = SettingsRepository()
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -58,18 +60,29 @@ struct AppearanceSettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func loadSettings() {
|
private func loadSettings() {
|
||||||
// Load theme setting
|
|
||||||
let themeString = UserDefaults.standard.string(forKey: "selectedTheme") ?? "system"
|
|
||||||
selectedTheme = Theme(rawValue: themeString) ?? .system
|
|
||||||
|
|
||||||
// Load card layout setting
|
|
||||||
Task {
|
Task {
|
||||||
|
// Load both theme and card layout from repository
|
||||||
|
if let settings = try? await settingsRepository.loadSettings() {
|
||||||
|
await MainActor.run {
|
||||||
|
selectedTheme = settings.theme ?? .system
|
||||||
|
}
|
||||||
|
}
|
||||||
selectedCardLayout = await loadCardLayoutUseCase.execute()
|
selectedCardLayout = await loadCardLayoutUseCase.execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveThemeSettings() {
|
private func saveThemeSettings() {
|
||||||
UserDefaults.standard.set(selectedTheme.rawValue, forKey: "selectedTheme")
|
Task {
|
||||||
|
// Load current settings, update theme, and save back
|
||||||
|
var settings = (try? await settingsRepository.loadSettings()) ?? Settings()
|
||||||
|
settings.theme = selectedTheme
|
||||||
|
try? await settingsRepository.saveSettings(settings)
|
||||||
|
|
||||||
|
// Notify app about theme change
|
||||||
|
await MainActor.run {
|
||||||
|
NotificationCenter.default.post(name: .settingsChanged, object: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveCardLayoutSettings() {
|
private func saveCardLayoutSettings() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user