Skip to content

Commit

Permalink
Create watchOS version (#110)
Browse files Browse the repository at this point in the history
feat: create watchOS version

closes #6
  • Loading branch information
lui5fl authored Oct 2, 2024
1 parent df161a1 commit 00b4ed0
Show file tree
Hide file tree
Showing 22 changed files with 924 additions and 9 deletions.
328 changes: 321 additions & 7 deletions Hax.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

92 changes: 92 additions & 0 deletions Hax.xcodeproj/xcshareddata/xcschemes/HaxWatch.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A0AC531C2CA1DC9900872903"
BuildableName = "HaxWatch.app"
BlueprintName = "HaxWatch"
ReferencedContainer = "container:Hax.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A064ABA828D3ADA500572ADD"
BuildableName = "Hax.app"
BlueprintName = "Hax"
ReferencedContainer = "container:Hax.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A0AC531C2CA1DC9900872903"
BuildableName = "HaxWatch.app"
BlueprintName = "HaxWatch"
ReferencedContainer = "container:Hax.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "A0AC531C2CA1DC9900872903"
BuildableName = "HaxWatch.app"
BlueprintName = "HaxWatch"
ReferencedContainer = "container:Hax.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
61 changes: 61 additions & 0 deletions Hax/Domain/Delegates/HaxWCSessionDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// HaxWCSessionDelegate.swift
// Hax
//
// Created by Luis Fariña on 30/9/24.
//

import WatchConnectivity

final class HaxWCSessionDelegate: NSObject {

// MARK: Properties

var didReceiveApplicationContext: (
@Sendable (_ applicationContext: [String: Any]) -> Void
)?

// MARK: Initialization

override init() {
super.init()

guard WCSession.isSupported() else {
return
}

let session = WCSession.default
session.delegate = self
session.activate()
}
}

// MARK: - WCSessionDelegate

extension HaxWCSessionDelegate: WCSessionDelegate {

func session(
_ session: WCSession,
activationDidCompleteWith activationState: WCSessionActivationState,
error: (any Error)?
) {
// Do nothing
}

#if !os(watchOS)
func sessionDidBecomeInactive(_ session: WCSession) {
// Do nothing
}

func sessionDidDeactivate(_ session: WCSession) {
// Do nothing
}
#endif

func session(
_ session: WCSession,
didReceiveApplicationContext applicationContext: [String: Any]
) {
didReceiveApplicationContext?(applicationContext)
}
}
1 change: 1 addition & 0 deletions Hax/Domain/Models/Constant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ enum Constant {
static let hackerNewsFeedURLString = "hax://feed/"
static let hackerNewsItemURLString = "news.ycombinator.com/item?id="
static let hackerNewsUserURLString = "news.ycombinator.com/user?id="
static let readItemUserActivity = "com.luisfl.Hax.UserActivity.ReadItem"
}
7 changes: 7 additions & 0 deletions Hax/HaxApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ struct HaxApp: App {
var body: some Scene {
WindowGroup {
MainView(model: mainViewModel)
.onContinueUserActivity(
Constant.readItemUserActivity
) { userActivity in
if let id = userActivity.userInfo?["id"] as? Int {
mainViewModel.presentedItem = Item(id: id)
}
}
.onOpenURL(perform: handleURL)
}
.modelContainer(
Expand Down
13 changes: 13 additions & 0 deletions Hax/Presentation/Screens/View Models/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,23 @@ final class MainViewModel: MainViewModelProtocol {
var selectedItem: Item?
var presentedItem: Item?
var presentedUser: IdentifiableString?
private let haxWCSessionDelegate = HaxWCSessionDelegate()

// MARK: Initialization

init(defaultFeedService: some DefaultFeedServiceProtocol = DefaultFeedService()) {
selectedFeed = defaultFeedService.defaultFeed()
haxWCSessionDelegate.didReceiveApplicationContext = { [weak self] applicationContext in
guard
let self,
let id = applicationContext["id"] as? Int
else {
return
}

Task { @MainActor in
presentedItem = Item(id: id)
}
}
}
}
8 changes: 6 additions & 2 deletions Hax/Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand All @@ -17,5 +15,11 @@
</array>
</dict>
</array>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>NSUserActivityTypes</key>
<array>
<string>com.luisfl.Hax.UserActivity.ReadItem</string>
</array>
</dict>
</plist>
20 changes: 20 additions & 0 deletions Hax/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,16 @@
}
}
},
"Open Hax on your iPhone to read it." : {
"localizations" : {
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Abre Hax en tu iPhone para leerla."
}
}
}
},
"Opens the specified feed." : {
"localizations" : {
"es" : {
Expand Down Expand Up @@ -541,6 +551,16 @@
}
}
},
"Story sent to your iPhone" : {
"localizations" : {
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Historia enviada a tu iPhone"
}
}
}
},
"Thank you very much!" : {
"localizations" : {
"es" : {
Expand Down
24 changes: 24 additions & 0 deletions HaxWatch/HaxWatchApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// HaxWatchApp.swift
// HaxWatch
//
// Created by Luis Fariña on 23/9/24.
//

import SwiftUI

@main
struct HaxWatchApp: App {

// MARK: Properties

private let haxWCSessionDelegate = HaxWCSessionDelegate()

// MARK: Body

var body: some Scene {
WindowGroup {
MainView()
}
}
}
62 changes: 62 additions & 0 deletions HaxWatch/Presentation/Components/ItemRowView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// ItemRowView.swift
// HaxWatch
//
// Created by Luis Fariña on 25/9/24.
//

import SwiftUI

struct ItemRowView: View {

// MARK: Properties

let model: ItemRowViewModel

// MARK: Body

var body: some View {
VStack(alignment: .leading, spacing: 5) {
HStack(spacing: 3) {
if model.shouldDisplayIndex {
Text(verbatim: "#\(model.index)")
Text(verbatim: "")
}
if model.shouldDisplayScore,
let score = model.item.score {
Text(
"\(Image(systemName: "arrow.up"))\(score)",
tableName: ""
)
Text(verbatim: "")
}
if let elapsedTimeString = model.item.elapsedTimeString {
Text(elapsedTimeString)
}
}
.foregroundColor(.secondary)
.lineLimit(1)
if let title = model.item.title {
Text(title)
}
Spacer()
if let urlSimpleString = model.item.urlSimpleString {
Text(urlSimpleString)
.foregroundColor(.secondary)
.lineLimit(1)
}
}
}
}

// MARK: - Previews

#Preview {
ItemRowView(
model: ItemRowViewModel(
in: .feed,
index: 42,
item: .example
)
)
}
52 changes: 52 additions & 0 deletions HaxWatch/Presentation/Screens/View Models/FeedViewModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// FeedViewModel.swift
// HaxWatch
//
// Created by Luis Fariña on 24/9/24.
//

import Foundation

@MainActor
@Observable
class FeedViewModel {

// MARK: Types

enum State {

// MARK: Cases

case error(Error)
case loaded(items: [Item])
case loading
}

// MARK: Properties

let feed: Feed
var state = State.loading

// MARK: Initialization

init(feed: Feed) {
self.feed = feed
}

// MARK: Methods

func onAppear() async {
do {
state = .loaded(
items: try await HackerNewsService.shared.items(
in: feed,
page: 1,
pageSize: 10,
resetCache: true
)
)
} catch {
state = .error(error)
}
}
}
Loading

0 comments on commit 00b4ed0

Please sign in to comment.