├── live-activity-control ├── expo-module.config.json ├── src │ ├── LiveActivityControlModule.web.ts │ └── LiveActivityControlModule.ts ├── ios │ ├── Attributes.swift │ ├── LiveActivityControl.podspec │ └── LiveActivityControlModule.swift ├── LICENSE └── index.ts └── README.md /live-activity-control/expo-module.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "platforms": ["ios"], 3 | "ios": { 4 | "modules": ["LiveActivityControlModule"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /live-activity-control/src/LiveActivityControlModule.web.ts: -------------------------------------------------------------------------------- 1 | const fallback = { 2 | areActivitiesEnabled: () => false, 3 | startActivity( 4 | _startTime: number, 5 | _endTime: number, 6 | _title: string, 7 | _headline: string, 8 | _widgetUrl: string, 9 | ) { 10 | return false; 11 | }, 12 | endActivity(_title: string, _headline: string, _widgetUrl: string) {}, 13 | }; 14 | 15 | export default fallback; 16 | -------------------------------------------------------------------------------- /live-activity-control/ios/Attributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FizlAttributes.swift 3 | // Fizl 4 | // 5 | // Created by Dominic on 2023-12-27. 6 | // 7 | 8 | import ActivityKit 9 | import SwiftUI 10 | 11 | struct FizlAttributes: ActivityAttributes { 12 | public typealias FizlStatus = ContentState 13 | 14 | public struct ContentState: Codable, Hashable { 15 | var startTime: Date 16 | var endTime: Date 17 | var title: String 18 | var headline: String 19 | var widgetUrl: String 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /live-activity-control/src/LiveActivityControlModule.ts: -------------------------------------------------------------------------------- 1 | import { requireNativeModule, Platform } from 'expo-modules-core'; 2 | 3 | const fallback = { 4 | areActivitiesEnabled: () => false, 5 | startActivity( 6 | _startTime: number, 7 | _endTime: number, 8 | _title: string, 9 | _headline: string, 10 | _widgetUrl: string, 11 | ) { 12 | return false; 13 | }, 14 | endActivity(_title: string, _headline: string, _widgetUrl: string) {}, 15 | }; 16 | 17 | export default Platform.OS === 'ios' 18 | ? requireNativeModule('LiveActivityControl') 19 | : fallback; 20 | -------------------------------------------------------------------------------- /live-activity-control/ios/LiveActivityControl.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'LiveActivityControl' 3 | s.version = '1.0.0' 4 | s.summary = 'Methods to control a live activity' 5 | s.description = 'Methods to control a live activity' 6 | s.author = 'Ludditech Inc.' 7 | s.homepage = 'https://fizl.io' 8 | s.platform = :ios, '13.0' 9 | s.source = { git: '' } 10 | s.static_framework = true 11 | 12 | s.dependency 'ExpoModulesCore' 13 | 14 | # Swift/Objective-C compatibility 15 | s.pod_target_xcconfig = { 16 | 'DEFINES_MODULE' => 'YES', 17 | 'SWIFT_COMPILATION_MODE' => 'wholemodule' 18 | } 19 | 20 | s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" 21 | end 22 | -------------------------------------------------------------------------------- /live-activity-control/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ludditech Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /live-activity-control/index.ts: -------------------------------------------------------------------------------- 1 | // Import the native module. On web, it will be resolved to LiveActivityControl.web.ts 2 | // and on native platforms to LiveActivityControl.ts 3 | import LiveActivityControlModule from './src/LiveActivityControlModule'; 4 | 5 | export function areActivitiesEnabled(): boolean { 6 | return LiveActivityControlModule.areActivitiesEnabled(); 7 | } 8 | 9 | interface StartActivityOptions { 10 | startTime: Date; 11 | endTime: Date; 12 | title: string; 13 | headline: string; 14 | widgetUrl: string; 15 | } 16 | 17 | interface EndActivityOptions { 18 | title: string; 19 | headline: string; 20 | widgetUrl: string; 21 | } 22 | 23 | /** 24 | * Starts an iOS Live Activity. 25 | * @param options Options for the activity. 26 | */ 27 | export function startActivity(options: StartActivityOptions): boolean { 28 | return startActivityInner( 29 | Math.floor(options.startTime.getTime() / 1000), 30 | Math.floor(options.endTime.getTime() / 1000), 31 | options.title, 32 | options.headline, 33 | options.widgetUrl 34 | ); 35 | } 36 | 37 | function startActivityInner( 38 | startTime: number, 39 | endTime: number, 40 | title: string, 41 | headline: string, 42 | widgetUrl: string 43 | ): boolean { 44 | return LiveActivityControlModule.startActivity( 45 | startTime, 46 | endTime, 47 | title, 48 | headline, 49 | widgetUrl 50 | ); 51 | } 52 | 53 | /** 54 | * Ends an iOS Live Activity. 55 | * @param options Options for the activity. 56 | */ 57 | export function endActivity(options: EndActivityOptions): void { 58 | endActivityInner(options.title, options.headline, options.widgetUrl); 59 | } 60 | 61 | function endActivityInner( 62 | title: string, 63 | headline: string, 64 | widgetUrl: string 65 | ): void { 66 | LiveActivityControlModule.endActivity(title, headline, widgetUrl); 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Live Activity Control Module 2 | 3 | Welcome to the GitHub repository for the _Live Activity Control Module_ project developed by Fizl. 4 | 5 | This module is an [Expo Native Module](https://docs.expo.dev/modules/get-started/#creating-the-local-expo-module) that allows you to control the live activity of the app. Whether you are a beginner or experienced developer, we invite you to follow this article in English[^1] or French[^2], which will explain in detail how to integrate and use the *Live Activity Sample* project in your iOS applications. 6 | 7 | ## Installation 8 | 9 | Create a new Expo project and a new expo module. The copy the files from this repository to the new module. You will need to build your expo project to see the changes. It will only work with development clients and not with Expo Go. 10 | 11 | To use the module in your project, use the functions exported from the `index.ts` file. These are the bindings to the native module. 12 | 13 | ## About Us 14 | 🔗 Visit our [website](https://fizl.io). 15 | 16 | 🌎 Located in Montreal, Canada. 17 | 18 | 💼 For career opportunities, please email careers@fizl.io. 19 | 20 | 🎓 For internship inquiries, please email internships@fizl.io. 21 | 22 | 🔔 Follow us on [LinkedIn](https://www.linkedin.com/company/fizl), [Youtube](https://www.youtube.com/@fizlapp), [Facebook](https://www.facebook.com/fizl.app1), [Instagram](fizl.app) and [TikTok](https://www.tiktok.com/@fizl.app?fbclid=IwAR39V3Gc62d85chxyevQVVRNqcl_lgb3Cm8sBk2fJqzpqztSLF0gVeMbbEE) to stay updated. 23 | 24 | 🎯 Empower service entrepreneurs to become what they aspire to be. 25 | 26 | 💚 Ludditech, the company behind Fizl, was named in honor of the 19th-century Luddites, who opposed the use of machines for fear of losing their jobs. With this legacy at the heart of our identity, our deep conviction is that technology should not replace workers, but assist them in realizing their potential – to "become what they aspire to be". 27 | 28 | [^1]: Article in English: [Implementing Live Activities in React-Native with Expo](https://fizl.io/en/blog/posts/live-activities) 29 | [^2]: Article in French: [Implémenter des Live Activities en React-Native avec Expo](https://fizl.io/fr/blog/posts/live-activities) 30 | -------------------------------------------------------------------------------- /live-activity-control/ios/LiveActivityControlModule.swift: -------------------------------------------------------------------------------- 1 | import ExpoModulesCore 2 | import ActivityKit 3 | 4 | public class LiveActivityControlModule: Module { 5 | public func definition() -> ModuleDefinition { 6 | Name("LiveActivityControl") 7 | 8 | Function("areActivitiesEnabled") { () -> Bool in 9 | let logger = Logger() 10 | logger.info("areActivitiesEnabled()") 11 | 12 | if #available(iOS 16.2, *) { 13 | return ActivityAuthorizationInfo().areActivitiesEnabled 14 | } else { 15 | return false 16 | } 17 | } 18 | 19 | Function("startActivity") { (startTimeUnix: UInt64, endTimeUnix: UInt64, title: String, headline: String, widgetUrl: String) -> Bool in 20 | let logger = Logger() 21 | logger.info("startActivity()") 22 | 23 | let startTime = Date(timeIntervalSince1970: TimeInterval(startTimeUnix)) 24 | let endTime = Date(timeIntervalSince1970: TimeInterval(endTimeUnix)) 25 | 26 | if #available(iOS 16.2, *) { 27 | let attributes = FizlAttributes() 28 | let contentState = FizlAttributes.ContentState(startTime: startTime, endTime: endTime, title: title, headline: headline, widgetUrl: widgetUrl) 29 | 30 | let activityContent = ActivityContent(state: contentState, staleDate: nil) 31 | 32 | do { 33 | let activity = try Activity.request(attributes: attributes, content: activityContent) 34 | logger.info("Requested a Live Activity \(String(describing: activity.id)).") 35 | return true 36 | } catch (let error) { 37 | logger.info("Error requesting Live Activity \(error.localizedDescription).") 38 | return false 39 | } 40 | } else { 41 | logger.info("iOS version is lower than 16.2. Live Activity is not available.") 42 | return false 43 | } 44 | } 45 | 46 | Function("endActivity") { (title: String, headline: String, widgetUrl: String) -> Void in 47 | let logger = Logger() 48 | logger.info("endActivity()") 49 | 50 | if #available(iOS 16.2, *) { 51 | let contentState = FizlAttributes.ContentState(startTime: .now, endTime: .now, title: title, headline: headline, widgetUrl: widgetUrl) 52 | let finalContent = ActivityContent(state: contentState, staleDate: nil) 53 | 54 | Task { 55 | for activity in Activity.activities { 56 | await activity.end(finalContent, dismissalPolicy: .immediate) 57 | logger.info("Ending the Live Activity: \(activity.id)") 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } --------------------------------------------------------------------------------