Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 6.0
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import Foundation
Expand All @@ -7,15 +7,13 @@ import PackageDescription
let remoteDependencies: [PackageDescription.Package.Dependency] = [
.package(url: "https://github.com/oversizedev/OversizeCore.git", .upToNextMajor(from: "1.3.0")),
.package(url: "https://github.com/oversizedev/OversizeLocalizable.git", .upToNextMajor(from: "1.5.0")),
.package(url: "https://github.com/hmlongco/Factory.git", .upToNextMajor(from: "2.1.3")),
.package(url: "https://github.com/oversizedev/OversizeModels.git", .upToNextMajor(from: "0.1.0")),
.package(url: "https://github.com/hmlongco/Factory.git", .upToNextMajor(from: "2.5.0")),
]

let localDependencies: [PackageDescription.Package.Dependency] = [
.package(name: "OversizeCore", path: "../OversizeCore"),
.package(name: "OversizeLocalizable", path: "../OversizeLocalizable"),
.package(name: "OversizeModels", path: "../OversizeModels"),
.package(url: "https://github.com/hmlongco/Factory.git", .upToNextMajor(from: "2.1.3")),
.package(url: "https://github.com/hmlongco/Factory.git", .upToNextMajor(from: "2.5.0")),
]

let dependencies: [PackageDescription.Package.Dependency] = remoteDependencies
Expand Down Expand Up @@ -46,46 +44,41 @@ let package = Package(
dependencies: [
.product(name: "OversizeCore", package: "OversizeCore"),
.product(name: "OversizeLocalizable", package: "OversizeLocalizable"),
.product(name: "OversizeModels", package: "OversizeModels"),
.product(name: "FactoryKit", package: "Factory"),
],
),
.target(
name: "OversizeFileManagerService",
dependencies: [
.product(name: "OversizeCore", package: "OversizeCore"),
.product(name: "OversizeModels", package: "OversizeModels"),
.product(name: "FactoryKit", package: "Factory"),
],
),
.target(
name: "OversizeContactsService",
dependencies: [
.product(name: "OversizeModels", package: "OversizeModels"),
.product(name: "OversizeCore", package: "OversizeCore"),
.product(name: "FactoryKit", package: "Factory"),
],
),
.target(
name: "OversizeCalendarService",
dependencies: [
.product(name: "OversizeCore", package: "OversizeCore"),
.product(name: "OversizeModels", package: "OversizeModels"),
.product(name: "FactoryKit", package: "Factory"),
],
),
.target(
name: "OversizeHealthService",
dependencies: [
.product(name: "OversizeCore", package: "OversizeCore"),
.product(name: "OversizeModels", package: "OversizeModels"),
.product(name: "FactoryKit", package: "Factory"),
],
),
.target(
name: "OversizeLocationService",
dependencies: [
.product(name: "OversizeCore", package: "OversizeCore"),
.product(name: "OversizeModels", package: "OversizeModels"),
.product(name: "FactoryKit", package: "Factory"),
],
),
Expand All @@ -94,15 +87,13 @@ let package = Package(
dependencies: [
"OversizeServices",
.product(name: "OversizeCore", package: "OversizeCore"),
.product(name: "OversizeModels", package: "OversizeModels"),
.product(name: "FactoryKit", package: "Factory"),
],
),
.target(
name: "OversizeNotificationService",
dependencies: [
.product(name: "OversizeCore", package: "OversizeCore"),
.product(name: "OversizeModels", package: "OversizeModels"),
.product(name: "FactoryKit", package: "Factory"),
],
),
Expand Down
105 changes: 105 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ A comprehensive collection of service modules for Apple platforms that provides
- 👥 **Contacts Services** - Contacts framework integration
- 🔔 **Notification Services** - Local notifications management
- 📁 **File Manager Services** - File operations with iCloud Documents support
- 🌐 **Translation Services** - Apple Translation framework integration for text translation
- 🧠 **Intelligence Services** - Apple Intelligence framework integration (iOS 26.0+)
- 🏭 **Dependency Injection** - Factory-based service registration and injection
- 🌐 **Multi-platform** - Support for iOS, macOS, tvOS, and watchOS

Expand Down Expand Up @@ -358,6 +360,109 @@ let localURL = URL(fileURLWithPath: "/path/to/local/document.pdf")
let localResult = await fileManager.saveDocument(pickedURL: localURL, folder: "LocalDocs")
```

### 🌐 TranslationService

Apple Translation framework integration for text translation.

**Features:**
- Single and batch text translation
- Automatic source language detection
- Streaming batch translations
- Pre-loading translation models
- Multi-platform support (iOS 26.0+, macOS 26.0+)

**Usage Example:**

```swift
import OversizeServices
import FactoryKit

