Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/fuzzy-turkeys-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kingstinct/react-native-activity-kit": patch
---

Add alertkit
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ jobs:

build-ios:
# Only run on macOS since we need Xcode
runs-on: macos-15
runs-on: macos-26
timeout-minutes: 50

steps:
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
.DS_Store


generated
generated
/packages/react-native-alarm-kit/android
5 changes: 4 additions & 1 deletion apps/activity-kit-example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"infoPlist": {
"NSAlarmKitUsageDescription": "This app uses ActivityKit to demonstrate Live Activities."
},
"supportsTablet": true,
"bundleIdentifier": "com.robertherber.activity-kit-example"
},
Expand Down Expand Up @@ -40,7 +43,7 @@
"expo-build-properties",
{
"ios": {
"deploymentTarget": "18.0"
"deploymentTarget": "26.0"
}
}
],
Expand Down
31 changes: 30 additions & 1 deletion apps/activity-kit-example/app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ActivityKit } from '@kingstinct/react-native-activity-kit' // Importing the ActivityKit module'
import { ActivityKit, AlarmKit } from '@kingstinct/react-native-activity-kit' // Importing the ActivityKit module'
import { Image } from 'expo-image'
import * as Notifications from 'expo-notifications'
import { useState } from 'react'
Expand Down Expand Up @@ -50,6 +50,35 @@ export default function HomeScreen() {
}
title="Push permissions"
></Button>
<Button
onPress={() => AlarmKit.requestAuthorization()}
title="AlarmKit permissions"
></Button>

<Button
onPress={() => {
const countdownDurationInSeconds = 10
AlarmKit.createCountdown({
tintColor: { red: 255, green: 0, blue: 0, alpha: 0.5 },
alert: {
title: 'Pomodoro focus time over!',
stopButton: {
text: 'Ok, cool!',
systemImageName: 'stop.fill',
textColor: { red: 0, green: 255, blue: 0 },
},
},
countdown: {
title: 'Pomodoro focus time!',
},
preAlert: countdownDurationInSeconds,
metadata: {
timerFiringAt: Date.now() + countdownDurationInSeconds * 1000,
},
})
}}
title="Start countdown"
></Button>

