Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
221 changes: 218 additions & 3 deletions RubyEvents.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@
<dict>
<key>SchemeUserState</key>
<dict>
<key>AnyCodable (Playground).xcscheme</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
</dict>
<key>RubyEvents.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>RubyEventsWidgetsExtension.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
</dict>
</dict>
</plist>
32 changes: 32 additions & 0 deletions RubyEvents/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ class App {
self.tabBarController.setupTabs()

switchToTabController()

NextEventUpdater.refresh()
NextEventUpdater.scheduleBackgroundRefresh()

NotificationCenter.default.addObserver(
forName: UIApplication.didBecomeActiveNotification, object: nil, queue: .main
) { _ in
NextEventUpdater.refresh()
}
}

func switchToNavigationController() {
Expand All @@ -78,6 +87,29 @@ class App {
func navigatorFor(title: String) -> Navigator? {
tabBarController.navigatorFor(title: title)
}

func route(_ url: URL) {
let target = webURL(from: url)

guard isTabbed else {
navigator.route(target)
return
}

if let index = tabBarController.configuration?.items.firstIndex(where: { $0.title == "Events" }) {
tabBarController.selectedIndex = index
}

(navigatorFor(title: "Events") ?? tabBarController.currentNavigator)?.route(target)
}

private func webURL(from url: URL) -> URL {
guard url.scheme == "rubyevents" else { return url }

let path = "/\(url.host ?? "")\(url.path)"

return Router.instance.root_url().appendingPathComponent(path)
}
}

extension App: NavigatorDelegate {
Expand Down
5 changes: 4 additions & 1 deletion RubyEvents/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
Hotwire.config.applicationUserAgentPrefix = "Hotwire Native iOS; app_version: \(versionNumber); unique_device_id: \(uniqueDeviceId);"

Hotwire.registerBridgeComponents([
ButtonComponent.self
ButtonComponent.self,
LiveActivityComponent.self
])

Hotwire.config.showDoneButtonOnModals = true
Hotwire.config.debugLoggingEnabled = true

NextEventUpdater.registerBackgroundTask()

Hotwire.loadPathConfiguration(from: [
.server(Router.instance.path_configuration_url()),
.file(Bundle.main.url(forResource: "path-configuration", withExtension: "json")!)
Expand Down
26 changes: 24 additions & 2 deletions RubyEvents/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,21 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>org.rubyevents.RubyEvents</string>
<key>CFBundleURLSchemes</key>
<array>
<string>rubyevents</string>
</array>
</dict>
</array>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
Expand All @@ -23,5 +36,14 @@
</array>
</dict>
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
</array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>org.rubyevents.RubyEvents.nextevent.refresh</string>
</array>
</dict>
</plist>
10 changes: 10 additions & 0 deletions RubyEvents/RubyEvents.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.rubyevents.RubyEvents</string>
</array>
</dict>
</plist>
22 changes: 22 additions & 0 deletions RubyEvents/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,27 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
App.instance.start(sceneDelegate: self)

if let url = connectionOptions.urlContexts.first?.url ?? connectionOptions.userActivities.first?.webpageURL {
routeAfterLaunch(url)
}
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
App.instance.route(url)
}
}

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let url = userActivity.webpageURL {
App.instance.route(url)
}
}

private func routeAfterLaunch(_ url: URL) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
App.instance.route(url)
}
}
}
57 changes: 57 additions & 0 deletions RubyEvents/bridge/LiveActivityComponent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// LiveActivityComponent.swift
// RubyEvents
//

import Foundation
import HotwireNative
import UIKit

final class LiveActivityComponent: BridgeComponent {
override class var name: String { "live-activity" }

override func onReceive(message: Message) {
switch message.event {
case "status":
replyStatus(to: message)

case "start":
guard let data: StartData = message.data() else { return }

Task {
await LiveActivityManager.shared.start(eventSlug: data.slug, eventName: data.name ?? "")

if LiveActivityManager.shared.isRunning {
UINotificationFeedbackGenerator().notificationOccurred(.success)
}

replyStatus(to: message)
}

case "stop":
Task {
await LiveActivityManager.shared.stop()
replyStatus(to: message)
}

default:
break
}
}

private func replyStatus(to message: Message) {
let data: StartData? = message.data()
let active = data.map { LiveActivityManager.shared.isActive(eventSlug: $0.slug) } ?? false

reply(to: message.event, with: StatusReply(active: active))
}

private struct StartData: Decodable {
let slug: String
let name: String?
}

private struct StatusReply: Encodable {
let active: Bool
}
}
Loading