// Inject the service
@Injected(\.translationService) var translationService: TranslationServiceProtocol

// Single translation
do {
let translation = try await translationService.translate(
"Hello, world!",
from: .init(languageCode: .english),
to: .init(languageCode: .russian)
)
print("Translation: \(translation)")
} catch {
print("Translation error: \(error)")
}

// Auto-detect source language
let autoTranslation = try await translationService.translate(
"Hello, world!",
from: nil, // auto-detect
to: .init(languageCode: .russian)
)

// Batch translation
let texts = ["Hello", "Goodbye", "Thank you"]
let translations = try await translationService.translate(
texts,
from: .init(languageCode: .english),
to: .init(languageCode: .russian)
)

// Streaming batch translation
let requests = texts.enumerated().map {
(text: $0.element, id: "\($0.offset)")
}

for try await (id, translation) in translationService.translateBatch(
requests,
from: nil,
to: .init(languageCode: .russian)
) {
print("[\(id)] \(translation)")
}

// Pre-load translation model
try await translationService.prepareTranslation(
from: .init(languageCode: .english),
to: .init(languageCode: .russian)
)
```

**Platform Requirements:**
- Protocol available from iOS 17.4+, macOS 14.4+ (for dependency injection)
- Translation functionality requires iOS 26.0+, macOS 26.0+
- Requires physical device (does not work in Simulator)
- Internet connection needed for initial language model download

### 🧠 IntelligenceService

Apple Intelligence framework integration (iOS 26.0+, macOS 26.0+).

**Features:**
- Text summarization
- Writing tools integration
- Privacy-focused on-device processing

**Usage Example:**

```swift
import OversizeServices
import FactoryKit

// Inject the service (iOS 26.0+ only)
@Injected(\.intelligenceService) var intelligenceService: IntelligenceServiceProtocol