{latestActivityId ? (
<Button
Expand Down
66 changes: 48 additions & 18 deletions apps/activity-kit-example/targets/widget/WidgetLiveActivity.swift
Original file line number Diff line number Diff line change
@@ -1,32 +1,62 @@
import ActivityKit
import AlarmKit
import WidgetKit
import SwiftUI
import NitroActivityKitCore

struct WidgetLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: ActivityKitModuleAttributes.self) { context in
// Lock screen/banner UI goes here
VStack {
Text("Hello \(context.state.getAsString("name"))")
Text(context.state.getDate("startedTimerAt") ?? Date(), style: .timer)
ActivityConfiguration(for: ActivityKitModuleAttributes.self) { context in
// Changed VStack to mimic built-in timer style
ZStack {
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(.ultraThinMaterial)

HStack(spacing: 8) {
Text("Timer")
.font(.subheadline.weight(.medium))
.foregroundColor(.secondary)

Text(context.state.getDate("startedTimerAt") ?? Date(), style: .timer)
.font(.system(size: 36, weight: .medium, design: .monospaced))
.foregroundColor(.orange)
.minimumScaleFactor(0.5)
.lineLimit(1)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
}
.activityBackgroundTint(Color.cyan)
.activitySystemActionForegroundColor(Color.black)
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
.padding(16)
.activityBackgroundTint(Color.black.opacity(0.1))
.containerBackground(.ultraThinMaterial, for: .widget)
// .activitySystemActionForegroundColor(Color.black)

} dynamicIsland: { context in
DynamicIsland {
// Expanded UI goes here. Compose the expanded UI through
// various regions, like leading/trailing/center/bottom
DynamicIslandExpandedRegion(.leading) {
Text("Leading")
}
DynamicIslandExpandedRegion(.trailing) {
Text("Trailing")
}
DynamicIslandExpandedRegion(.bottom) {
Text("Bottom \(context.state.getString("name"))")
// more content
DynamicIslandExpandedRegion(.leading) { Text("Leading") }
DynamicIslandExpandedRegion(.trailing) { Text("Trailing") }
DynamicIslandExpandedRegion(.center) {
ZStack {
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(.ultraThinMaterial)

HStack(spacing: 8) {
Text("Timer")
.font(.subheadline.weight(.medium))
.foregroundColor(.secondary)

Text(context.state.getDate("startedTimerAt") ?? Date(), style: .timer)
.font(.system(size: 36, weight: .medium, design: .monospaced))
.foregroundColor(.orange)
.minimumScaleFactor(0.5)
.lineLimit(1)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
}
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
.padding(16)
}
} compactLeading: {
Text("L")
Expand Down
111 changes: 111 additions & 0 deletions apps/activity-kit-example/targets/widget/WidgetLiveActivityAlarm.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import ActivityKit
import AlarmKit
import WidgetKit
import SwiftUI
import NitroActivityKitCore

@available(iOS 26.0, *)
func getAlarmTimeInterval() -> Date {
do {
let addInterval = try AlarmManager.shared.alarms.first(where: { alarm in
alarm.state == .countdown
})?.countdownDuration?.preAlert ?? 10

let now = Date(timeIntervalSinceNow: addInterval)

// now.addTimeInterval(addInterval)

return now
} catch {
print("AlarmKit: failed to fetch countdown alarm — \(error)")
return Date(timeIntervalSinceNow: 20)
}

}

@available(iOS 26.0, *)
struct WidgetLiveActivityAlarm: Widget {

var body: some WidgetConfiguration {

ActivityConfiguration(
for: AlarmAttributes<GenericDictionaryAlarmStruct>.self) { context in
// Changed VStack to mimic built-in timer style
ZStack {
/*RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(.ultraThinMaterial)*/
// context.state.mode <- for checking state
HStack(spacing: 8) {
Text("Timer")
.font(.subheadline.weight(.medium))
// .glassEffect(.tint(.orange))
.foregroundColor(.secondary)

Text(context.attributes.metadata?.getDate("timerFiringAt") ?? Date(),
style: .timer)
.font(.system(size: 36, weight: .medium, design: .monospaced))
.foregroundColor(.orange)
.minimumScaleFactor(0.5)
// .glassEffect(.tint(.orange))
.lineLimit(1)
}
.padding()
.activityBackgroundTint(Color.black.opacity(0.1))
.frame(maxWidth: .infinity, alignment: .leading)
}
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
.padding(16)
.activityBackgroundTint(Color.black.opacity(0.1))
.containerBackground(.ultraThinMaterial, for: .widget)
// .activitySystemActionForegroundColor(Color.black)

} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Text("Leading")
.glassEffect()
}
DynamicIslandExpandedRegion(.trailing) {
Text("Trailing")
.glassEffect()
}
DynamicIslandExpandedRegion(.center) {
ZStack {
RoundedRectangle(cornerRadius: 18, style: .continuous)
.fill(.ultraThinMaterial)

HStack(spacing: 8) {
Text("Timer")
.font(.subheadline.weight(.medium))
.foregroundColor(.secondary)
.glassEffect()
Text(context.attributes.metadata?.getDate("timerFiringAt") ?? Date(),
style: .timer,
)
.font(.system(size: 36, weight: .medium, design: .monospaced))
.foregroundColor(.orange)
.minimumScaleFactor(0.5)
.lineLimit(1)
.glassEffect()
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
}
.clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
.padding(16)
}
} compactLeading: {
Text("L")
.glassEffect()
} compactTrailing: {
Text("Hello")
.glassEffect()
} minimal: {
Text("Hello")
.glassEffect()
}
.widgetURL(URL(string: "https://www.expo.dev"))
.keylineTint(Color.red)
}
}
}
23 changes: 17 additions & 6 deletions apps/activity-kit-example/targets/widget/index.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@ import SwiftUI

@main
struct exportWidgets: WidgetBundle {
var body: some Widget {
// Export widgets here
widget()
widgetControl()
WidgetLiveActivity()
}
@WidgetBundleBuilder
var body: some Widget {
widgets()
}

private func widgets() -> some Widget {
if #available(iOS 26, *) {
return WidgetBundleBuilder.buildBlock( widget(),
// widgetControl(),
WidgetLiveActivity(),
WidgetLiveActivityAlarm())
} else {
return WidgetBundleBuilder.buildBlock( widget(),
// widgetControl(),
WidgetLiveActivity())
}
}
}
6 changes: 5 additions & 1 deletion apps/activity-kit-example/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
],
"@kingstinct/react-native-activity-kit/*": [
"./packages/react-native-activity-kit/src/*"
]
],
"react-native-alarm-kit": [
"./packages/react-native-alarm-kit/src/index.ts"
],
"react-native-alarm-kit/*": ["./packages/react-native-alarm-kit/src/*"]
}
},
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
Expand Down
Loading
Loading