// Summarize text
do {
let summary = try await intelligenceService.summarize(
"Long text to summarize...",
type: .brief
)
print("Summary: \(summary)")
} catch {
print("Summarization error: \(error)")
}
```

### 🏭 OversizeServices (Core)

The main module that provides service registration and dependency injection setup.
Expand Down
31 changes: 15 additions & 16 deletions Sources/OversizeCalendarService/CalendarService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@
#endif
import Foundation
import OversizeCore
import OversizeModels

#if !os(tvOS)
public class CalendarService: @unchecked Sendable {
private let eventStore: EKEventStore = .init()
public init() {}

func requestFullAccess() async -> Result<Bool, AppError> {
func requestFullAccess() async -> Result<Bool, Error> {
do {
let status: Bool = if #available(iOS 17.0, macOS 14.0, watchOS 10.0, *) {
try await eventStore.requestFullAccessToEvents()
Expand All @@ -25,14 +24,14 @@ public class CalendarService: @unchecked Sendable {
if status {
return .success(true)
} else {
return .failure(AppError.eventKit(type: .notAccess))
return .failure(CalendarError.accessDenied)
}
} catch {
return .failure(AppError.eventKit(type: .notAccess))
return .failure(CalendarError.accessDenied)
}
}

func requestWriteOnlyAccess() async -> Result<Bool, AppError> {
func requestWriteOnlyAccess() async -> Result<Bool, Error> {
do {
let status: Bool = if #available(iOS 17.0, macOS 14.0, watchOS 10.0, *) {
try await eventStore.requestWriteOnlyAccessToEvents()
Expand All @@ -42,14 +41,14 @@ public class CalendarService: @unchecked Sendable {
if status {
return .success(true)
} else {
return .failure(AppError.eventKit(type: .notAccess))
return .failure(CalendarError.accessDenied)
}
} catch {
return .failure(AppError.eventKit(type: .notAccess))
return .failure(CalendarError.accessDenied)
}
}

public func fetchEvents(start: Date, end: Date = Date(), filtredCalendarsIds: [String] = []) async -> Result<[EKEvent], AppError> {
public func fetchEvents(start: Date, end: Date = Date(), filtredCalendarsIds: [String] = []) async -> Result<[EKEvent], Error> {
let access = await requestFullAccess()
if case let .failure(error) = access { return .failure(error) }
let calendars = eventStore.calendars(for: .event)
Expand All @@ -70,21 +69,21 @@ public class CalendarService: @unchecked Sendable {
return .success(events)
}

public func fetchCalendars() async -> Result<[EKCalendar], AppError> {
public func fetchCalendars() async -> Result<[EKCalendar], Error> {
let access = await requestFullAccess()
if case let .failure(error) = access { return .failure(error) }
let calendars = eventStore.calendars(for: .event)
return .success(calendars)
}

public func fetchDefaultCalendar() async -> Result<EKCalendar?, AppError> {
public func fetchDefaultCalendar() async -> Result<EKCalendar?, Error> {
let access = await requestFullAccess()
if case let .failure(error) = access { return .failure(error) }
let calendar = eventStore.defaultCalendarForNewEvents
return .success(calendar)
}

public func fetchSourses() async -> Result<[EKSource], AppError> {
public func fetchSourses() async -> Result<[EKSource], Error> {
let access = await requestFullAccess()
if case let .failure(error) = access { return .failure(error) }
let calendars = eventStore.sources
Expand All @@ -108,7 +107,7 @@ public class CalendarService: @unchecked Sendable {
recurrenceRules: CalendarEventRecurrenceRules = .never,
recurrenceEndRules: CalendarEventEndRecurrenceRules = .never,
span: EKSpan = .thisEvent,
) async -> Result<Bool, AppError> {
) async -> Result<Bool, Error> {
let access = await requestWriteOnlyAccess()
if case let .failure(error) = access { return .failure(error) }
let newEvent: EKEvent = if let event {
Expand Down Expand Up @@ -159,23 +158,23 @@ public class CalendarService: @unchecked Sendable {
#endif
return .success(true)
} catch {
return .failure(.eventKit(type: .savingItem))
return .failure(CalendarError.saveFailed)
}
}

@available(iOS 15.0, macOS 13.0, visionOS 1.0, *)
public func deleteEvent(identifier: String, span: EKSpan = .thisEvent) async -> Result<Bool, AppError> {
public func deleteEvent(identifier: String, span: EKSpan = .thisEvent) async -> Result<Bool, Error> {
let access = await requestFullAccess()
if case let .failure(error) = access { return .failure(error) }
guard let event = eventStore.fetchEvent(identifier: identifier) else { return .failure(.custom(title: "Not deleted")) }
guard let event = eventStore.fetchEvent(identifier: identifier) else { return .failure(CalendarError.itemNotFound) }

do {
#if !os(watchOS)
try eventStore.remove(event, span: span, commit: true)
#endif
return .success(true)
} catch {
return .failure(.eventKit(type: .deleteItem))
return .failure(CalendarError.deleteFailed)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ public extension EKEvent {
return meetType.title
} else if let location = location?.components(separatedBy: .newlines), let locationText: String = location.first {
if locationText.count < 16 {
let clean = locationText.trimmingCharacters(in: .whitespacesAndNewlines)
return clean
return locationText.trimmingCharacters(in: .whitespacesAndNewlines)
} else {
var clean = locationText.trimmingCharacters(in: .whitespacesAndNewlines)
let range = clean.index(clean.startIndex, offsetBy: 16) ..< clean.endIndex
Expand Down
12 changes: 6 additions & 6 deletions Sources/OversizeContactsService/ContactsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,31 @@
import Contacts
#endif
import Foundation
import OversizeModels
import OversizeCore

#if canImport(Contacts)
public class ContactsService: @unchecked Sendable {
private let contactStore: CNContactStore = .init()
public init() {}

public func requestAccess() async -> Result<Bool, AppError> {
public func requestAccess() async -> Result<Bool, Error> {
do {
let status = try await contactStore.requestAccess(for: .contacts)
if status {
return .success(true)
} else {
return .failure(AppError.contacts(type: .notAccess))
return .failure(ContactsError.notAccess)
}
} catch {
return .failure(AppError.contacts(type: .notAccess))
return .failure(ContactsError.notAccess)
}
}

public func fetchContacts(
keysToFetch: [CNKeyDescriptor] = [CNContactVCardSerialization.descriptorForRequiredKeys()],
order: CNContactSortOrder = .none,
unifyResults: Bool = true,
) async -> Result<[CNContact], AppError> {
) async -> Result<[CNContact], Error> {
var contacts: [CNContact] = []
let fetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch)
fetchRequest.unifyResults = unifyResults
Expand All @@ -42,7 +42,7 @@ public class ContactsService: @unchecked Sendable {
}
return .success(contacts)
} catch {
return .failure(AppError.contacts(type: .unknown))
return .failure(ContactsError.unknown(error))
}
}
}
Expand Down
Loading
Loading