diff --git a/CHURCH_SERVICE_MIGRATION.md b/CHURCH_SERVICE_MIGRATION.md new file mode 100644 index 0000000..08fdbc0 --- /dev/null +++ b/CHURCH_SERVICE_MIGRATION.md @@ -0,0 +1,149 @@ +# ChurchService Migration Guide + +The new `ChurchService` replaces all legacy networking services and uses the church_core Rust library for consistent, performant API calls. + +## Migration Overview + +**Replace these services:** +- `PocketBaseService` ❌ +- `BulletinService` ❌ +- `BibleService` ❌ +- `ConfigService` ❌ + +**With this unified service:** +- `ChurchService` ✅ + +## Quick Migration + +### Before (Old Services) +```swift +// Config +let config = try await PocketBaseService.shared.fetchConfig() + +// Events +let events = try await PocketBaseService.shared.fetchEvents() + +// Bulletins +let bulletins = try await BulletinService.shared.getBulletins() +let latest = try await BulletinService.shared.getLatestBulletin() + +// Bible Verses +let verse = try await BibleService.shared.getRandomVerse() +let specificVerse = try await BibleService.shared.getVerse(reference: "John 3:16") +``` + +### After (ChurchService) +```swift +// Config +let config = try await ChurchService.shared.fetchConfig() + +// Events +let events = try await ChurchService.shared.fetchEvents() + +// Bulletins +let bulletins = try await ChurchService.shared.getBulletins() +let latest = try await ChurchService.shared.getLatestBulletin() + +// Bible Verses +let verse = try await ChurchService.shared.getRandomVerse() +let specificVerse = try await ChurchService.shared.getVerse(reference: "John 3:16") + +// Contact +let success = try await ChurchService.shared.submitContact( + name: "John Doe", + email: "john@example.com", + message: "Hello" +) + +// Sermons +let sermons = try await ChurchService.shared.fetchSermons() +``` + +## Benefits of Migration + +### ✅ **Code Reduction** +- **Before:** ~500+ lines across 4 services +- **After:** ~220 lines in 1 service +- **Reduction:** 60%+ less duplicate code + +### ✅ **Performance** +- Uses optimized Rust networking (reqwest) +- Built-in memory caching +- Faster JSON parsing +- Better error handling + +### ✅ **Consistency** +- Same API used by website and Beacon +- Consistent data models +- Unified error handling +- Single source of truth + +### ✅ **Maintenance** +- One service to maintain +- Automatic updates from church_core +- Fewer bugs and edge cases +- Better testing coverage + +## Implementation Details + +### Required Files +1. **Add to project:** `church_core.swift` (UniFFI bindings) +2. **Add to project:** `libchurch_core.dylib` (Rust library) +3. **Replace with:** `ChurchService.swift` (Unified service) + +### Xcode Integration +1. Add `church_core.swift` to your Xcode project +2. Add `libchurch_core.dylib` to your project and link it +3. Replace service imports with `ChurchService.shared` + +### Backwards Compatibility +The new service maintains the same method signatures as the old services, so migration is a simple find-and-replace: + +```swift +// Find: PocketBaseService.shared +// Replace: ChurchService.shared + +// Find: BulletinService.shared +// Replace: ChurchService.shared + +// Find: BibleService.shared +// Replace: ChurchService.shared +``` + +## Testing +```swift +// Test the new service +Task { + do { + let config = try await ChurchService.shared.fetchConfig() + print("Church: \(config.churchName)") + + let verse = try await ChurchService.shared.getRandomVerse() + print("Verse: \(verse.reference) - \(verse.verse)") + + let bulletins = try await ChurchService.shared.getBulletins() + print("Bulletins: \(bulletins.count)") + + } catch { + print("Error: \(error)") + } +} +``` + +## Migration Checklist + +- [ ] Add `church_core.swift` to Xcode project +- [ ] Add `libchurch_core.dylib` to project and link +- [ ] Add `ChurchService.swift` to project +- [ ] Update Sermon model to be Codable (already done) +- [ ] Replace service imports in Views/ViewModels +- [ ] Test all functionality works +- [ ] Remove old service files (optional) +- [ ] Update any custom networking code + +## Next Steps + +Once migration is complete, you can: +1. Delete the old service files +2. Clean up any unused networking utilities +3. Enjoy simplified, faster networking! 🚀 \ No newline at end of file diff --git a/ChurchCore.xcframework/Info.plist b/ChurchCore.xcframework/Info.plist new file mode 100644 index 0000000..25e47e9 --- /dev/null +++ b/ChurchCore.xcframework/Info.plist @@ -0,0 +1,66 @@ + + + + + AvailableLibraries + + + BinaryPath + libchurch_core_device.a + HeadersPath + Headers + LibraryIdentifier + ios-arm64 + LibraryPath + libchurch_core_device.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + BinaryPath + libchurch_core_mac_catalyst.a + HeadersPath + Headers + LibraryIdentifier + ios-arm64_x86_64-maccatalyst + LibraryPath + libchurch_core_mac_catalyst.a + SupportedArchitectures + + arm64 + x86_64 + + SupportedPlatform + ios + SupportedPlatformVariant + maccatalyst + + + BinaryPath + libchurch_core_sim.a + HeadersPath + Headers + LibraryIdentifier + ios-arm64-simulator + LibraryPath + libchurch_core_sim.a + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/ChurchCore.xcframework/ios-arm64-simulator/Headers/church_coreFFI.h b/ChurchCore.xcframework/ios-arm64-simulator/Headers/church_coreFFI.h new file mode 100644 index 0000000..7ef029c --- /dev/null +++ b/ChurchCore.xcframework/ios-arm64-simulator/Headers/church_coreFFI.h @@ -0,0 +1,1204 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + uint64_t capacity; + uint64_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H +#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +typedef void (*UniffiForeignFutureFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +typedef void (*UniffiCallbackInterfaceFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE +typedef struct UniffiForeignFuture { + uint64_t handle; + UniffiForeignFutureFree _Nonnull free; +} UniffiForeignFuture; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +typedef struct UniffiForeignFutureStructU8 { + uint8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureStructU8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +typedef struct UniffiForeignFutureStructI8 { + int8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureStructI8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +typedef struct UniffiForeignFutureStructU16 { + uint16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureStructU16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +typedef struct UniffiForeignFutureStructI16 { + int16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureStructI16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +typedef struct UniffiForeignFutureStructU32 { + uint32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureStructU32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +typedef struct UniffiForeignFutureStructI32 { + int32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureStructI32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +typedef struct UniffiForeignFutureStructU64 { + uint64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureStructU64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +typedef struct UniffiForeignFutureStructI64 { + int64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureStructI64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +typedef struct UniffiForeignFutureStructF32 { + float returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureStructF32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +typedef struct UniffiForeignFutureStructF64 { + double returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureStructF64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +typedef struct UniffiForeignFutureStructPointer { + void*_Nonnull returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructPointer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +typedef void (*UniffiForeignFutureCompletePointer)(uint64_t, UniffiForeignFutureStructPointer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +typedef struct UniffiForeignFutureStructRustBuffer { + RustBuffer returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructRustBuffer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureStructRustBuffer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +typedef struct UniffiForeignFutureStructVoid { + RustCallStatus callStatus; +} UniffiForeignFutureStructVoid; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid + ); + +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_CALENDAR_EVENT_DATA +RustBuffer uniffi_church_core_fn_func_create_calendar_event_data(RustBuffer event_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +RustBuffer uniffi_church_core_fn_func_create_sermon_share_items_json(RustBuffer title, RustBuffer speaker, RustBuffer video_url, RustBuffer audio_url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_DEVICE_SUPPORTS_AV1 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_DEVICE_SUPPORTS_AV1 +int8_t uniffi_church_core_fn_func_device_supports_av1(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_FULL_VERSE_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_FULL_VERSE_TEXT +RustBuffer uniffi_church_core_fn_func_extract_full_verse_text(RustBuffer verses_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +RustBuffer uniffi_church_core_fn_func_extract_scripture_references_string(RustBuffer scripture_text, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +RustBuffer uniffi_church_core_fn_func_extract_stream_url_from_status(RustBuffer status_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BIBLE_VERSE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_bible_verse_json(RustBuffer query, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BULLETINS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BULLETINS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_bulletins_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CACHED_IMAGE_BASE64 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CACHED_IMAGE_BASE64 +RustBuffer uniffi_church_core_fn_func_fetch_cached_image_base64(RustBuffer url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CONFIG_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CONFIG_JSON +RustBuffer uniffi_church_core_fn_func_fetch_config_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CURRENT_BULLETIN_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CURRENT_BULLETIN_JSON +RustBuffer uniffi_church_core_fn_func_fetch_current_bulletin_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_EVENTS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_events_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_FEATURED_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_FEATURED_EVENTS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_featured_events_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVE_STREAM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVE_STREAM_JSON +RustBuffer uniffi_church_core_fn_func_fetch_live_stream_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_livestream_archive_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_random_bible_verse_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +RustBuffer uniffi_church_core_fn_func_fetch_scripture_verses_for_sermon_json(RustBuffer sermon_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SERMONS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SERMONS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_sermons_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_STREAM_STATUS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_STREAM_STATUS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_stream_status_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +RustBuffer uniffi_church_core_fn_func_filter_sermons_by_media_type(RustBuffer sermons_json, RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +RustBuffer uniffi_church_core_fn_func_format_event_for_display_json(RustBuffer event_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +RustBuffer uniffi_church_core_fn_func_format_scripture_text_json(RustBuffer scripture_text, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_TIME_RANGE_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_TIME_RANGE_STRING +RustBuffer uniffi_church_core_fn_func_format_time_range_string(RustBuffer start_time, RustBuffer end_time, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_HOME_FEED_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_HOME_FEED_JSON +RustBuffer uniffi_church_core_fn_func_generate_home_feed_json(RustBuffer events_json, RustBuffer sermons_json, RustBuffer bulletins_json, RustBuffer verse_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_VERSE_DESCRIPTION +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_VERSE_DESCRIPTION +RustBuffer uniffi_church_core_fn_func_generate_verse_description(RustBuffer verses_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_ABOUT_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_ABOUT_TEXT +RustBuffer uniffi_church_core_fn_func_get_about_text(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_AV1_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_AV1_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_av1_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_BRAND_COLOR +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_BRAND_COLOR +RustBuffer uniffi_church_core_fn_func_get_brand_color(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_ADDRESS +RustBuffer uniffi_church_core_fn_func_get_church_address(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_NAME +RustBuffer uniffi_church_core_fn_func_get_church_name(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_EMAIL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_EMAIL +RustBuffer uniffi_church_core_fn_func_get_contact_email(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_PHONE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_PHONE +RustBuffer uniffi_church_core_fn_func_get_contact_phone(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_COORDINATES +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_COORDINATES +RustBuffer uniffi_church_core_fn_func_get_coordinates(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_DONATION_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_DONATION_URL +RustBuffer uniffi_church_core_fn_func_get_donation_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_FACEBOOK_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_FACEBOOK_URL +RustBuffer uniffi_church_core_fn_func_get_facebook_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_HLS_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_HLS_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_hls_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_INSTAGRAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_INSTAGRAM_URL +RustBuffer uniffi_church_core_fn_func_get_instagram_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_LIVESTREAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_LIVESTREAM_URL +RustBuffer uniffi_church_core_fn_func_get_livestream_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +RustBuffer uniffi_church_core_fn_func_get_media_type_display_name(RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_ICON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_ICON +RustBuffer uniffi_church_core_fn_func_get_media_type_icon(RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MISSION_STATEMENT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MISSION_STATEMENT +RustBuffer uniffi_church_core_fn_func_get_mission_statement(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_OPTIMAL_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_OPTIMAL_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_optimal_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_STREAM_LIVE_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_STREAM_LIVE_STATUS +int8_t uniffi_church_core_fn_func_get_stream_live_status(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_WEBSITE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_WEBSITE_URL +RustBuffer uniffi_church_core_fn_func_get_website_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_YOUTUBE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_YOUTUBE_URL +RustBuffer uniffi_church_core_fn_func_get_youtube_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_IS_MULTI_DAY_EVENT_CHECK +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_IS_MULTI_DAY_EVENT_CHECK +int8_t uniffi_church_core_fn_func_is_multi_day_event_check(RustBuffer date, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_bible_verse_from_json(RustBuffer verse_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BULLETINS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BULLETINS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_bulletins_from_json(RustBuffer bulletins_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CALENDAR_EVENT_DATA +RustBuffer uniffi_church_core_fn_func_parse_calendar_event_data(RustBuffer calendar_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_contact_result_from_json(RustBuffer result_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_EVENTS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_EVENTS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_events_from_json(RustBuffer events_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_SERMONS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_SERMONS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_sermons_from_json(RustBuffer sermons_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_JSON +RustBuffer uniffi_church_core_fn_func_submit_contact_json(RustBuffer name, RustBuffer email, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON +RustBuffer uniffi_church_core_fn_func_submit_contact_v2_json(RustBuffer name, RustBuffer email, RustBuffer subject, RustBuffer message, RustBuffer phone, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +RustBuffer uniffi_church_core_fn_func_submit_contact_v2_json_legacy(RustBuffer first_name, RustBuffer last_name, RustBuffer email, RustBuffer subject, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_CONTACT_FORM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_CONTACT_FORM_JSON +RustBuffer uniffi_church_core_fn_func_validate_contact_form_json(RustBuffer form_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_EMAIL_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_EMAIL_ADDRESS +int8_t uniffi_church_core_fn_func_validate_email_address(RustBuffer email, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_PHONE_NUMBER +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_PHONE_NUMBER +int8_t uniffi_church_core_fn_func_validate_phone_number(RustBuffer phone, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_ALLOC +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_ALLOC +RustBuffer ffi_church_core_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FROM_BYTES +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FROM_BYTES +RustBuffer ffi_church_core_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FREE +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FREE +void ffi_church_core_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_RESERVE +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_RESERVE +RustBuffer ffi_church_core_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U8 +void ffi_church_core_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U8 +void ffi_church_core_rust_future_cancel_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U8 +void ffi_church_core_rust_future_free_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U8 +uint8_t ffi_church_core_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I8 +void ffi_church_core_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I8 +void ffi_church_core_rust_future_cancel_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I8 +void ffi_church_core_rust_future_free_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I8 +int8_t ffi_church_core_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U16 +void ffi_church_core_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U16 +void ffi_church_core_rust_future_cancel_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U16 +void ffi_church_core_rust_future_free_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U16 +uint16_t ffi_church_core_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I16 +void ffi_church_core_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I16 +void ffi_church_core_rust_future_cancel_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I16 +void ffi_church_core_rust_future_free_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I16 +int16_t ffi_church_core_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U32 +void ffi_church_core_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U32 +void ffi_church_core_rust_future_cancel_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U32 +void ffi_church_core_rust_future_free_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U32 +uint32_t ffi_church_core_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I32 +void ffi_church_core_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I32 +void ffi_church_core_rust_future_cancel_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I32 +void ffi_church_core_rust_future_free_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I32 +int32_t ffi_church_core_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U64 +void ffi_church_core_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U64 +void ffi_church_core_rust_future_cancel_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U64 +void ffi_church_core_rust_future_free_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U64 +uint64_t ffi_church_core_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I64 +void ffi_church_core_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I64 +void ffi_church_core_rust_future_cancel_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I64 +void ffi_church_core_rust_future_free_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I64 +int64_t ffi_church_core_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F32 +void ffi_church_core_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F32 +void ffi_church_core_rust_future_cancel_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F32 +void ffi_church_core_rust_future_free_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F32 +float ffi_church_core_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F64 +void ffi_church_core_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F64 +void ffi_church_core_rust_future_cancel_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F64 +void ffi_church_core_rust_future_free_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F64 +double ffi_church_core_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_POINTER +void ffi_church_core_rust_future_poll_pointer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_POINTER +void ffi_church_core_rust_future_cancel_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_POINTER +void ffi_church_core_rust_future_free_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_POINTER +void*_Nonnull ffi_church_core_rust_future_complete_pointer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_RUST_BUFFER +void ffi_church_core_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +void ffi_church_core_rust_future_cancel_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_RUST_BUFFER +void ffi_church_core_rust_future_free_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +RustBuffer ffi_church_core_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_VOID +void ffi_church_core_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_VOID +void ffi_church_core_rust_future_cancel_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_VOID +void ffi_church_core_rust_future_free_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_VOID +void ffi_church_core_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_CALENDAR_EVENT_DATA +uint16_t uniffi_church_core_checksum_func_create_calendar_event_data(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +uint16_t uniffi_church_core_checksum_func_create_sermon_share_items_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_DEVICE_SUPPORTS_AV1 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_DEVICE_SUPPORTS_AV1 +uint16_t uniffi_church_core_checksum_func_device_supports_av1(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_FULL_VERSE_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_FULL_VERSE_TEXT +uint16_t uniffi_church_core_checksum_func_extract_full_verse_text(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +uint16_t uniffi_church_core_checksum_func_extract_scripture_references_string(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +uint16_t uniffi_church_core_checksum_func_extract_stream_url_from_status(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BIBLE_VERSE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_bible_verse_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BULLETINS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BULLETINS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_bulletins_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CACHED_IMAGE_BASE64 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CACHED_IMAGE_BASE64 +uint16_t uniffi_church_core_checksum_func_fetch_cached_image_base64(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CONFIG_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CONFIG_JSON +uint16_t uniffi_church_core_checksum_func_fetch_config_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CURRENT_BULLETIN_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CURRENT_BULLETIN_JSON +uint16_t uniffi_church_core_checksum_func_fetch_current_bulletin_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_EVENTS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_events_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_FEATURED_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_FEATURED_EVENTS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_featured_events_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVE_STREAM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVE_STREAM_JSON +uint16_t uniffi_church_core_checksum_func_fetch_live_stream_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_livestream_archive_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_random_bible_verse_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +uint16_t uniffi_church_core_checksum_func_fetch_scripture_verses_for_sermon_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SERMONS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SERMONS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_sermons_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_STREAM_STATUS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_STREAM_STATUS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_stream_status_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +uint16_t uniffi_church_core_checksum_func_filter_sermons_by_media_type(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +uint16_t uniffi_church_core_checksum_func_format_event_for_display_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +uint16_t uniffi_church_core_checksum_func_format_scripture_text_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_TIME_RANGE_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_TIME_RANGE_STRING +uint16_t uniffi_church_core_checksum_func_format_time_range_string(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_HOME_FEED_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_HOME_FEED_JSON +uint16_t uniffi_church_core_checksum_func_generate_home_feed_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_VERSE_DESCRIPTION +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_VERSE_DESCRIPTION +uint16_t uniffi_church_core_checksum_func_generate_verse_description(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_ABOUT_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_ABOUT_TEXT +uint16_t uniffi_church_core_checksum_func_get_about_text(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_AV1_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_AV1_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_av1_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_BRAND_COLOR +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_BRAND_COLOR +uint16_t uniffi_church_core_checksum_func_get_brand_color(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_ADDRESS +uint16_t uniffi_church_core_checksum_func_get_church_address(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_NAME +uint16_t uniffi_church_core_checksum_func_get_church_name(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_EMAIL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_EMAIL +uint16_t uniffi_church_core_checksum_func_get_contact_email(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_PHONE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_PHONE +uint16_t uniffi_church_core_checksum_func_get_contact_phone(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_COORDINATES +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_COORDINATES +uint16_t uniffi_church_core_checksum_func_get_coordinates(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_DONATION_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_DONATION_URL +uint16_t uniffi_church_core_checksum_func_get_donation_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_FACEBOOK_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_FACEBOOK_URL +uint16_t uniffi_church_core_checksum_func_get_facebook_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_HLS_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_HLS_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_hls_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_INSTAGRAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_INSTAGRAM_URL +uint16_t uniffi_church_core_checksum_func_get_instagram_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_LIVESTREAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_LIVESTREAM_URL +uint16_t uniffi_church_core_checksum_func_get_livestream_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +uint16_t uniffi_church_core_checksum_func_get_media_type_display_name(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_ICON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_ICON +uint16_t uniffi_church_core_checksum_func_get_media_type_icon(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MISSION_STATEMENT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MISSION_STATEMENT +uint16_t uniffi_church_core_checksum_func_get_mission_statement(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_OPTIMAL_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_OPTIMAL_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_optimal_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_STREAM_LIVE_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_STREAM_LIVE_STATUS +uint16_t uniffi_church_core_checksum_func_get_stream_live_status(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_WEBSITE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_WEBSITE_URL +uint16_t uniffi_church_core_checksum_func_get_website_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_YOUTUBE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_YOUTUBE_URL +uint16_t uniffi_church_core_checksum_func_get_youtube_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_IS_MULTI_DAY_EVENT_CHECK +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_IS_MULTI_DAY_EVENT_CHECK +uint16_t uniffi_church_core_checksum_func_is_multi_day_event_check(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_bible_verse_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BULLETINS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BULLETINS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_bulletins_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CALENDAR_EVENT_DATA +uint16_t uniffi_church_core_checksum_func_parse_calendar_event_data(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_contact_result_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_EVENTS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_EVENTS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_events_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_SERMONS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_SERMONS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_sermons_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_JSON +uint16_t uniffi_church_core_checksum_func_submit_contact_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON +uint16_t uniffi_church_core_checksum_func_submit_contact_v2_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +uint16_t uniffi_church_core_checksum_func_submit_contact_v2_json_legacy(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_CONTACT_FORM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_CONTACT_FORM_JSON +uint16_t uniffi_church_core_checksum_func_validate_contact_form_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_EMAIL_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_EMAIL_ADDRESS +uint16_t uniffi_church_core_checksum_func_validate_email_address(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_PHONE_NUMBER +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_PHONE_NUMBER +uint16_t uniffi_church_core_checksum_func_validate_phone_number(void + +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_UNIFFI_CONTRACT_VERSION +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_UNIFFI_CONTRACT_VERSION +uint32_t ffi_church_core_uniffi_contract_version(void + +); +#endif + diff --git a/ChurchCore.xcframework/ios-arm64-simulator/libchurch_core_sim.a b/ChurchCore.xcframework/ios-arm64-simulator/libchurch_core_sim.a new file mode 100644 index 0000000..755e862 Binary files /dev/null and b/ChurchCore.xcframework/ios-arm64-simulator/libchurch_core_sim.a differ diff --git a/ChurchCore.xcframework/ios-arm64/Headers/church_coreFFI.h b/ChurchCore.xcframework/ios-arm64/Headers/church_coreFFI.h new file mode 100644 index 0000000..7ef029c --- /dev/null +++ b/ChurchCore.xcframework/ios-arm64/Headers/church_coreFFI.h @@ -0,0 +1,1204 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + uint64_t capacity; + uint64_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H +#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +typedef void (*UniffiForeignFutureFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +typedef void (*UniffiCallbackInterfaceFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE +typedef struct UniffiForeignFuture { + uint64_t handle; + UniffiForeignFutureFree _Nonnull free; +} UniffiForeignFuture; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +typedef struct UniffiForeignFutureStructU8 { + uint8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureStructU8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +typedef struct UniffiForeignFutureStructI8 { + int8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureStructI8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +typedef struct UniffiForeignFutureStructU16 { + uint16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureStructU16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +typedef struct UniffiForeignFutureStructI16 { + int16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureStructI16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +typedef struct UniffiForeignFutureStructU32 { + uint32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureStructU32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +typedef struct UniffiForeignFutureStructI32 { + int32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureStructI32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +typedef struct UniffiForeignFutureStructU64 { + uint64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureStructU64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +typedef struct UniffiForeignFutureStructI64 { + int64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureStructI64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +typedef struct UniffiForeignFutureStructF32 { + float returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureStructF32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +typedef struct UniffiForeignFutureStructF64 { + double returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureStructF64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +typedef struct UniffiForeignFutureStructPointer { + void*_Nonnull returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructPointer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +typedef void (*UniffiForeignFutureCompletePointer)(uint64_t, UniffiForeignFutureStructPointer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +typedef struct UniffiForeignFutureStructRustBuffer { + RustBuffer returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructRustBuffer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureStructRustBuffer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +typedef struct UniffiForeignFutureStructVoid { + RustCallStatus callStatus; +} UniffiForeignFutureStructVoid; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid + ); + +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_CALENDAR_EVENT_DATA +RustBuffer uniffi_church_core_fn_func_create_calendar_event_data(RustBuffer event_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +RustBuffer uniffi_church_core_fn_func_create_sermon_share_items_json(RustBuffer title, RustBuffer speaker, RustBuffer video_url, RustBuffer audio_url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_DEVICE_SUPPORTS_AV1 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_DEVICE_SUPPORTS_AV1 +int8_t uniffi_church_core_fn_func_device_supports_av1(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_FULL_VERSE_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_FULL_VERSE_TEXT +RustBuffer uniffi_church_core_fn_func_extract_full_verse_text(RustBuffer verses_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +RustBuffer uniffi_church_core_fn_func_extract_scripture_references_string(RustBuffer scripture_text, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +RustBuffer uniffi_church_core_fn_func_extract_stream_url_from_status(RustBuffer status_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BIBLE_VERSE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_bible_verse_json(RustBuffer query, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BULLETINS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BULLETINS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_bulletins_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CACHED_IMAGE_BASE64 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CACHED_IMAGE_BASE64 +RustBuffer uniffi_church_core_fn_func_fetch_cached_image_base64(RustBuffer url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CONFIG_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CONFIG_JSON +RustBuffer uniffi_church_core_fn_func_fetch_config_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CURRENT_BULLETIN_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CURRENT_BULLETIN_JSON +RustBuffer uniffi_church_core_fn_func_fetch_current_bulletin_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_EVENTS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_events_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_FEATURED_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_FEATURED_EVENTS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_featured_events_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVE_STREAM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVE_STREAM_JSON +RustBuffer uniffi_church_core_fn_func_fetch_live_stream_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_livestream_archive_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_random_bible_verse_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +RustBuffer uniffi_church_core_fn_func_fetch_scripture_verses_for_sermon_json(RustBuffer sermon_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SERMONS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SERMONS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_sermons_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_STREAM_STATUS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_STREAM_STATUS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_stream_status_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +RustBuffer uniffi_church_core_fn_func_filter_sermons_by_media_type(RustBuffer sermons_json, RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +RustBuffer uniffi_church_core_fn_func_format_event_for_display_json(RustBuffer event_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +RustBuffer uniffi_church_core_fn_func_format_scripture_text_json(RustBuffer scripture_text, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_TIME_RANGE_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_TIME_RANGE_STRING +RustBuffer uniffi_church_core_fn_func_format_time_range_string(RustBuffer start_time, RustBuffer end_time, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_HOME_FEED_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_HOME_FEED_JSON +RustBuffer uniffi_church_core_fn_func_generate_home_feed_json(RustBuffer events_json, RustBuffer sermons_json, RustBuffer bulletins_json, RustBuffer verse_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_VERSE_DESCRIPTION +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_VERSE_DESCRIPTION +RustBuffer uniffi_church_core_fn_func_generate_verse_description(RustBuffer verses_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_ABOUT_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_ABOUT_TEXT +RustBuffer uniffi_church_core_fn_func_get_about_text(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_AV1_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_AV1_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_av1_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_BRAND_COLOR +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_BRAND_COLOR +RustBuffer uniffi_church_core_fn_func_get_brand_color(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_ADDRESS +RustBuffer uniffi_church_core_fn_func_get_church_address(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_NAME +RustBuffer uniffi_church_core_fn_func_get_church_name(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_EMAIL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_EMAIL +RustBuffer uniffi_church_core_fn_func_get_contact_email(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_PHONE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_PHONE +RustBuffer uniffi_church_core_fn_func_get_contact_phone(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_COORDINATES +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_COORDINATES +RustBuffer uniffi_church_core_fn_func_get_coordinates(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_DONATION_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_DONATION_URL +RustBuffer uniffi_church_core_fn_func_get_donation_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_FACEBOOK_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_FACEBOOK_URL +RustBuffer uniffi_church_core_fn_func_get_facebook_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_HLS_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_HLS_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_hls_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_INSTAGRAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_INSTAGRAM_URL +RustBuffer uniffi_church_core_fn_func_get_instagram_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_LIVESTREAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_LIVESTREAM_URL +RustBuffer uniffi_church_core_fn_func_get_livestream_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +RustBuffer uniffi_church_core_fn_func_get_media_type_display_name(RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_ICON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_ICON +RustBuffer uniffi_church_core_fn_func_get_media_type_icon(RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MISSION_STATEMENT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MISSION_STATEMENT +RustBuffer uniffi_church_core_fn_func_get_mission_statement(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_OPTIMAL_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_OPTIMAL_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_optimal_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_STREAM_LIVE_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_STREAM_LIVE_STATUS +int8_t uniffi_church_core_fn_func_get_stream_live_status(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_WEBSITE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_WEBSITE_URL +RustBuffer uniffi_church_core_fn_func_get_website_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_YOUTUBE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_YOUTUBE_URL +RustBuffer uniffi_church_core_fn_func_get_youtube_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_IS_MULTI_DAY_EVENT_CHECK +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_IS_MULTI_DAY_EVENT_CHECK +int8_t uniffi_church_core_fn_func_is_multi_day_event_check(RustBuffer date, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_bible_verse_from_json(RustBuffer verse_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BULLETINS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BULLETINS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_bulletins_from_json(RustBuffer bulletins_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CALENDAR_EVENT_DATA +RustBuffer uniffi_church_core_fn_func_parse_calendar_event_data(RustBuffer calendar_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_contact_result_from_json(RustBuffer result_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_EVENTS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_EVENTS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_events_from_json(RustBuffer events_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_SERMONS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_SERMONS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_sermons_from_json(RustBuffer sermons_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_JSON +RustBuffer uniffi_church_core_fn_func_submit_contact_json(RustBuffer name, RustBuffer email, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON +RustBuffer uniffi_church_core_fn_func_submit_contact_v2_json(RustBuffer name, RustBuffer email, RustBuffer subject, RustBuffer message, RustBuffer phone, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +RustBuffer uniffi_church_core_fn_func_submit_contact_v2_json_legacy(RustBuffer first_name, RustBuffer last_name, RustBuffer email, RustBuffer subject, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_CONTACT_FORM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_CONTACT_FORM_JSON +RustBuffer uniffi_church_core_fn_func_validate_contact_form_json(RustBuffer form_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_EMAIL_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_EMAIL_ADDRESS +int8_t uniffi_church_core_fn_func_validate_email_address(RustBuffer email, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_PHONE_NUMBER +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_PHONE_NUMBER +int8_t uniffi_church_core_fn_func_validate_phone_number(RustBuffer phone, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_ALLOC +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_ALLOC +RustBuffer ffi_church_core_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FROM_BYTES +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FROM_BYTES +RustBuffer ffi_church_core_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FREE +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FREE +void ffi_church_core_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_RESERVE +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_RESERVE +RustBuffer ffi_church_core_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U8 +void ffi_church_core_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U8 +void ffi_church_core_rust_future_cancel_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U8 +void ffi_church_core_rust_future_free_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U8 +uint8_t ffi_church_core_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I8 +void ffi_church_core_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I8 +void ffi_church_core_rust_future_cancel_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I8 +void ffi_church_core_rust_future_free_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I8 +int8_t ffi_church_core_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U16 +void ffi_church_core_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U16 +void ffi_church_core_rust_future_cancel_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U16 +void ffi_church_core_rust_future_free_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U16 +uint16_t ffi_church_core_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I16 +void ffi_church_core_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I16 +void ffi_church_core_rust_future_cancel_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I16 +void ffi_church_core_rust_future_free_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I16 +int16_t ffi_church_core_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U32 +void ffi_church_core_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U32 +void ffi_church_core_rust_future_cancel_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U32 +void ffi_church_core_rust_future_free_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U32 +uint32_t ffi_church_core_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I32 +void ffi_church_core_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I32 +void ffi_church_core_rust_future_cancel_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I32 +void ffi_church_core_rust_future_free_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I32 +int32_t ffi_church_core_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U64 +void ffi_church_core_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U64 +void ffi_church_core_rust_future_cancel_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U64 +void ffi_church_core_rust_future_free_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U64 +uint64_t ffi_church_core_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I64 +void ffi_church_core_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I64 +void ffi_church_core_rust_future_cancel_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I64 +void ffi_church_core_rust_future_free_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I64 +int64_t ffi_church_core_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F32 +void ffi_church_core_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F32 +void ffi_church_core_rust_future_cancel_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F32 +void ffi_church_core_rust_future_free_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F32 +float ffi_church_core_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F64 +void ffi_church_core_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F64 +void ffi_church_core_rust_future_cancel_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F64 +void ffi_church_core_rust_future_free_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F64 +double ffi_church_core_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_POINTER +void ffi_church_core_rust_future_poll_pointer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_POINTER +void ffi_church_core_rust_future_cancel_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_POINTER +void ffi_church_core_rust_future_free_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_POINTER +void*_Nonnull ffi_church_core_rust_future_complete_pointer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_RUST_BUFFER +void ffi_church_core_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +void ffi_church_core_rust_future_cancel_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_RUST_BUFFER +void ffi_church_core_rust_future_free_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +RustBuffer ffi_church_core_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_VOID +void ffi_church_core_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_VOID +void ffi_church_core_rust_future_cancel_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_VOID +void ffi_church_core_rust_future_free_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_VOID +void ffi_church_core_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_CALENDAR_EVENT_DATA +uint16_t uniffi_church_core_checksum_func_create_calendar_event_data(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +uint16_t uniffi_church_core_checksum_func_create_sermon_share_items_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_DEVICE_SUPPORTS_AV1 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_DEVICE_SUPPORTS_AV1 +uint16_t uniffi_church_core_checksum_func_device_supports_av1(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_FULL_VERSE_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_FULL_VERSE_TEXT +uint16_t uniffi_church_core_checksum_func_extract_full_verse_text(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +uint16_t uniffi_church_core_checksum_func_extract_scripture_references_string(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +uint16_t uniffi_church_core_checksum_func_extract_stream_url_from_status(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BIBLE_VERSE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_bible_verse_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BULLETINS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BULLETINS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_bulletins_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CACHED_IMAGE_BASE64 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CACHED_IMAGE_BASE64 +uint16_t uniffi_church_core_checksum_func_fetch_cached_image_base64(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CONFIG_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CONFIG_JSON +uint16_t uniffi_church_core_checksum_func_fetch_config_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CURRENT_BULLETIN_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CURRENT_BULLETIN_JSON +uint16_t uniffi_church_core_checksum_func_fetch_current_bulletin_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_EVENTS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_events_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_FEATURED_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_FEATURED_EVENTS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_featured_events_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVE_STREAM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVE_STREAM_JSON +uint16_t uniffi_church_core_checksum_func_fetch_live_stream_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_livestream_archive_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_random_bible_verse_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +uint16_t uniffi_church_core_checksum_func_fetch_scripture_verses_for_sermon_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SERMONS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SERMONS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_sermons_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_STREAM_STATUS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_STREAM_STATUS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_stream_status_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +uint16_t uniffi_church_core_checksum_func_filter_sermons_by_media_type(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +uint16_t uniffi_church_core_checksum_func_format_event_for_display_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +uint16_t uniffi_church_core_checksum_func_format_scripture_text_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_TIME_RANGE_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_TIME_RANGE_STRING +uint16_t uniffi_church_core_checksum_func_format_time_range_string(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_HOME_FEED_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_HOME_FEED_JSON +uint16_t uniffi_church_core_checksum_func_generate_home_feed_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_VERSE_DESCRIPTION +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_VERSE_DESCRIPTION +uint16_t uniffi_church_core_checksum_func_generate_verse_description(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_ABOUT_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_ABOUT_TEXT +uint16_t uniffi_church_core_checksum_func_get_about_text(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_AV1_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_AV1_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_av1_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_BRAND_COLOR +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_BRAND_COLOR +uint16_t uniffi_church_core_checksum_func_get_brand_color(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_ADDRESS +uint16_t uniffi_church_core_checksum_func_get_church_address(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_NAME +uint16_t uniffi_church_core_checksum_func_get_church_name(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_EMAIL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_EMAIL +uint16_t uniffi_church_core_checksum_func_get_contact_email(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_PHONE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_PHONE +uint16_t uniffi_church_core_checksum_func_get_contact_phone(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_COORDINATES +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_COORDINATES +uint16_t uniffi_church_core_checksum_func_get_coordinates(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_DONATION_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_DONATION_URL +uint16_t uniffi_church_core_checksum_func_get_donation_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_FACEBOOK_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_FACEBOOK_URL +uint16_t uniffi_church_core_checksum_func_get_facebook_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_HLS_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_HLS_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_hls_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_INSTAGRAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_INSTAGRAM_URL +uint16_t uniffi_church_core_checksum_func_get_instagram_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_LIVESTREAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_LIVESTREAM_URL +uint16_t uniffi_church_core_checksum_func_get_livestream_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +uint16_t uniffi_church_core_checksum_func_get_media_type_display_name(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_ICON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_ICON +uint16_t uniffi_church_core_checksum_func_get_media_type_icon(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MISSION_STATEMENT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MISSION_STATEMENT +uint16_t uniffi_church_core_checksum_func_get_mission_statement(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_OPTIMAL_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_OPTIMAL_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_optimal_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_STREAM_LIVE_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_STREAM_LIVE_STATUS +uint16_t uniffi_church_core_checksum_func_get_stream_live_status(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_WEBSITE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_WEBSITE_URL +uint16_t uniffi_church_core_checksum_func_get_website_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_YOUTUBE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_YOUTUBE_URL +uint16_t uniffi_church_core_checksum_func_get_youtube_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_IS_MULTI_DAY_EVENT_CHECK +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_IS_MULTI_DAY_EVENT_CHECK +uint16_t uniffi_church_core_checksum_func_is_multi_day_event_check(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_bible_verse_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BULLETINS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BULLETINS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_bulletins_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CALENDAR_EVENT_DATA +uint16_t uniffi_church_core_checksum_func_parse_calendar_event_data(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_contact_result_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_EVENTS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_EVENTS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_events_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_SERMONS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_SERMONS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_sermons_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_JSON +uint16_t uniffi_church_core_checksum_func_submit_contact_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON +uint16_t uniffi_church_core_checksum_func_submit_contact_v2_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +uint16_t uniffi_church_core_checksum_func_submit_contact_v2_json_legacy(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_CONTACT_FORM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_CONTACT_FORM_JSON +uint16_t uniffi_church_core_checksum_func_validate_contact_form_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_EMAIL_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_EMAIL_ADDRESS +uint16_t uniffi_church_core_checksum_func_validate_email_address(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_PHONE_NUMBER +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_PHONE_NUMBER +uint16_t uniffi_church_core_checksum_func_validate_phone_number(void + +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_UNIFFI_CONTRACT_VERSION +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_UNIFFI_CONTRACT_VERSION +uint32_t ffi_church_core_uniffi_contract_version(void + +); +#endif + diff --git a/ChurchCore.xcframework/ios-arm64/libchurch_core_device.a b/ChurchCore.xcframework/ios-arm64/libchurch_core_device.a new file mode 100644 index 0000000..49a63d8 Binary files /dev/null and b/ChurchCore.xcframework/ios-arm64/libchurch_core_device.a differ diff --git a/ChurchCore.xcframework/ios-arm64_x86_64-maccatalyst/Headers/church_coreFFI.h b/ChurchCore.xcframework/ios-arm64_x86_64-maccatalyst/Headers/church_coreFFI.h new file mode 100644 index 0000000..7ef029c --- /dev/null +++ b/ChurchCore.xcframework/ios-arm64_x86_64-maccatalyst/Headers/church_coreFFI.h @@ -0,0 +1,1204 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + uint64_t capacity; + uint64_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H +#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +typedef void (*UniffiForeignFutureFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +typedef void (*UniffiCallbackInterfaceFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE +typedef struct UniffiForeignFuture { + uint64_t handle; + UniffiForeignFutureFree _Nonnull free; +} UniffiForeignFuture; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +typedef struct UniffiForeignFutureStructU8 { + uint8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureStructU8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +typedef struct UniffiForeignFutureStructI8 { + int8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureStructI8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +typedef struct UniffiForeignFutureStructU16 { + uint16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureStructU16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +typedef struct UniffiForeignFutureStructI16 { + int16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureStructI16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +typedef struct UniffiForeignFutureStructU32 { + uint32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureStructU32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +typedef struct UniffiForeignFutureStructI32 { + int32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureStructI32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +typedef struct UniffiForeignFutureStructU64 { + uint64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureStructU64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +typedef struct UniffiForeignFutureStructI64 { + int64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureStructI64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +typedef struct UniffiForeignFutureStructF32 { + float returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureStructF32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +typedef struct UniffiForeignFutureStructF64 { + double returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureStructF64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +typedef struct UniffiForeignFutureStructPointer { + void*_Nonnull returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructPointer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +typedef void (*UniffiForeignFutureCompletePointer)(uint64_t, UniffiForeignFutureStructPointer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +typedef struct UniffiForeignFutureStructRustBuffer { + RustBuffer returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructRustBuffer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureStructRustBuffer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +typedef struct UniffiForeignFutureStructVoid { + RustCallStatus callStatus; +} UniffiForeignFutureStructVoid; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid + ); + +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_CALENDAR_EVENT_DATA +RustBuffer uniffi_church_core_fn_func_create_calendar_event_data(RustBuffer event_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +RustBuffer uniffi_church_core_fn_func_create_sermon_share_items_json(RustBuffer title, RustBuffer speaker, RustBuffer video_url, RustBuffer audio_url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_DEVICE_SUPPORTS_AV1 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_DEVICE_SUPPORTS_AV1 +int8_t uniffi_church_core_fn_func_device_supports_av1(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_FULL_VERSE_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_FULL_VERSE_TEXT +RustBuffer uniffi_church_core_fn_func_extract_full_verse_text(RustBuffer verses_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +RustBuffer uniffi_church_core_fn_func_extract_scripture_references_string(RustBuffer scripture_text, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +RustBuffer uniffi_church_core_fn_func_extract_stream_url_from_status(RustBuffer status_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BIBLE_VERSE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_bible_verse_json(RustBuffer query, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BULLETINS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BULLETINS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_bulletins_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CACHED_IMAGE_BASE64 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CACHED_IMAGE_BASE64 +RustBuffer uniffi_church_core_fn_func_fetch_cached_image_base64(RustBuffer url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CONFIG_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CONFIG_JSON +RustBuffer uniffi_church_core_fn_func_fetch_config_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CURRENT_BULLETIN_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CURRENT_BULLETIN_JSON +RustBuffer uniffi_church_core_fn_func_fetch_current_bulletin_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_EVENTS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_events_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_FEATURED_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_FEATURED_EVENTS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_featured_events_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVE_STREAM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVE_STREAM_JSON +RustBuffer uniffi_church_core_fn_func_fetch_live_stream_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_livestream_archive_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_random_bible_verse_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +RustBuffer uniffi_church_core_fn_func_fetch_scripture_verses_for_sermon_json(RustBuffer sermon_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SERMONS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SERMONS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_sermons_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_STREAM_STATUS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_STREAM_STATUS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_stream_status_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +RustBuffer uniffi_church_core_fn_func_filter_sermons_by_media_type(RustBuffer sermons_json, RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +RustBuffer uniffi_church_core_fn_func_format_event_for_display_json(RustBuffer event_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +RustBuffer uniffi_church_core_fn_func_format_scripture_text_json(RustBuffer scripture_text, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_TIME_RANGE_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_TIME_RANGE_STRING +RustBuffer uniffi_church_core_fn_func_format_time_range_string(RustBuffer start_time, RustBuffer end_time, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_HOME_FEED_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_HOME_FEED_JSON +RustBuffer uniffi_church_core_fn_func_generate_home_feed_json(RustBuffer events_json, RustBuffer sermons_json, RustBuffer bulletins_json, RustBuffer verse_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_VERSE_DESCRIPTION +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_VERSE_DESCRIPTION +RustBuffer uniffi_church_core_fn_func_generate_verse_description(RustBuffer verses_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_ABOUT_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_ABOUT_TEXT +RustBuffer uniffi_church_core_fn_func_get_about_text(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_AV1_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_AV1_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_av1_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_BRAND_COLOR +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_BRAND_COLOR +RustBuffer uniffi_church_core_fn_func_get_brand_color(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_ADDRESS +RustBuffer uniffi_church_core_fn_func_get_church_address(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_NAME +RustBuffer uniffi_church_core_fn_func_get_church_name(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_EMAIL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_EMAIL +RustBuffer uniffi_church_core_fn_func_get_contact_email(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_PHONE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_PHONE +RustBuffer uniffi_church_core_fn_func_get_contact_phone(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_COORDINATES +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_COORDINATES +RustBuffer uniffi_church_core_fn_func_get_coordinates(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_DONATION_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_DONATION_URL +RustBuffer uniffi_church_core_fn_func_get_donation_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_FACEBOOK_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_FACEBOOK_URL +RustBuffer uniffi_church_core_fn_func_get_facebook_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_HLS_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_HLS_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_hls_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_INSTAGRAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_INSTAGRAM_URL +RustBuffer uniffi_church_core_fn_func_get_instagram_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_LIVESTREAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_LIVESTREAM_URL +RustBuffer uniffi_church_core_fn_func_get_livestream_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +RustBuffer uniffi_church_core_fn_func_get_media_type_display_name(RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_ICON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_ICON +RustBuffer uniffi_church_core_fn_func_get_media_type_icon(RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MISSION_STATEMENT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MISSION_STATEMENT +RustBuffer uniffi_church_core_fn_func_get_mission_statement(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_OPTIMAL_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_OPTIMAL_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_optimal_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_STREAM_LIVE_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_STREAM_LIVE_STATUS +int8_t uniffi_church_core_fn_func_get_stream_live_status(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_WEBSITE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_WEBSITE_URL +RustBuffer uniffi_church_core_fn_func_get_website_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_YOUTUBE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_YOUTUBE_URL +RustBuffer uniffi_church_core_fn_func_get_youtube_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_IS_MULTI_DAY_EVENT_CHECK +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_IS_MULTI_DAY_EVENT_CHECK +int8_t uniffi_church_core_fn_func_is_multi_day_event_check(RustBuffer date, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_bible_verse_from_json(RustBuffer verse_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BULLETINS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BULLETINS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_bulletins_from_json(RustBuffer bulletins_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CALENDAR_EVENT_DATA +RustBuffer uniffi_church_core_fn_func_parse_calendar_event_data(RustBuffer calendar_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_contact_result_from_json(RustBuffer result_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_EVENTS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_EVENTS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_events_from_json(RustBuffer events_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_SERMONS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_SERMONS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_sermons_from_json(RustBuffer sermons_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_JSON +RustBuffer uniffi_church_core_fn_func_submit_contact_json(RustBuffer name, RustBuffer email, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON +RustBuffer uniffi_church_core_fn_func_submit_contact_v2_json(RustBuffer name, RustBuffer email, RustBuffer subject, RustBuffer message, RustBuffer phone, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +RustBuffer uniffi_church_core_fn_func_submit_contact_v2_json_legacy(RustBuffer first_name, RustBuffer last_name, RustBuffer email, RustBuffer subject, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_CONTACT_FORM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_CONTACT_FORM_JSON +RustBuffer uniffi_church_core_fn_func_validate_contact_form_json(RustBuffer form_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_EMAIL_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_EMAIL_ADDRESS +int8_t uniffi_church_core_fn_func_validate_email_address(RustBuffer email, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_PHONE_NUMBER +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_PHONE_NUMBER +int8_t uniffi_church_core_fn_func_validate_phone_number(RustBuffer phone, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_ALLOC +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_ALLOC +RustBuffer ffi_church_core_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FROM_BYTES +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FROM_BYTES +RustBuffer ffi_church_core_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FREE +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FREE +void ffi_church_core_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_RESERVE +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_RESERVE +RustBuffer ffi_church_core_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U8 +void ffi_church_core_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U8 +void ffi_church_core_rust_future_cancel_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U8 +void ffi_church_core_rust_future_free_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U8 +uint8_t ffi_church_core_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I8 +void ffi_church_core_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I8 +void ffi_church_core_rust_future_cancel_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I8 +void ffi_church_core_rust_future_free_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I8 +int8_t ffi_church_core_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U16 +void ffi_church_core_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U16 +void ffi_church_core_rust_future_cancel_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U16 +void ffi_church_core_rust_future_free_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U16 +uint16_t ffi_church_core_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I16 +void ffi_church_core_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I16 +void ffi_church_core_rust_future_cancel_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I16 +void ffi_church_core_rust_future_free_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I16 +int16_t ffi_church_core_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U32 +void ffi_church_core_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U32 +void ffi_church_core_rust_future_cancel_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U32 +void ffi_church_core_rust_future_free_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U32 +uint32_t ffi_church_core_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I32 +void ffi_church_core_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I32 +void ffi_church_core_rust_future_cancel_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I32 +void ffi_church_core_rust_future_free_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I32 +int32_t ffi_church_core_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U64 +void ffi_church_core_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U64 +void ffi_church_core_rust_future_cancel_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U64 +void ffi_church_core_rust_future_free_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U64 +uint64_t ffi_church_core_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I64 +void ffi_church_core_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I64 +void ffi_church_core_rust_future_cancel_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I64 +void ffi_church_core_rust_future_free_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I64 +int64_t ffi_church_core_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F32 +void ffi_church_core_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F32 +void ffi_church_core_rust_future_cancel_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F32 +void ffi_church_core_rust_future_free_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F32 +float ffi_church_core_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F64 +void ffi_church_core_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F64 +void ffi_church_core_rust_future_cancel_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F64 +void ffi_church_core_rust_future_free_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F64 +double ffi_church_core_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_POINTER +void ffi_church_core_rust_future_poll_pointer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_POINTER +void ffi_church_core_rust_future_cancel_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_POINTER +void ffi_church_core_rust_future_free_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_POINTER +void*_Nonnull ffi_church_core_rust_future_complete_pointer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_RUST_BUFFER +void ffi_church_core_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +void ffi_church_core_rust_future_cancel_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_RUST_BUFFER +void ffi_church_core_rust_future_free_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +RustBuffer ffi_church_core_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_VOID +void ffi_church_core_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_VOID +void ffi_church_core_rust_future_cancel_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_VOID +void ffi_church_core_rust_future_free_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_VOID +void ffi_church_core_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_CALENDAR_EVENT_DATA +uint16_t uniffi_church_core_checksum_func_create_calendar_event_data(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +uint16_t uniffi_church_core_checksum_func_create_sermon_share_items_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_DEVICE_SUPPORTS_AV1 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_DEVICE_SUPPORTS_AV1 +uint16_t uniffi_church_core_checksum_func_device_supports_av1(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_FULL_VERSE_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_FULL_VERSE_TEXT +uint16_t uniffi_church_core_checksum_func_extract_full_verse_text(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +uint16_t uniffi_church_core_checksum_func_extract_scripture_references_string(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +uint16_t uniffi_church_core_checksum_func_extract_stream_url_from_status(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BIBLE_VERSE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_bible_verse_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BULLETINS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BULLETINS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_bulletins_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CACHED_IMAGE_BASE64 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CACHED_IMAGE_BASE64 +uint16_t uniffi_church_core_checksum_func_fetch_cached_image_base64(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CONFIG_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CONFIG_JSON +uint16_t uniffi_church_core_checksum_func_fetch_config_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CURRENT_BULLETIN_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CURRENT_BULLETIN_JSON +uint16_t uniffi_church_core_checksum_func_fetch_current_bulletin_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_EVENTS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_events_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_FEATURED_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_FEATURED_EVENTS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_featured_events_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVE_STREAM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVE_STREAM_JSON +uint16_t uniffi_church_core_checksum_func_fetch_live_stream_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_livestream_archive_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_random_bible_verse_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +uint16_t uniffi_church_core_checksum_func_fetch_scripture_verses_for_sermon_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SERMONS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SERMONS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_sermons_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_STREAM_STATUS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_STREAM_STATUS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_stream_status_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +uint16_t uniffi_church_core_checksum_func_filter_sermons_by_media_type(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +uint16_t uniffi_church_core_checksum_func_format_event_for_display_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +uint16_t uniffi_church_core_checksum_func_format_scripture_text_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_TIME_RANGE_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_TIME_RANGE_STRING +uint16_t uniffi_church_core_checksum_func_format_time_range_string(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_HOME_FEED_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_HOME_FEED_JSON +uint16_t uniffi_church_core_checksum_func_generate_home_feed_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_VERSE_DESCRIPTION +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_VERSE_DESCRIPTION +uint16_t uniffi_church_core_checksum_func_generate_verse_description(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_ABOUT_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_ABOUT_TEXT +uint16_t uniffi_church_core_checksum_func_get_about_text(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_AV1_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_AV1_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_av1_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_BRAND_COLOR +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_BRAND_COLOR +uint16_t uniffi_church_core_checksum_func_get_brand_color(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_ADDRESS +uint16_t uniffi_church_core_checksum_func_get_church_address(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_NAME +uint16_t uniffi_church_core_checksum_func_get_church_name(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_EMAIL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_EMAIL +uint16_t uniffi_church_core_checksum_func_get_contact_email(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_PHONE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_PHONE +uint16_t uniffi_church_core_checksum_func_get_contact_phone(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_COORDINATES +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_COORDINATES +uint16_t uniffi_church_core_checksum_func_get_coordinates(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_DONATION_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_DONATION_URL +uint16_t uniffi_church_core_checksum_func_get_donation_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_FACEBOOK_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_FACEBOOK_URL +uint16_t uniffi_church_core_checksum_func_get_facebook_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_HLS_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_HLS_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_hls_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_INSTAGRAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_INSTAGRAM_URL +uint16_t uniffi_church_core_checksum_func_get_instagram_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_LIVESTREAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_LIVESTREAM_URL +uint16_t uniffi_church_core_checksum_func_get_livestream_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +uint16_t uniffi_church_core_checksum_func_get_media_type_display_name(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_ICON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_ICON +uint16_t uniffi_church_core_checksum_func_get_media_type_icon(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MISSION_STATEMENT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MISSION_STATEMENT +uint16_t uniffi_church_core_checksum_func_get_mission_statement(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_OPTIMAL_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_OPTIMAL_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_optimal_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_STREAM_LIVE_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_STREAM_LIVE_STATUS +uint16_t uniffi_church_core_checksum_func_get_stream_live_status(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_WEBSITE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_WEBSITE_URL +uint16_t uniffi_church_core_checksum_func_get_website_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_YOUTUBE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_YOUTUBE_URL +uint16_t uniffi_church_core_checksum_func_get_youtube_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_IS_MULTI_DAY_EVENT_CHECK +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_IS_MULTI_DAY_EVENT_CHECK +uint16_t uniffi_church_core_checksum_func_is_multi_day_event_check(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_bible_verse_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BULLETINS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BULLETINS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_bulletins_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CALENDAR_EVENT_DATA +uint16_t uniffi_church_core_checksum_func_parse_calendar_event_data(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_contact_result_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_EVENTS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_EVENTS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_events_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_SERMONS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_SERMONS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_sermons_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_JSON +uint16_t uniffi_church_core_checksum_func_submit_contact_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON +uint16_t uniffi_church_core_checksum_func_submit_contact_v2_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +uint16_t uniffi_church_core_checksum_func_submit_contact_v2_json_legacy(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_CONTACT_FORM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_CONTACT_FORM_JSON +uint16_t uniffi_church_core_checksum_func_validate_contact_form_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_EMAIL_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_EMAIL_ADDRESS +uint16_t uniffi_church_core_checksum_func_validate_email_address(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_PHONE_NUMBER +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_PHONE_NUMBER +uint16_t uniffi_church_core_checksum_func_validate_phone_number(void + +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_UNIFFI_CONTRACT_VERSION +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_UNIFFI_CONTRACT_VERSION +uint32_t ffi_church_core_uniffi_contract_version(void + +); +#endif + diff --git a/ChurchCore.xcframework/ios-arm64_x86_64-maccatalyst/libchurch_core_mac_catalyst.a b/ChurchCore.xcframework/ios-arm64_x86_64-maccatalyst/libchurch_core_mac_catalyst.a new file mode 100644 index 0000000..c377db6 Binary files /dev/null and b/ChurchCore.xcframework/ios-arm64_x86_64-maccatalyst/libchurch_core_mac_catalyst.a differ diff --git a/ChurchCore.xcframework/macos-arm64_x86_64/Headers/church_coreFFI.h b/ChurchCore.xcframework/macos-arm64_x86_64/Headers/church_coreFFI.h new file mode 100644 index 0000000..c592825 --- /dev/null +++ b/ChurchCore.xcframework/macos-arm64_x86_64/Headers/church_coreFFI.h @@ -0,0 +1,914 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + uint64_t capacity; + uint64_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H +#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +typedef void (*UniffiForeignFutureFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +typedef void (*UniffiCallbackInterfaceFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE +typedef struct UniffiForeignFuture { + uint64_t handle; + UniffiForeignFutureFree _Nonnull free; +} UniffiForeignFuture; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +typedef struct UniffiForeignFutureStructU8 { + uint8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureStructU8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +typedef struct UniffiForeignFutureStructI8 { + int8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureStructI8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +typedef struct UniffiForeignFutureStructU16 { + uint16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureStructU16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +typedef struct UniffiForeignFutureStructI16 { + int16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureStructI16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +typedef struct UniffiForeignFutureStructU32 { + uint32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureStructU32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +typedef struct UniffiForeignFutureStructI32 { + int32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureStructI32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +typedef struct UniffiForeignFutureStructU64 { + uint64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureStructU64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +typedef struct UniffiForeignFutureStructI64 { + int64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureStructI64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +typedef struct UniffiForeignFutureStructF32 { + float returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureStructF32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +typedef struct UniffiForeignFutureStructF64 { + double returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureStructF64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +typedef struct UniffiForeignFutureStructPointer { + void*_Nonnull returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructPointer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +typedef void (*UniffiForeignFutureCompletePointer)(uint64_t, UniffiForeignFutureStructPointer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +typedef struct UniffiForeignFutureStructRustBuffer { + RustBuffer returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructRustBuffer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureStructRustBuffer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +typedef struct UniffiForeignFutureStructVoid { + RustCallStatus callStatus; +} UniffiForeignFutureStructVoid; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid + ); + +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +RustBuffer uniffi_church_core_fn_func_create_sermon_share_items_json(RustBuffer title, RustBuffer speaker, RustBuffer video_url, RustBuffer audio_url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_DEVICE_SUPPORTS_AV1 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_DEVICE_SUPPORTS_AV1 +int8_t uniffi_church_core_fn_func_device_supports_av1(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +RustBuffer uniffi_church_core_fn_func_extract_scripture_references_string(RustBuffer scripture_text, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BIBLE_VERSE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_bible_verse_json(RustBuffer query, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BULLETINS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BULLETINS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_bulletins_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CACHED_IMAGE_BASE64 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CACHED_IMAGE_BASE64 +RustBuffer uniffi_church_core_fn_func_fetch_cached_image_base64(RustBuffer url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CONFIG_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CONFIG_JSON +RustBuffer uniffi_church_core_fn_func_fetch_config_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CURRENT_BULLETIN_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CURRENT_BULLETIN_JSON +RustBuffer uniffi_church_core_fn_func_fetch_current_bulletin_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_EVENTS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_events_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_FEATURED_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_FEATURED_EVENTS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_featured_events_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVE_STREAM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVE_STREAM_JSON +RustBuffer uniffi_church_core_fn_func_fetch_live_stream_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_livestream_archive_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_random_bible_verse_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +RustBuffer uniffi_church_core_fn_func_fetch_scripture_verses_for_sermon_json(RustBuffer sermon_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SERMONS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SERMONS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_sermons_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_STREAM_STATUS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_STREAM_STATUS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_stream_status_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +RustBuffer uniffi_church_core_fn_func_filter_sermons_by_media_type(RustBuffer sermons_json, RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +RustBuffer uniffi_church_core_fn_func_format_event_for_display_json(RustBuffer event_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +RustBuffer uniffi_church_core_fn_func_format_scripture_text_json(RustBuffer scripture_text, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_TIME_RANGE_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_TIME_RANGE_STRING +RustBuffer uniffi_church_core_fn_func_format_time_range_string(RustBuffer start_time, RustBuffer end_time, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_HOME_FEED_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_HOME_FEED_JSON +RustBuffer uniffi_church_core_fn_func_generate_home_feed_json(RustBuffer events_json, RustBuffer sermons_json, RustBuffer bulletins_json, RustBuffer verse_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_AV1_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_AV1_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_av1_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_HLS_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_HLS_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_hls_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +RustBuffer uniffi_church_core_fn_func_get_media_type_display_name(RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_ICON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_ICON +RustBuffer uniffi_church_core_fn_func_get_media_type_icon(RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_OPTIMAL_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_OPTIMAL_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_optimal_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_IS_MULTI_DAY_EVENT_CHECK +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_IS_MULTI_DAY_EVENT_CHECK +int8_t uniffi_church_core_fn_func_is_multi_day_event_check(RustBuffer date, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_JSON +RustBuffer uniffi_church_core_fn_func_submit_contact_json(RustBuffer name, RustBuffer email, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON +RustBuffer uniffi_church_core_fn_func_submit_contact_v2_json(RustBuffer name, RustBuffer email, RustBuffer subject, RustBuffer message, RustBuffer phone, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +RustBuffer uniffi_church_core_fn_func_submit_contact_v2_json_legacy(RustBuffer first_name, RustBuffer last_name, RustBuffer email, RustBuffer subject, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_CONTACT_FORM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_CONTACT_FORM_JSON +RustBuffer uniffi_church_core_fn_func_validate_contact_form_json(RustBuffer form_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_EMAIL_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_EMAIL_ADDRESS +int8_t uniffi_church_core_fn_func_validate_email_address(RustBuffer email, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_PHONE_NUMBER +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_PHONE_NUMBER +int8_t uniffi_church_core_fn_func_validate_phone_number(RustBuffer phone, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_ALLOC +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_ALLOC +RustBuffer ffi_church_core_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FROM_BYTES +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FROM_BYTES +RustBuffer ffi_church_core_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FREE +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FREE +void ffi_church_core_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_RESERVE +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_RESERVE +RustBuffer ffi_church_core_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U8 +void ffi_church_core_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U8 +void ffi_church_core_rust_future_cancel_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U8 +void ffi_church_core_rust_future_free_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U8 +uint8_t ffi_church_core_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I8 +void ffi_church_core_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I8 +void ffi_church_core_rust_future_cancel_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I8 +void ffi_church_core_rust_future_free_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I8 +int8_t ffi_church_core_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U16 +void ffi_church_core_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U16 +void ffi_church_core_rust_future_cancel_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U16 +void ffi_church_core_rust_future_free_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U16 +uint16_t ffi_church_core_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I16 +void ffi_church_core_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I16 +void ffi_church_core_rust_future_cancel_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I16 +void ffi_church_core_rust_future_free_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I16 +int16_t ffi_church_core_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U32 +void ffi_church_core_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U32 +void ffi_church_core_rust_future_cancel_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U32 +void ffi_church_core_rust_future_free_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U32 +uint32_t ffi_church_core_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I32 +void ffi_church_core_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I32 +void ffi_church_core_rust_future_cancel_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I32 +void ffi_church_core_rust_future_free_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I32 +int32_t ffi_church_core_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U64 +void ffi_church_core_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U64 +void ffi_church_core_rust_future_cancel_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U64 +void ffi_church_core_rust_future_free_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U64 +uint64_t ffi_church_core_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I64 +void ffi_church_core_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I64 +void ffi_church_core_rust_future_cancel_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I64 +void ffi_church_core_rust_future_free_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I64 +int64_t ffi_church_core_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F32 +void ffi_church_core_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F32 +void ffi_church_core_rust_future_cancel_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F32 +void ffi_church_core_rust_future_free_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F32 +float ffi_church_core_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F64 +void ffi_church_core_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F64 +void ffi_church_core_rust_future_cancel_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F64 +void ffi_church_core_rust_future_free_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F64 +double ffi_church_core_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_POINTER +void ffi_church_core_rust_future_poll_pointer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_POINTER +void ffi_church_core_rust_future_cancel_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_POINTER +void ffi_church_core_rust_future_free_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_POINTER +void*_Nonnull ffi_church_core_rust_future_complete_pointer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_RUST_BUFFER +void ffi_church_core_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +void ffi_church_core_rust_future_cancel_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_RUST_BUFFER +void ffi_church_core_rust_future_free_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +RustBuffer ffi_church_core_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_VOID +void ffi_church_core_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_VOID +void ffi_church_core_rust_future_cancel_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_VOID +void ffi_church_core_rust_future_free_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_VOID +void ffi_church_core_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +uint16_t uniffi_church_core_checksum_func_create_sermon_share_items_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_DEVICE_SUPPORTS_AV1 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_DEVICE_SUPPORTS_AV1 +uint16_t uniffi_church_core_checksum_func_device_supports_av1(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +uint16_t uniffi_church_core_checksum_func_extract_scripture_references_string(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BIBLE_VERSE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_bible_verse_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BULLETINS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BULLETINS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_bulletins_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CACHED_IMAGE_BASE64 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CACHED_IMAGE_BASE64 +uint16_t uniffi_church_core_checksum_func_fetch_cached_image_base64(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CONFIG_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CONFIG_JSON +uint16_t uniffi_church_core_checksum_func_fetch_config_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CURRENT_BULLETIN_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CURRENT_BULLETIN_JSON +uint16_t uniffi_church_core_checksum_func_fetch_current_bulletin_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_EVENTS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_events_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_FEATURED_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_FEATURED_EVENTS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_featured_events_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVE_STREAM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVE_STREAM_JSON +uint16_t uniffi_church_core_checksum_func_fetch_live_stream_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_livestream_archive_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_random_bible_verse_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +uint16_t uniffi_church_core_checksum_func_fetch_scripture_verses_for_sermon_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SERMONS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SERMONS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_sermons_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_STREAM_STATUS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_STREAM_STATUS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_stream_status_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +uint16_t uniffi_church_core_checksum_func_filter_sermons_by_media_type(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +uint16_t uniffi_church_core_checksum_func_format_event_for_display_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +uint16_t uniffi_church_core_checksum_func_format_scripture_text_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_TIME_RANGE_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_TIME_RANGE_STRING +uint16_t uniffi_church_core_checksum_func_format_time_range_string(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_HOME_FEED_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_HOME_FEED_JSON +uint16_t uniffi_church_core_checksum_func_generate_home_feed_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_AV1_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_AV1_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_av1_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_HLS_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_HLS_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_hls_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +uint16_t uniffi_church_core_checksum_func_get_media_type_display_name(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_ICON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_ICON +uint16_t uniffi_church_core_checksum_func_get_media_type_icon(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_OPTIMAL_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_OPTIMAL_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_optimal_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_IS_MULTI_DAY_EVENT_CHECK +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_IS_MULTI_DAY_EVENT_CHECK +uint16_t uniffi_church_core_checksum_func_is_multi_day_event_check(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_JSON +uint16_t uniffi_church_core_checksum_func_submit_contact_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON +uint16_t uniffi_church_core_checksum_func_submit_contact_v2_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +uint16_t uniffi_church_core_checksum_func_submit_contact_v2_json_legacy(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_CONTACT_FORM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_CONTACT_FORM_JSON +uint16_t uniffi_church_core_checksum_func_validate_contact_form_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_EMAIL_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_EMAIL_ADDRESS +uint16_t uniffi_church_core_checksum_func_validate_email_address(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_PHONE_NUMBER +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_PHONE_NUMBER +uint16_t uniffi_church_core_checksum_func_validate_phone_number(void + +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_UNIFFI_CONTRACT_VERSION +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_UNIFFI_CONTRACT_VERSION +uint32_t ffi_church_core_uniffi_contract_version(void + +); +#endif + diff --git a/ChurchCore.xcframework/macos-arm64_x86_64/libchurch_core_mac_catalyst.a b/ChurchCore.xcframework/macos-arm64_x86_64/libchurch_core_mac_catalyst.a new file mode 100644 index 0000000..8026b7a Binary files /dev/null and b/ChurchCore.xcframework/macos-arm64_x86_64/libchurch_core_mac_catalyst.a differ diff --git a/Extensions/Color+tvOS.swift b/Extensions/Color+tvOS.swift new file mode 100644 index 0000000..3acd95f --- /dev/null +++ b/Extensions/Color+tvOS.swift @@ -0,0 +1,85 @@ +import SwiftUI +import UIKit + +// MARK: - Global tvOS Compatibility + +#if os(tvOS) +// Add missing system colors for tvOS +extension UIColor { + static var systemBackground: UIColor { return UIColor.black } + static var secondarySystemBackground: UIColor { return UIColor.gray.withAlphaComponent(0.1) } + static var systemGray6: UIColor { return UIColor.gray.withAlphaComponent(0.2) } +} + +// Dummy UIActivityViewController for tvOS +class UIActivityViewController: UIViewController { + convenience init(activityItems: [Any], applicationActivities: [UIActivity]?) { + self.init() + } +} + +// Add missing NavigationBarItem types +extension NavigationBarItem { + enum TitleDisplayMode { + case automatic + case inline + case large + } +} +#endif + +// MARK: - SwiftUI Extensions + +extension Color { + static var systemBackground: Color { + #if os(iOS) + return Color(.systemBackground) + #else + return Color.black + #endif + } + + static var secondarySystemBackground: Color { + #if os(iOS) + return Color(.secondarySystemBackground) + #else + return Color.secondary.opacity(0.1) + #endif + } + + static var systemGray6: Color { + #if os(iOS) + return Color(.systemGray6) + #else + return Color.gray.opacity(0.2) + #endif + } +} + +// MARK: - View Extensions for tvOS Compatibility + +extension View { + func navigationBarTitleDisplayMode(_ mode: NavigationBarItem.TitleDisplayMode) -> some View { + #if os(iOS) + return self.navigationBarTitleDisplayMode(mode) + #else + return self + #endif + } + + func navigationBarBackButtonHidden(_ hidden: Bool) -> some View { + #if os(iOS) + return self.navigationBarBackButtonHidden(hidden) + #else + return self + #endif + } + + func listRowSeparator(_ visibility: Visibility, edges: VerticalEdge.Set = .all) -> some View { + #if os(iOS) + return self.listRowSeparator(visibility, edges: edges) + #else + return self + #endif + } +} \ No newline at end of file diff --git a/Managers/PermissionsManager.swift b/Managers/PermissionsManager.swift index 8808041..3dbfa86 100644 --- a/Managers/PermissionsManager.swift +++ b/Managers/PermissionsManager.swift @@ -1,8 +1,12 @@ import Foundation +#if canImport(EventKit) import EventKit +#endif import CoreLocation import Photos +#if canImport(Contacts) import Contacts +#endif import AVFoundation @MainActor @@ -33,6 +37,7 @@ class PermissionsManager: ObservableObject { // MARK: - Calendar func checkCalendarAccess() { + #if canImport(EventKit) if #available(iOS 17.0, *) { let status = EKEventStore.authorizationStatus(for: .event) calendarAccess = status == .fullAccess || status == .writeOnly @@ -40,9 +45,13 @@ class PermissionsManager: ObservableObject { let status = EKEventStore.authorizationStatus(for: .event) calendarAccess = status == .authorized } + #else + calendarAccess = false + #endif } func requestCalendarAccess() async -> Bool { + #if canImport(EventKit) let store = EKEventStore() do { if #available(iOS 17.0, *) { @@ -104,6 +113,9 @@ class PermissionsManager: ObservableObject { print("❌ Calendar access error: \(error)") return false } + #else + return false + #endif } // MARK: - Location @@ -150,11 +162,16 @@ class PermissionsManager: ObservableObject { // MARK: - Contacts func checkContactsAccess() { + #if canImport(Contacts) let status = CNContactStore.authorizationStatus(for: .contacts) contactsAccess = status == .authorized + #else + contactsAccess = false + #endif } func requestContactsAccess() async -> Bool { + #if canImport(Contacts) let store = CNContactStore() do { let granted = try await store.requestAccess(for: .contacts) @@ -166,6 +183,9 @@ class PermissionsManager: ObservableObject { print("❌ Contacts access error: \(error)") return false } + #else + return false + #endif } // MARK: - Microphone @@ -186,6 +206,7 @@ class PermissionsManager: ObservableObject { func handleLimitedAccess(for feature: AppFeature) -> FeatureAvailability { switch feature { case .calendar: + #if canImport(EventKit) if #available(iOS 17.0, *) { let status = EKEventStore.authorizationStatus(for: .event) switch status { @@ -203,6 +224,9 @@ class PermissionsManager: ObservableObject { } return .unavailable } + #else + return .unavailable + #endif case .location: return locationAccess ? .full : .limited case .camera: @@ -219,7 +243,11 @@ class PermissionsManager: ObservableObject { return .unavailable } case .contacts: + #if canImport(Contacts) return contactsAccess ? .full : .limited + #else + return .unavailable + #endif case .microphone: return microphoneAccess ? .full : .limited } diff --git a/Models/Bulletin.swift b/Models/Bulletin.swift deleted file mode 100644 index cc3a243..0000000 --- a/Models/Bulletin.swift +++ /dev/null @@ -1,66 +0,0 @@ -import Foundation - -struct BulletinSection: Identifiable { - let id = UUID() - let title: String - let content: String -} - -struct Bulletin: Identifiable, Codable { - let id: String - let title: String - let date: Date - let sections: [BulletinSection] - let pdfUrl: String? - let isActive: Bool - let created: Date - let updated: Date - - enum CodingKeys: String, CodingKey { - case id - case title - case date - case sections - case pdfUrl = "pdf_url" - case isActive = "is_active" - case created - case updated - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: .id) - title = try container.decode(String.self, forKey: .title) - date = try container.decode(Date.self, forKey: .date) - pdfUrl = try container.decodeIfPresent(String.self, forKey: .pdfUrl) - isActive = try container.decode(Bool.self, forKey: .isActive) - created = try container.decode(Date.self, forKey: .created) - updated = try container.decode(Date.self, forKey: .updated) - - // Decode sections - let sectionsData = try container.decode([[String: String]].self, forKey: .sections) - sections = sectionsData.map { section in - BulletinSection( - title: section["title"] ?? "", - content: section["content"] ?? "" - ) - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(title, forKey: .title) - try container.encode(date, forKey: .date) - try container.encodeIfPresent(pdfUrl, forKey: .pdfUrl) - try container.encode(isActive, forKey: .isActive) - try container.encode(created, forKey: .created) - try container.encode(updated, forKey: .updated) - - // Encode sections - let sectionsData = sections.map { section in - ["title": section.title, "content": section.content] - } - try container.encode(sectionsData, forKey: .sections) - } -} \ No newline at end of file diff --git a/Models/Config.swift b/Models/Config.swift index 7b9e52b..43a1c2b 100644 --- a/Models/Config.swift +++ b/Models/Config.swift @@ -3,13 +3,47 @@ import Foundation struct Config: Codable { let id: String let churchName: String + let tagline: String let contactEmail: String let contactPhone: String let churchAddress: String + let coordinates: Coordinates + let donationUrl: String + let brandColor: String let googleMapsUrl: String let aboutText: String + let poBox: String + let serviceTimes: [ServiceTime] let apiKeys: APIKeys + enum CodingKeys: String, CodingKey { + case id + case churchName = "church_name" + case tagline + case contactEmail = "contact_email" + case contactPhone = "contact_phone" + case churchAddress = "church_address" + case coordinates + case donationUrl = "donation_url" + case brandColor = "brand_color" + case googleMapsUrl = "google_maps_url" + case aboutText = "about_text" + case poBox = "po_box" + case serviceTimes = "service_times" + case apiKeys = "api_keys" + } + + struct Coordinates: Codable { + let lat: Double + let lng: Double + } + + struct ServiceTime: Codable { + let day: String + let time: String + let service: String + } + struct APIKeys: Codable { let bibleApiKey: String let jellyfinApiKey: String @@ -18,18 +52,10 @@ struct Config: Codable { case bibleApiKey = "bible_api_key" case jellyfinApiKey = "jellyfin_api_key" } + } - enum CodingKeys: String, CodingKey { - case id - case churchName = "church_name" - case contactEmail = "contact_email" - case contactPhone = "contact_phone" - case churchAddress = "church_address" - case googleMapsUrl = "google_maps_url" - case aboutText = "about_text" - case apiKeys = "api_key" - } +} } struct ConfigResponse: Codable { @@ -37,13 +63,14 @@ struct ConfigResponse: Codable { let perPage: Int let totalPages: Int let totalItems: Int - let items: [Config] enum CodingKeys: String, CodingKey { case page - case perPage = "perPage" - case totalPages = "totalPages" - case totalItems = "totalItems" + case perPage = "per_page" + case totalPages = "total_pages" + case totalItems = "total_items" case items } + let items: [Config] + } \ No newline at end of file diff --git a/Models/ContentModels.swift b/Models/ContentModels.swift new file mode 100644 index 0000000..c84e4e6 --- /dev/null +++ b/Models/ContentModels.swift @@ -0,0 +1,419 @@ +import Foundation + +// MARK: - Content Models for RTSDA v2.0 + +struct ChurchEvent: Identifiable, Codable, Hashable { + let id: String + let title: String + let description: String + + // Raw ISO timestamps for calendar/system APIs + let startTime: String + let endTime: String + + // Formatted display strings from Rust (RTSDA Architecture Rules compliance) + let formattedTime: String // "6:00 PM - 8:00 PM" + let formattedDate: String // "Friday, August 15, 2025" + let formattedDateTime: String // "Friday, August 15, 2025 at 6:00 PM" + + // Additional display fields from Rust (RTSDA Architecture Rules compliance) + let dayOfMonth: String // "15" + let monthAbbreviation: String // "AUG" + let timeString: String // "6:00 PM - 8:00 PM" (alias for formattedTime) + let isMultiDay: Bool // true if event spans multiple days + let detailedTimeDisplay: String // Full time range for detail views + + let location: String + let locationUrl: String? + let image: String? + let thumbnail: String? + let category: String + let isFeatured: Bool + let recurringType: String? + let createdAt: String + let updatedAt: String + + enum CodingKeys: String, CodingKey { + case id, title, description, location, image, thumbnail, category + case startTime = "start_time" + case endTime = "end_time" + case formattedTime = "formatted_time" + case formattedDate = "formatted_date" + case formattedDateTime = "formatted_date_time" + case dayOfMonth = "day_of_month" + case monthAbbreviation = "month_abbreviation" + case timeString = "time_string" + case isMultiDay = "is_multi_day" + case detailedTimeDisplay = "detailed_time_display" + case locationUrl = "location_url" + case isFeatured = "is_featured" + case recurringType = "recurring_type" + case createdAt = "created_at" + case updatedAt = "updated_at" + } + + // All formatting now handled by Rust church-core crate (RTSDA Architecture Rules compliance) + + /// Returns formatted date range for display - now using Rust-provided formattedDate + var formattedDateRange: String { + return formattedDate + } + + + // MARK: - Sample Data + static func sampleEvent() -> ChurchEvent { + return ChurchEvent( + id: "sample-event-1", + title: "Community Potluck", + description: "Join us for fellowship and food", + startTime: "2025-01-15T18:00:00-05:00", + endTime: "2025-01-15T20:00:00-05:00", + formattedTime: "6:00 PM - 8:00 PM", + formattedDate: "January 15, 2025", + formattedDateTime: "January 15, 2025 at 6:00 PM", + dayOfMonth: "15", + monthAbbreviation: "JAN", + timeString: "6:00 PM - 8:00 PM", + isMultiDay: false, + detailedTimeDisplay: "6:00 PM - 8:00 PM", + location: "Fellowship Hall", + locationUrl: nil, + image: nil, + thumbnail: nil, + category: "Social", + isFeatured: false, + recurringType: nil, + createdAt: "2025-01-10T09:00:00-05:00", + updatedAt: "2025-01-10T09:00:00-05:00" + ) + } +} + +struct Sermon: Identifiable, Codable { + let id: String + let title: String + let speaker: String + let description: String? + let date: String? + let audioUrl: String? + let videoUrl: String? + let duration: String? + let mediaType: String? + let thumbnail: String? + let image: String? + let scriptureReading: String? + + // CodingKeys no longer needed - Rust now sends camelCase field names + + + var formattedDate: String { + return date ?? "Date unknown" // Already formatted by API + } + + var durationFormatted: String? { + return duration // Already formatted by API + } + + // MARK: - Sample Data + static func sampleSermon() -> Sermon { + return Sermon( + id: "sample-1", + title: "Walking in Faith During Difficult Times", + speaker: "Pastor John Smith", + description: "A message about trusting God during challenging times.", + date: "January 10th, 2025", + audioUrl: nil, + videoUrl: "https://example.com/video.mp4", + duration: "35:42", + mediaType: "Video", + thumbnail: nil, + image: nil, + scriptureReading: "Philippians 4:13" + ) + } +} + +struct ChurchBulletin: Identifiable, Codable { + let id: String + let title: String + let date: String + let sabbathSchool: String + let divineWorship: String + let scriptureReading: String + let sunset: String + let pdfPath: String? + let coverImage: String? + let isActive: Bool + + enum CodingKeys: String, CodingKey { + case id, title, date, sunset + case sabbathSchool = "sabbath_school" + case divineWorship = "divine_worship" + case scriptureReading = "scripture_reading" + case pdfPath = "pdf_path" + case coverImage = "cover_image" + case isActive = "is_active" + } + + + var formattedDate: String { + // Parse ISO8601 or YYYY-MM-DD format and return US-friendly date + let formatter = ISO8601DateFormatter() + if let isoDate = formatter.date(from: date) { + let usFormatter = DateFormatter() + usFormatter.dateStyle = .long // "August 2, 2025" + return usFormatter.string(from: isoDate) + } else if date.contains("-") { + // Try parsing YYYY-MM-DD format + let components = date.split(separator: "-") + if components.count >= 3, + let year = Int(components[0]), + let month = Int(components[1]), + let day = Int(components[2]) { + let dateComponents = DateComponents(year: year, month: month, day: day) + if let parsedDate = Calendar.current.date(from: dateComponents) { + let usFormatter = DateFormatter() + usFormatter.dateStyle = .long // "August 2, 2025" + return usFormatter.string(from: parsedDate) + } + } + } + return date // Fallback to original if parsing fails + } +} + +struct BibleVerse: Identifiable, Codable { + let text: String + let reference: String + let version: String? + let book: String? + let chapter: UInt32? + let verse: UInt32? + let category: String? + + // Computed property for Identifiable + var id: String { + return reference + text.prefix(50) // Use reference + start of text as unique ID + } +} + +// MARK: - API Response Models + +struct EventsResponse: Codable { + let items: [ChurchEvent] + let total: Int + let page: Int + let perPage: Int + let hasMore: Bool + + enum CodingKeys: String, CodingKey { + case items, total, page + case perPage = "per_page" + case hasMore = "has_more" + } + +} + +struct ContactSubmissionResult: Codable { + let success: Bool + let message: String? +} + +// MARK: - Content Feed Item + +enum FeedItemType { + case sermon(Sermon) + case event(ChurchEvent) + case bulletin(ChurchBulletin) + case verse(BibleVerse) +} + +// MARK: - Rust Feed Item (from church-core) + +struct RustFeedItem: Identifiable, Codable { + let id: String + let feedType: RustFeedItemType + + enum CodingKeys: String, CodingKey { + case id + case feedType = "feed_type" + case timestamp + case priority + } + let timestamp: String // ISO8601 format + let priority: Int32 + +} + +enum RustFeedItemType: Codable { + case event(ChurchEvent) + case sermon(Sermon) + case bulletin(ChurchBulletin) + case verse(BibleVerse) + + enum CodingKeys: String, CodingKey { + case type + case event + case sermon + case bulletin + case verse + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(String.self, forKey: .type) + + switch type { + case "event": + let event = try container.decode(ChurchEvent.self, forKey: .event) + self = .event(event) + case "sermon": + let sermon = try container.decode(Sermon.self, forKey: .sermon) + self = .sermon(sermon) + case "bulletin": + let bulletin = try container.decode(ChurchBulletin.self, forKey: .bulletin) + self = .bulletin(bulletin) + case "verse": + let verse = try container.decode(BibleVerse.self, forKey: .verse) + self = .verse(verse) + default: + throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unknown feed item type: \(type)")) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .event(let event): + try container.encode("event", forKey: .type) + try container.encode(event, forKey: .event) + case .sermon(let sermon): + try container.encode("sermon", forKey: .type) + try container.encode(sermon, forKey: .sermon) + case .bulletin(let bulletin): + try container.encode("bulletin", forKey: .type) + try container.encode(bulletin, forKey: .bulletin) + case .verse(let verse): + try container.encode("verse", forKey: .type) + try container.encode(verse, forKey: .verse) + } + } +} + +// MARK: - Swift Feed Item (legacy) + +struct FeedItem: Identifiable { + let id = UUID() + let type: FeedItemType + let timestamp: Date + + var title: String { + switch type { + case .sermon(let sermon): + return sermon.title + case .event(let event): + return event.title + case .bulletin(let bulletin): + return bulletin.title + case .verse(let verse): + return verse.reference + } + } + + var subtitle: String? { + switch type { + case .sermon(let sermon): + return sermon.speaker + case .event(let event): + return event.formattedDate + case .bulletin(let bulletin): + return bulletin.formattedDate + case .verse(let verse): + return verse.text + } + } +} + +// MARK: - Search Utilities + +struct SearchUtils { + + /// Searches events by title, description, location, and date + static func searchEvents(_ events: [ChurchEvent], searchText: String) -> [ChurchEvent] { + guard !searchText.isEmpty else { return events } + + return events.filter { event in + let titleMatch = event.title.localizedCaseInsensitiveContains(searchText) + let descMatch = event.description.localizedCaseInsensitiveContains(searchText) + let locationMatch = event.location.localizedCaseInsensitiveContains(searchText) + let dateMatch = event.formattedDate.localizedCaseInsensitiveContains(searchText) + let smartDateMatch = checkSmartDateMatch(searchText: searchText, sermonDate: event.formattedDate) + return titleMatch || descMatch || locationMatch || dateMatch || smartDateMatch + } + } + + /// Searches sermons by title, speaker, description, and date + static func searchSermons(_ sermons: [Sermon], searchText: String, contentType: String = "sermons") -> [Sermon] { + guard !searchText.isEmpty else { return sermons } + + return sermons.filter { sermon in + let titleMatch = sermon.title.localizedCaseInsensitiveContains(searchText) + let speakerMatch = sermon.speaker.localizedCaseInsensitiveContains(searchText) + let descMatch = sermon.description?.localizedCaseInsensitiveContains(searchText) ?? false + let dateMatch = sermon.date?.localizedCaseInsensitiveContains(searchText) ?? false + let smartDateMatch = checkSmartDateMatch(searchText: searchText, sermonDate: sermon.date) + return titleMatch || speakerMatch || descMatch || dateMatch || smartDateMatch + } + } + + /// Smart date matching for patterns like "January 2025", "Jan 2024", etc. + private static func checkSmartDateMatch(searchText: String, sermonDate: String?) -> Bool { + guard let sermonDate = sermonDate else { return false } + + let searchWords = searchText.lowercased().components(separatedBy: .whitespaces).filter { !$0.isEmpty } + guard searchWords.count >= 2 else { return false } + + // Check for month + year patterns like "January 2025" or "Jan 2024" + let monthNames = ["january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december"] + let monthAbbrevs = ["jan", "feb", "mar", "apr", "may", "jun", + "jul", "aug", "sep", "oct", "nov", "dec"] + + for i in 0..= 2000 && year <= 2100 { + // Check if sermon date contains both the month and year + if sermonDateLower.contains(fullMonthName) && sermonDateLower.contains(String(year)) { + return true + } + } + // Check if second word could be a day OR part of a year + else if let number = Int(word2) { + // Check if sermon date contains both the month and this number anywhere + // This catches both day matches (e.g., "January 20th") and year substring matches (e.g., "January" + "20" in "2020") + if sermonDateLower.contains(fullMonthName) && sermonDateLower.contains(String(number)) { + return true + } + } + } + } + + return false + } +} \ No newline at end of file diff --git a/Models/Event.swift b/Models/Event.swift deleted file mode 100644 index d8807f3..0000000 --- a/Models/Event.swift +++ /dev/null @@ -1,403 +0,0 @@ -import Foundation -import EventKit -import UIKit - -struct Event: Identifiable, Codable { - let id: String - let title: String - let description: String // Original HTML description - let startDate: Date - let endDate: Date - let location: String? - let locationURL: String? - let image: String? - let thumbnail: String? - let category: EventCategory - let isFeatured: Bool - let reoccuring: ReoccurringType - let isPublished: Bool - let created: Date - let updated: Date - - enum EventCategory: String, Codable { - case service = "Service" - case social = "Social" - case ministry = "Ministry" - case other = "Other" - } - - enum ReoccurringType: String, Codable { - case none = "" // For non-recurring events - case daily = "DAILY" - case weekly = "WEEKLY" - case biweekly = "BIWEEKLY" - case firstTuesday = "FIRST_TUESDAY" - - var calendarRecurrenceRule: EKRecurrenceRule? { - switch self { - case .none: - return nil // No recurrence for one-time events - case .daily: - return EKRecurrenceRule( - recurrenceWith: .daily, - interval: 1, - end: nil - ) - case .weekly: - return EKRecurrenceRule( - recurrenceWith: .weekly, - interval: 1, - end: nil - ) - case .biweekly: - return EKRecurrenceRule( - recurrenceWith: .weekly, - interval: 2, - end: nil - ) - case .firstTuesday: - let tuesday = EKWeekday.tuesday - return EKRecurrenceRule( - recurrenceWith: .monthly, - interval: 1, - daysOfTheWeek: [EKRecurrenceDayOfWeek(tuesday)], - daysOfTheMonth: nil, - monthsOfTheYear: nil, - weeksOfTheYear: nil, - daysOfTheYear: nil, - setPositions: [1], - end: nil - ) - } - } - } - - var formattedDateTime: String { - let dateFormatter = DateFormatter() - dateFormatter.dateStyle = .medium - dateFormatter.timeStyle = .short - dateFormatter.timeZone = .gmt // Use GMT to match database times exactly - - let startDateString = dateFormatter.string(from: startDate) - let endTimeFormatter = DateFormatter() - endTimeFormatter.timeStyle = .short - endTimeFormatter.timeZone = .gmt // Use GMT to match database times exactly - let endTimeString = endTimeFormatter.string(from: endDate) - - return "\(startDateString) • \(endTimeString)" - } - - var hasLocation: Bool { - return (location != nil && !location!.isEmpty) - } - - var hasLocationUrl: Bool { - return (locationURL != nil && !locationURL!.isEmpty) - } - - var canOpenInMaps: Bool { - return hasLocation - } - - var displayLocation: String { - if let location = location { - return location - } - if let locationURL = locationURL { - // Try to extract a readable location from the URL - if let url = URL(string: locationURL) { - let components = url.pathComponents - if components.count > 1 { - return components.last?.replacingOccurrences(of: "+", with: " ") ?? locationURL - } - } - return locationURL - } - return "No location specified" - } - - var imageURL: URL? { - guard let image = image else { return nil } - return URL(string: "https://pocketbase.rockvilletollandsda.church/api/files/events/\(id)/\(image)") - } - - var thumbnailURL: URL? { - guard let thumbnail = thumbnail else { return nil } - return URL(string: "https://pocketbase.rockvilletollandsda.church/api/files/events/\(id)/\(thumbnail)") - } - - func callPhone() { - if let phoneNumber = extractPhoneNumber() { - let cleanNumber = phoneNumber.replacingOccurrences(of: "[^0-9+]", with: "", options: .regularExpression) - if let url = URL(string: "tel://\(cleanNumber)") { - UIApplication.shared.open(url) - } - } - } - - func extractPhoneNumber() -> String? { - let phonePattern = #"Phone:.*?(\+\d{1})?[\s-]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})"# - if let match = description.range(of: phonePattern, options: .regularExpression) { - let phoneText = String(description[match]) - let numberPattern = #"(\+\d{1})?[\s-]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})"# - if let numberMatch = phoneText.range(of: numberPattern, options: .regularExpression) { - return String(phoneText[numberMatch]) - } - } - return nil - } - - var plainDescription: String { - // First remove all table structures and divs - var cleanedText = description.replacingOccurrences(of: "]*>.*?", with: "", options: .regularExpression) - cleanedText = cleanedText.replacingOccurrences(of: "]*>", with: "", options: .regularExpression) - cleanedText = cleanedText.replacingOccurrences(of: "", with: "\n", options: .regularExpression) - - // Replace other HTML tags - cleanedText = cleanedText.replacingOccurrences(of: "", with: "\n", options: .regularExpression) - cleanedText = cleanedText.replacingOccurrences(of: "

", with: "", options: .regularExpression) - cleanedText = cleanedText.replacingOccurrences(of: "

", with: "\n", options: .regularExpression) - cleanedText = cleanedText.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression) - - // Decode common HTML entities - let htmlEntities = [ - " ": " ", - "&": "&", - "<": "<", - ">": ">", - """: "\"", - "'": "'", - "'": "'", - "/": "/", - "'": "'", - "/": "/", - "’": "'", - "—": "—" - ] - - for (entity, replacement) in htmlEntities { - cleanedText = cleanedText.replacingOccurrences(of: entity, with: replacement) - } - - // Format phone numbers with better pattern matching - let phonePattern = #"(?m)^Phone:.*?(\+\d{1})?[\s-]?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})"# - cleanedText = cleanedText.replacingOccurrences( - of: phonePattern, - with: "📞 Phone: ($2) $3-$4", - options: .regularExpression - ) - - // Clean up whitespace while preserving intentional line breaks - let lines = cleanedText.components(separatedBy: .newlines) - let nonEmptyLines = lines.map { $0.trimmingCharacters(in: .whitespaces) } - .filter { !$0.isEmpty } - - return nonEmptyLines.joined(separator: "\n") - } - - func openInMaps() async { - let permissionsManager = await PermissionsManager.shared - - // We don't strictly need location permission to open maps, - // but we'll request it for better functionality - await permissionsManager.requestLocationAccess() - - if let locationURL = locationURL, let url = URL(string: locationURL) { - await UIApplication.shared.open(url, options: [:]) - } else if let location = location, !location.isEmpty { - let searchQuery = location.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? location - if let mapsUrl = URL(string: "http://maps.apple.com/?q=\(searchQuery)") { - await UIApplication.shared.open(mapsUrl, options: [:]) - } - } - } - - func addToCalendar(completion: @escaping (Bool, Error?) -> Void) async { - let permissionsManager = await PermissionsManager.shared - let eventStore = EKEventStore() - - do { - let accessGranted = await permissionsManager.requestCalendarAccess() - - if !accessGranted { - await MainActor.run { - completion(false, NSError(domain: "com.rtsda.calendar", code: 1, - userInfo: [NSLocalizedDescriptionKey: "Calendar access is not available. You can enable it in Settings."])) - } - return - } - - let event = EKEvent(eventStore: eventStore) - - // Set basic event details - event.title = self.title - event.notes = self.plainDescription - event.startDate = self.startDate - event.endDate = self.endDate - event.location = self.location ?? self.locationURL - - // Set recurrence rule if applicable - if let rule = self.reoccuring.calendarRecurrenceRule { - event.recurrenceRules = [rule] - } - - // Get the default calendar - guard let calendar = eventStore.defaultCalendarForNewEvents else { - await MainActor.run { - completion(false, NSError(domain: "com.rtsda.calendar", code: 2, - userInfo: [NSLocalizedDescriptionKey: "Could not access default calendar."])) - } - return - } - - event.calendar = calendar - - try eventStore.save(event, span: .thisEvent) - await MainActor.run { - completion(true, nil) - } - } catch { - await MainActor.run { - completion(false, error) - } - } - } - - enum CodingKeys: String, CodingKey { - case id - case title - case description - case startDate = "start_time" - case endDate = "end_time" - case location - case locationURL = "location_url" - case image - case thumbnail - case category - case isFeatured = "is_featured" - case reoccuring - case isPublished = "is_published" - case created - case updated - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - id = try container.decode(String.self, forKey: .id) - title = try container.decode(String.self, forKey: .title) - description = try container.decode(String.self, forKey: .description) - - // Try multiple date formats - let startDateString = try container.decode(String.self, forKey: .startDate) - let endDateString = try container.decode(String.self, forKey: .endDate) - let createdString = try container.decode(String.self, forKey: .created) - let updatedString = try container.decode(String.self, forKey: .updated) - - // Create formatters for different possible formats - let formatters = [ - { () -> DateFormatter in - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSZ" - formatter.timeZone = TimeZone(identifier: "America/New_York")! // Eastern Time - return formatter - }(), - { () -> ISO8601DateFormatter in - let formatter = ISO8601DateFormatter() - formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] - formatter.timeZone = TimeZone(identifier: "America/New_York")! // Eastern Time - return formatter - }(), - { () -> ISO8601DateFormatter in - let formatter = ISO8601DateFormatter() - formatter.formatOptions = [.withInternetDateTime] - formatter.timeZone = TimeZone(identifier: "America/New_York")! // Eastern Time - return formatter - }() - ] - - // Function to try parsing date with multiple formatters - func parseDate(_ dateString: String, field: String) throws -> Date { - // Print the date string we're trying to parse - print("🗓️ Trying to parse date: \(dateString) for field: \(field)") - - for formatter in formatters { - if let date = (formatter as? ISO8601DateFormatter)?.date(from: dateString) ?? - (formatter as? DateFormatter)?.date(from: dateString) { - print("✅ Successfully parsed date using \(type(of: formatter))") - return date - } - } - - throw DecodingError.dataCorruptedError( - forKey: CodingKeys(stringValue: field)!, - in: container, - debugDescription: "Date string '\(dateString)' does not match any expected format" - ) - } - - // Parse all dates - startDate = try parseDate(startDateString, field: "start_time") - endDate = try parseDate(endDateString, field: "end_time") - created = try parseDate(createdString, field: "created") - updated = try parseDate(updatedString, field: "updated") - - // Decode remaining fields - location = try container.decodeIfPresent(String.self, forKey: .location) - locationURL = try container.decodeIfPresent(String.self, forKey: .locationURL) - image = try container.decodeIfPresent(String.self, forKey: .image) - thumbnail = try container.decodeIfPresent(String.self, forKey: .thumbnail) - category = try container.decode(EventCategory.self, forKey: .category) - isFeatured = try container.decode(Bool.self, forKey: .isFeatured) - reoccuring = try container.decode(ReoccurringType.self, forKey: .reoccuring) - isPublished = try container.decodeIfPresent(Bool.self, forKey: .isPublished) ?? true // Default to true if not present - } - - init(id: String = UUID().uuidString, - title: String, - description: String, - startDate: Date, - endDate: Date, - location: String? = nil, - locationURL: String? = nil, - image: String? = nil, - thumbnail: String? = nil, - category: EventCategory, - isFeatured: Bool = false, - reoccuring: ReoccurringType, - isPublished: Bool = true, - created: Date = Date(), - updated: Date = Date()) { - self.id = id - self.title = title - self.description = description - self.startDate = startDate - self.endDate = endDate - self.location = location - self.locationURL = locationURL - self.image = image - self.thumbnail = thumbnail - self.category = category - self.isFeatured = isFeatured - self.reoccuring = reoccuring - self.isPublished = isPublished - self.created = created - self.updated = updated - } -} - -struct EventResponse: Codable { - let page: Int - let perPage: Int - let totalPages: Int - let totalItems: Int - let items: [Event] - - enum CodingKeys: String, CodingKey { - case page - case perPage = "perPage" - case totalPages = "totalPages" - case totalItems = "totalItems" - case items - } -} diff --git a/Models/Message.swift b/Models/Message.swift deleted file mode 100644 index 05bf21b..0000000 --- a/Models/Message.swift +++ /dev/null @@ -1,57 +0,0 @@ -import Foundation - -struct Message: Identifiable { - let id: String - let title: String - let description: String - let speaker: String - let videoUrl: String - let thumbnailUrl: String? - let duration: TimeInterval - let isLiveStream: Bool - let isPublished: Bool - let isDeleted: Bool - let liveBroadcastStatus: String // "none", "upcoming", "live", or "completed" - let date: String // ISO8601 formatted date string - - var formattedDuration: String { - let formatter = DateComponentsFormatter() - formatter.allowedUnits = [.hour, .minute] - formatter.unitsStyle = .abbreviated - formatter.maximumUnitCount = 2 - return formatter.string(from: duration) ?? "" - } - - var formattedDate: String { - // Parse the date string - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd" - dateFormatter.timeZone = TimeZone(identifier: "America/New_York") - - guard let date = dateFormatter.date(from: date) else { return date } - - // Format for display - let displayFormatter = DateFormatter() - displayFormatter.dateFormat = "MMMM d, yyyy" - displayFormatter.timeZone = TimeZone(identifier: "America/New_York") - return displayFormatter.string(from: date) - } -} - -// MARK: - Codable -extension Message: Codable { - enum CodingKeys: String, CodingKey { - case id - case title - case description - case speaker - case videoUrl - case thumbnailUrl - case duration - case isLiveStream - case isPublished - case isDeleted - case liveBroadcastStatus - case date - } -} diff --git a/Models/Sermon.swift b/Models/Sermon.swift deleted file mode 100644 index 9a83ce4..0000000 --- a/Models/Sermon.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation - -struct Sermon: Identifiable { - let id: String - let title: String - let description: String - let date: Date - let speaker: String - let type: SermonType - let videoUrl: String? - let thumbnail: String? - - init(id: String, title: String, description: String, date: Date, speaker: String, type: SermonType, videoUrl: String?, thumbnail: String?) { - self.id = id - self.title = title - self.description = description - self.date = date - self.speaker = speaker - self.type = type - self.videoUrl = videoUrl - self.thumbnail = thumbnail - } -} - -enum SermonType: String { - case sermon = "Sermons" - case liveArchive = "LiveStreams" -} diff --git a/README.md b/README.md index 23ae737..2636b6c 100644 --- a/README.md +++ b/README.md @@ -5,72 +5,95 @@ The official iOS app for the Rockville-Tolland Seventh-day Adventist Church. Thi ## Features - **Live Streaming**: Watch church services live through OwnCast integration -- **Sermon Library**: Access archived sermons and special programs via Jellyfin +- **Video Library**: Access archived sermons and special programs via Jellyfin - **Digital Bulletin**: - - View weekly church bulletins + - View weekly church bulletins with enhanced formatting - Interactive hymn links that open in the Adventist Hymnal app - Bible verse links that open in YouVersion Bible app - PDF download option for offline viewing -- **Church Bulletin**: Stay updated with church announcements and events + - Responsive reading support +- **Events & Calendar**: Stay updated with church announcements and upcoming events +- **Contact Form**: Direct communication with church staff - **Church Information**: Access church beliefs, contact information, and more +- **Home Feed**: Unified view of latest bulletins, events, and media content -## Technical Details +## Architecture -- Built with SwiftUI -- Minimum iOS version: 17.0 -- Uses async/await for network operations -- Integrates with multiple services: +### Version 2.0 - Major Rewrite +This version represents a complete architectural overhaul with significant improvements: + +- **Unified Data Layer**: All networking consolidated into a single `ChurchService` powered by the church_core Rust library +- **Performance Improvements**: 60% code reduction with optimized Rust-based networking +- **Consistent API**: Same backend used across website, iOS app, and Beacon platform +- **Enhanced UI**: Completely redesigned interface with improved navigation and user experience +- **Better Error Handling**: Robust error management with user-friendly messaging + +### Technical Stack + +- **Frontend**: SwiftUI with iOS 17.0+ features +- **Backend Integration**: church_core Rust library via UniFFI bindings +- **Networking**: Optimized Rust reqwest with built-in caching +- **Media Integration**: - Jellyfin for video content - OwnCast for live streaming - - PocketBase for church data - - YouVersion Bible API for verse content - - Adventist Hymnal app integration + - External app integration (YouVersion Bible, Adventist Hymnal) ## Building the App -1. Clone the repository -2. Open `RTSDA.xcodeproj` in Xcode -3. Build and run the project - -## Requirements - +### Prerequisites - Xcode 15.0 or later - iOS 17.0 or later - Swift 5.9 or later +### Setup +1. Clone the repository: + ```bash + git clone ssh://rockvilleav@git.rockvilletollandsda.church:10443/RTSDA/RTSDA-iOS.git + ``` +2. Open `RTSDA.xcodeproj` in Xcode +3. Ensure the church_core framework is properly linked +4. Build and run the project + +### Dependencies +The app includes the following bundled dependencies: +- **ChurchCore.xcframework**: Rust-based networking library +- **UniFFI Bindings**: Swift bindings for church_core functionality + ## Version History +### Version 2.0 (Current) +**Major Release - Complete Rewrite** +- 🚀 **Unified ChurchService**: Replaced 4 separate networking services with single optimized service +- ⚡ **60% Code Reduction**: Eliminated duplicate networking code for better maintainability +- 🎨 **UI/UX Overhaul**: Completely redesigned interface with improved navigation +- 📱 **Enhanced Views**: New MainAppView, HomeFeedView, and improved detail views +- 🔄 **Better State Management**: Improved data flow and error handling +- 📖 **Responsive Reading**: Added support for call-and-response bulletin sections +- 🏗️ **Rust Backend**: Leveraging church_core library for consistent, performant API calls +- 📊 **Improved Performance**: Faster data loading and better memory management +- 🔗 **Unified Platform**: Same backend as website and Beacon for data consistency + ### Version 1.2.1 - Improved Bible verse formatting in splash screen - - Removed verse numbers - - Removed paragraph markers - - Cleaned up parenthetical content - - Better text formatting - Enhanced bulletin view formatting - - Improved header detection and styling - - Better section organization - - Consistent spacing and alignment - - Cleaner text presentation +- Better text processing and presentation ### Version 1.2 - Added Digital Bulletin system - - View weekly church bulletins - - Interactive hymn links - - Bible verse links - - PDF download option -- Updated minimum iOS version to 17.0 -- Updated Xcode version requirement to 15.0 +- Interactive hymn and Bible verse links +- PDF download functionality +- Updated to iOS 17.0 minimum ### Version 1.1 - Initial release -- Live streaming -- Sermon library -- Church information -- Beliefs reference +- Basic live streaming and media library +- Church information and beliefs reference ## License -This project is licensed under the MIT License - see the LICENSE file for details. +This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details. + +**Important Note**: While the app code is GPL v3, the church content (sermons, bulletins, media) remains copyrighted by the Rockville-Tolland Seventh-day Adventist Church. See LICENSE file for full details. ## Contact diff --git a/RTSDA-Bridging-Header.h b/RTSDA-Bridging-Header.h new file mode 100644 index 0000000..26798cc --- /dev/null +++ b/RTSDA-Bridging-Header.h @@ -0,0 +1,8 @@ +// +// RTSDA-Bridging-Header.h +// RTSDA +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "church_coreFFI.h" \ No newline at end of file diff --git a/RTSDA.entitlements b/RTSDA.entitlements index 903def2..27d2b80 100644 --- a/RTSDA.entitlements +++ b/RTSDA.entitlements @@ -4,5 +4,11 @@ aps-environment development + com.apple.security.app-sandbox + + com.apple.security.network.client + + com.apple.security.personal-information.calendars + diff --git a/RTSDA.xcodeproj/project.pbxproj b/RTSDA.xcodeproj/project.pbxproj index 9dc8da9..6b28697 100644 --- a/RTSDA.xcodeproj/project.pbxproj +++ b/RTSDA.xcodeproj/project.pbxproj @@ -7,59 +7,50 @@ objects = { /* Begin PBXBuildFile section */ - EA019ECF2D978167002FC58F /* BulletinViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA019ECE2D978167002FC58F /* BulletinViewModel.swift */; }; - EA1C83A42D43EA4900D8B78F /* SermonBrowserViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C838F2D43EA4900D8B78F /* SermonBrowserViewModel.swift */; }; EA1C83A52D43EA4900D8B78F /* RTSDAApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83702D43EA4900D8B78F /* RTSDAApp.swift */; }; - EA1C83A62D43EA4900D8B78F /* EventsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C838C2D43EA4900D8B78F /* EventsViewModel.swift */; }; - EA1C83A72D43EA4900D8B78F /* LivestreamCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C839A2D43EA4900D8B78F /* LivestreamCard.swift */; }; - EA1C83A92D43EA4900D8B78F /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83622D43EA4900D8B78F /* Event.swift */; }; - EA1C83AA2D43EA4900D8B78F /* MessagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C839C2D43EA4900D8B78F /* MessagesView.swift */; }; - EA1C83AB2D43EA4900D8B78F /* EventsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83982D43EA4900D8B78F /* EventsView.swift */; }; - EA1C83AC2D43EA4900D8B78F /* EventDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83972D43EA4900D8B78F /* EventDetailView.swift */; }; - EA1C83AD2D43EA4900D8B78F /* CachedAsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83932D43EA4900D8B78F /* CachedAsyncImage.swift */; }; - EA1C83AE2D43EA4900D8B78F /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83792D43EA4900D8B78F /* ImageCache.swift */; }; - EA1C83AF2D43EA4900D8B78F /* OwncastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C839D2D43EA4900D8B78F /* OwncastView.swift */; }; - EA1C83B02D43EA4900D8B78F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83952D43EA4900D8B78F /* ContentView.swift */; }; - EA1C83B12D43EA4900D8B78F /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83612D43EA4900D8B78F /* Config.swift */; }; - EA1C83B22D43EA4900D8B78F /* OwncastViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C838E2D43EA4900D8B78F /* OwncastViewModel.swift */; }; - EA1C83B32D43EA4900D8B78F /* PocketBaseService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C837C2D43EA4900D8B78F /* PocketBaseService.swift */; }; - EA1C83B42D43EA4900D8B78F /* ContactFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83942D43EA4900D8B78F /* ContactFormView.swift */; }; - EA1C83B52D43EA4900D8B78F /* JellyfinService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C837A2D43EA4900D8B78F /* JellyfinService.swift */; }; - EA1C83B62D43EA4900D8B78F /* EventCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83962D43EA4900D8B78F /* EventCard.swift */; }; - EA1C83B72D43EA4900D8B78F /* ConfigService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83782D43EA4900D8B78F /* ConfigService.swift */; }; - EA1C83B82D43EA4900D8B78F /* MessageCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C839B2D43EA4900D8B78F /* MessageCard.swift */; }; - EA1C83B92D43EA4900D8B78F /* Sermon.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83642D43EA4900D8B78F /* Sermon.swift */; }; EA1C83BA2D43EA4900D8B78F /* PermissionsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C835F2D43EA4900D8B78F /* PermissionsManager.swift */; }; - EA1C83BC2D43EA4900D8B78F /* BeliefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83912D43EA4900D8B78F /* BeliefsView.swift */; }; - EA1C83BD2D43EA4900D8B78F /* BulletinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83922D43EA4900D8B78F /* BulletinView.swift */; }; - EA1C83BE2D43EA4900D8B78F /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83632D43EA4900D8B78F /* Message.swift */; }; - EA1C83BF2D43EA4900D8B78F /* BibleService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83772D43EA4900D8B78F /* BibleService.swift */; }; EA1C83C02D43EA4900D8B78F /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C835C2D43EA4900D8B78F /* Color+Extensions.swift */; }; - EA1C83C12D43EA4900D8B78F /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C839F2D43EA4900D8B78F /* VideoPlayerView.swift */; }; - EA1C83C22D43EA4900D8B78F /* MessagesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C838D2D43EA4900D8B78F /* MessagesViewModel.swift */; }; - EA1C83C32D43EA4900D8B78F /* OwnCastService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C837B2D43EA4900D8B78F /* OwnCastService.swift */; }; - EA1C83C42D43EA4900D8B78F /* SplashScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C839E2D43EA4900D8B78F /* SplashScreenView.swift */; }; - EA1C83C52D43EA4900D8B78F /* JellyfinPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83992D43EA4900D8B78F /* JellyfinPlayerView.swift */; }; - EA1C83C62D43EA4900D8B78F /* AppAvailabilityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1C83762D43EA4900D8B78F /* AppAvailabilityService.swift */; }; EA1C83C72D43EA4900D8B78F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EA1C83662D43EA4900D8B78F /* Preview Assets.xcassets */; }; EA1C83C92D43EA4900D8B78F /* Lora-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EA1C83682D43EA4900D8B78F /* Lora-Italic.ttf */; }; EA1C83CD2D43EA4900D8B78F /* Lora-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EA1C83692D43EA4900D8B78F /* Lora-Regular.ttf */; }; EA1C83CE2D43EA4900D8B78F /* Montserrat-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EA1C836B2D43EA4900D8B78F /* Montserrat-SemiBold.ttf */; }; EA1C83CF2D43EA4900D8B78F /* Montserrat-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EA1C836A2D43EA4900D8B78F /* Montserrat-Regular.ttf */; }; EA1C83D22D43EA4900D8B78F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EA1C835B2D43EA4900D8B78F /* Assets.xcassets */; }; + EA24699E2E222470004A9D9F /* church_core.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA24699D2E222470004A9D9F /* church_core.swift */; }; + EA2469C52E22EE2A004A9D9F /* ChurchDataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469C32E22EE2A004A9D9F /* ChurchDataService.swift */; }; + EA2469E42E22EEEE004A9D9F /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469DE2E22EEEE004A9D9F /* VideoPlayerView.swift */; }; + EA2469E52E22EEEE004A9D9F /* MainAppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469DC2E22EEEE004A9D9F /* MainAppView.swift */; }; + EA2469E92E22EEEE004A9D9F /* SplashScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469DD2E22EEEE004A9D9F /* SplashScreenView.swift */; }; + EA2469EA2E22EEEE004A9D9F /* ContactFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469DB2E22EEEE004A9D9F /* ContactFormView.swift */; }; + EA2469EB2E22EEEE004A9D9F /* CachedAsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469DA2E22EEEE004A9D9F /* CachedAsyncImage.swift */; }; + EA2469EC2E22EEEE004A9D9F /* FeedItemCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469C72E22EEEE004A9D9F /* FeedItemCard.swift */; }; + EA2469F02E22EEEE004A9D9F /* ContentCards.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469C62E22EEEE004A9D9F /* ContentCards.swift */; }; + EA2469F32E22EEEE004A9D9F /* BeliefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469D92E22EEEE004A9D9F /* BeliefsView.swift */; }; + EA2469F52E22EEEE004A9D9F /* BulletinDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469C92E22EEEE004A9D9F /* BulletinDetailView.swift */; }; + EA2469F82E22EFC3004A9D9F /* ContentModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA2469F62E22EFC3004A9D9F /* ContentModels.swift */; }; + EA246A042E2349B8004A9D9F /* EventsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA246A032E2349B8004A9D9F /* EventsListView.swift */; }; + EA9A999F2E4413410039692E /* ServiceTimeRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A999E2E4413410039692E /* ServiceTimeRow.swift */; }; + EA9A99A22E457CF00039692E /* EventDetailShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99A12E457CF00039692E /* EventDetailShared.swift */; }; + EA9A99A42E457CFB0039692E /* EventDetailView+iPad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99A32E457CFB0039692E /* EventDetailView+iPad.swift */; }; + EA9A99A62E457D040039692E /* EventDetailView+iPhone.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99A52E457D040039692E /* EventDetailView+iPhone.swift */; }; + EA9A99A82E457D130039692E /* EventDetailViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99A72E457D130039692E /* EventDetailViewWrapper.swift */; }; + EA9A99AC2E4802EE0039692E /* ChurchCore.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA9A99AB2E4802EE0039692E /* ChurchCore.xcframework */; }; + EA9A99B42E482DD50039692E /* SharedVideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99B32E482DD50039692E /* SharedVideoPlayerView.swift */; }; + EA9A99B72E4969DA0039692E /* BulletinsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99B62E4969DA0039692E /* BulletinsView.swift */; }; + EA9A99B92E496A910039692E /* WatchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99B82E496A910039692E /* WatchView.swift */; }; + EA9A99BB2E496B3E0039692E /* ConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99BA2E496B3E0039692E /* ConnectView.swift */; }; + EA9A99BD2E496C030039692E /* HomeFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99BC2E496C030039692E /* HomeFeedView.swift */; }; + EA9A99BF2E496C0A0039692E /* FilterSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99BE2E496C0A0039692E /* FilterSheet.swift */; }; + EA9A99C12E496CC70039692E /* ScriptureComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9A99C02E496CC70039692E /* ScriptureComponents.swift */; }; + EAA1DEC12E4BAE27001414BF /* ContactActionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAA1DEC02E4BAE27001414BF /* ContactActionRow.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - EA019ECE2D978167002FC58F /* BulletinViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BulletinViewModel.swift; sourceTree = ""; }; EA07AAFA2D43EF78002ACBF8 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; EA1C835B2D43EA4900D8B78F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; EA1C835C2D43EA4900D8B78F /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = ""; }; EA1C835E2D43EA4900D8B78F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EA1C835F2D43EA4900D8B78F /* PermissionsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsManager.swift; sourceTree = ""; }; - EA1C83612D43EA4900D8B78F /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; - EA1C83622D43EA4900D8B78F /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; - EA1C83632D43EA4900D8B78F /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; - EA1C83642D43EA4900D8B78F /* Sermon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sermon.swift; sourceTree = ""; }; EA1C83662D43EA4900D8B78F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; EA1C83682D43EA4900D8B78F /* Lora-Italic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Lora-Italic.ttf"; sourceTree = ""; }; EA1C83692D43EA4900D8B78F /* Lora-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Lora-Regular.ttf"; sourceTree = ""; }; @@ -68,33 +59,36 @@ EA1C836E2D43EA4900D8B78F /* RTSDA.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RTSDA.entitlements; sourceTree = ""; }; EA1C836F2D43EA4900D8B78F /* RTSDA.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = RTSDA.xcodeproj; sourceTree = ""; }; EA1C83702D43EA4900D8B78F /* RTSDAApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTSDAApp.swift; sourceTree = ""; }; - EA1C83762D43EA4900D8B78F /* AppAvailabilityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAvailabilityService.swift; sourceTree = ""; }; - EA1C83772D43EA4900D8B78F /* BibleService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BibleService.swift; sourceTree = ""; }; - EA1C83782D43EA4900D8B78F /* ConfigService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigService.swift; sourceTree = ""; }; - EA1C83792D43EA4900D8B78F /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; - EA1C837A2D43EA4900D8B78F /* JellyfinService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinService.swift; sourceTree = ""; }; - EA1C837B2D43EA4900D8B78F /* OwnCastService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwnCastService.swift; sourceTree = ""; }; - EA1C837C2D43EA4900D8B78F /* PocketBaseService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PocketBaseService.swift; sourceTree = ""; }; - EA1C838C2D43EA4900D8B78F /* EventsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsViewModel.swift; sourceTree = ""; }; - EA1C838D2D43EA4900D8B78F /* MessagesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesViewModel.swift; sourceTree = ""; }; - EA1C838E2D43EA4900D8B78F /* OwncastViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwncastViewModel.swift; sourceTree = ""; }; - EA1C838F2D43EA4900D8B78F /* SermonBrowserViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SermonBrowserViewModel.swift; sourceTree = ""; }; - EA1C83912D43EA4900D8B78F /* BeliefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeliefsView.swift; sourceTree = ""; }; - EA1C83922D43EA4900D8B78F /* BulletinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BulletinView.swift; sourceTree = ""; }; - EA1C83932D43EA4900D8B78F /* CachedAsyncImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedAsyncImage.swift; sourceTree = ""; }; - EA1C83942D43EA4900D8B78F /* ContactFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactFormView.swift; sourceTree = ""; }; - EA1C83952D43EA4900D8B78F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - EA1C83962D43EA4900D8B78F /* EventCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCard.swift; sourceTree = ""; }; - EA1C83972D43EA4900D8B78F /* EventDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailView.swift; sourceTree = ""; }; - EA1C83982D43EA4900D8B78F /* EventsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsView.swift; sourceTree = ""; }; - EA1C83992D43EA4900D8B78F /* JellyfinPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinPlayerView.swift; sourceTree = ""; }; - EA1C839A2D43EA4900D8B78F /* LivestreamCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LivestreamCard.swift; sourceTree = ""; }; - EA1C839B2D43EA4900D8B78F /* MessageCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCard.swift; sourceTree = ""; }; - EA1C839C2D43EA4900D8B78F /* MessagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesView.swift; sourceTree = ""; }; - EA1C839D2D43EA4900D8B78F /* OwncastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OwncastView.swift; sourceTree = ""; }; - EA1C839E2D43EA4900D8B78F /* SplashScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenView.swift; sourceTree = ""; }; - EA1C839F2D43EA4900D8B78F /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; + EA24699D2E222470004A9D9F /* church_core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = church_core.swift; sourceTree = ""; }; + EA2469B52E22DDD4004A9D9F /* libchurch_core.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libchurch_core.a; sourceTree = ""; }; + EA2469C32E22EE2A004A9D9F /* ChurchDataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChurchDataService.swift; sourceTree = ""; }; + EA2469C62E22EEEE004A9D9F /* ContentCards.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentCards.swift; sourceTree = ""; }; + EA2469C72E22EEEE004A9D9F /* FeedItemCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemCard.swift; sourceTree = ""; }; + EA2469C92E22EEEE004A9D9F /* BulletinDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BulletinDetailView.swift; sourceTree = ""; }; + EA2469D92E22EEEE004A9D9F /* BeliefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeliefsView.swift; sourceTree = ""; }; + EA2469DA2E22EEEE004A9D9F /* CachedAsyncImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedAsyncImage.swift; sourceTree = ""; }; + EA2469DB2E22EEEE004A9D9F /* ContactFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactFormView.swift; sourceTree = ""; }; + EA2469DC2E22EEEE004A9D9F /* MainAppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainAppView.swift; sourceTree = ""; }; + EA2469DD2E22EEEE004A9D9F /* SplashScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenView.swift; sourceTree = ""; }; + EA2469DE2E22EEEE004A9D9F /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; + EA2469F62E22EFC3004A9D9F /* ContentModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentModels.swift; sourceTree = ""; }; + EA246A032E2349B8004A9D9F /* EventsListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsListView.swift; sourceTree = ""; }; EA2F9F7E2CF406E800B9F454 /* RTSDA.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RTSDA.app; sourceTree = BUILT_PRODUCTS_DIR; }; + EA3401352E42FC4C006B90C6 /* libchurch_core_tvos.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libchurch_core_tvos.a; sourceTree = ""; }; + EA9A999E2E4413410039692E /* ServiceTimeRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceTimeRow.swift; sourceTree = ""; }; + EA9A99A12E457CF00039692E /* EventDetailShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailShared.swift; sourceTree = ""; }; + EA9A99A32E457CFB0039692E /* EventDetailView+iPad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventDetailView+iPad.swift"; sourceTree = ""; }; + EA9A99A52E457D040039692E /* EventDetailView+iPhone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EventDetailView+iPhone.swift"; sourceTree = ""; }; + EA9A99A72E457D130039692E /* EventDetailViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailViewWrapper.swift; sourceTree = ""; }; + EA9A99AB2E4802EE0039692E /* ChurchCore.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = ChurchCore.xcframework; sourceTree = ""; }; + EA9A99B32E482DD50039692E /* SharedVideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedVideoPlayerView.swift; sourceTree = ""; }; + EA9A99B62E4969DA0039692E /* BulletinsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BulletinsView.swift; sourceTree = ""; }; + EA9A99B82E496A910039692E /* WatchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchView.swift; sourceTree = ""; }; + EA9A99BA2E496B3E0039692E /* ConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectView.swift; sourceTree = ""; }; + EA9A99BC2E496C030039692E /* HomeFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeFeedView.swift; sourceTree = ""; }; + EA9A99BE2E496C0A0039692E /* FilterSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterSheet.swift; sourceTree = ""; }; + EA9A99C02E496CC70039692E /* ScriptureComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptureComponents.swift; sourceTree = ""; }; + EAA1DEC02E4BAE27001414BF /* ContactActionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactActionRow.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -102,6 +96,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + EA9A99AC2E4802EE0039692E /* ChurchCore.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -111,6 +106,7 @@ EA07AAF92D43EF78002ACBF8 /* Frameworks */ = { isa = PBXGroup; children = ( + EA3401352E42FC4C006B90C6 /* libchurch_core_tvos.a */, EA07AAFA2D43EF78002ACBF8 /* XCTest.framework */, ); name = Frameworks; @@ -132,17 +128,6 @@ path = Managers; sourceTree = ""; }; - EA1C83652D43EA4900D8B78F /* Models */ = { - isa = PBXGroup; - children = ( - EA1C83612D43EA4900D8B78F /* Config.swift */, - EA1C83622D43EA4900D8B78F /* Event.swift */, - EA1C83632D43EA4900D8B78F /* Message.swift */, - EA1C83642D43EA4900D8B78F /* Sermon.swift */, - ); - path = Models; - sourceTree = ""; - }; EA1C83672D43EA4900D8B78F /* Preview Content */ = { isa = PBXGroup; children = ( @@ -170,77 +155,93 @@ path = Resources; sourceTree = ""; }; - EA1C837D2D43EA4900D8B78F /* Services */ = { + EA1C83A12D43EA4900D8B78F /* Products */ = { + isa = PBXGroup; + name = Products; + sourceTree = ""; + }; + EA2469C42E22EE2A004A9D9F /* Services */ = { isa = PBXGroup; children = ( - EA1C83762D43EA4900D8B78F /* AppAvailabilityService.swift */, - EA1C83772D43EA4900D8B78F /* BibleService.swift */, - EA1C83782D43EA4900D8B78F /* ConfigService.swift */, - EA1C83792D43EA4900D8B78F /* ImageCache.swift */, - EA1C837A2D43EA4900D8B78F /* JellyfinService.swift */, - EA1C837B2D43EA4900D8B78F /* OwnCastService.swift */, - EA1C837C2D43EA4900D8B78F /* PocketBaseService.swift */, + EA2469C32E22EE2A004A9D9F /* ChurchDataService.swift */, ); path = Services; sourceTree = ""; }; - EA1C83902D43EA4900D8B78F /* ViewModels */ = { + EA2469C82E22EEEE004A9D9F /* Components */ = { isa = PBXGroup; children = ( - EA1C838C2D43EA4900D8B78F /* EventsViewModel.swift */, - EA1C838D2D43EA4900D8B78F /* MessagesViewModel.swift */, - EA1C838E2D43EA4900D8B78F /* OwncastViewModel.swift */, - EA1C838F2D43EA4900D8B78F /* SermonBrowserViewModel.swift */, - EA019ECE2D978167002FC58F /* BulletinViewModel.swift */, + EA2469C62E22EEEE004A9D9F /* ContentCards.swift */, + EA2469C72E22EEEE004A9D9F /* FeedItemCard.swift */, + EA9A999E2E4413410039692E /* ServiceTimeRow.swift */, + EA9A99C02E496CC70039692E /* ScriptureComponents.swift */, + EAA1DEC02E4BAE27001414BF /* ContactActionRow.swift */, ); - path = ViewModels; + path = Components; sourceTree = ""; }; - EA1C83A02D43EA4900D8B78F /* Views */ = { + EA2469CC2E22EEEE004A9D9F /* Detail */ = { isa = PBXGroup; children = ( - EA1C83912D43EA4900D8B78F /* BeliefsView.swift */, - EA1C83922D43EA4900D8B78F /* BulletinView.swift */, - EA1C83932D43EA4900D8B78F /* CachedAsyncImage.swift */, - EA1C83942D43EA4900D8B78F /* ContactFormView.swift */, - EA1C83952D43EA4900D8B78F /* ContentView.swift */, - EA1C83962D43EA4900D8B78F /* EventCard.swift */, - EA1C83972D43EA4900D8B78F /* EventDetailView.swift */, - EA1C83982D43EA4900D8B78F /* EventsView.swift */, - EA1C83992D43EA4900D8B78F /* JellyfinPlayerView.swift */, - EA1C839A2D43EA4900D8B78F /* LivestreamCard.swift */, - EA1C839B2D43EA4900D8B78F /* MessageCard.swift */, - EA1C839C2D43EA4900D8B78F /* MessagesView.swift */, - EA1C839D2D43EA4900D8B78F /* OwncastView.swift */, - EA1C839E2D43EA4900D8B78F /* SplashScreenView.swift */, - EA1C839F2D43EA4900D8B78F /* VideoPlayerView.swift */, + EA2469C92E22EEEE004A9D9F /* BulletinDetailView.swift */, + EA9A99A12E457CF00039692E /* EventDetailShared.swift */, + EA9A99A32E457CFB0039692E /* EventDetailView+iPad.swift */, + EA9A99A52E457D040039692E /* EventDetailView+iPhone.swift */, + EA9A99A72E457D130039692E /* EventDetailViewWrapper.swift */, + ); + path = Detail; + sourceTree = ""; + }; + EA2469DF2E22EEEE004A9D9F /* Views */ = { + isa = PBXGroup; + children = ( + EA2469C82E22EEEE004A9D9F /* Components */, + EA2469CC2E22EEEE004A9D9F /* Detail */, + EA2469D92E22EEEE004A9D9F /* BeliefsView.swift */, + EA2469DA2E22EEEE004A9D9F /* CachedAsyncImage.swift */, + EA2469DB2E22EEEE004A9D9F /* ContactFormView.swift */, + EA2469DC2E22EEEE004A9D9F /* MainAppView.swift */, + EA2469DD2E22EEEE004A9D9F /* SplashScreenView.swift */, + EA2469DE2E22EEEE004A9D9F /* VideoPlayerView.swift */, + EA246A032E2349B8004A9D9F /* EventsListView.swift */, + EA9A99B32E482DD50039692E /* SharedVideoPlayerView.swift */, + EA9A99B62E4969DA0039692E /* BulletinsView.swift */, + EA9A99B82E496A910039692E /* WatchView.swift */, + EA9A99BA2E496B3E0039692E /* ConnectView.swift */, + EA9A99BC2E496C030039692E /* HomeFeedView.swift */, + EA9A99BE2E496C0A0039692E /* FilterSheet.swift */, ); path = Views; sourceTree = ""; }; - EA1C83A12D43EA4900D8B78F /* Products */ = { + EA2469F72E22EFC3004A9D9F /* Models */ = { isa = PBXGroup; - name = Products; + children = ( + EA2469F62E22EFC3004A9D9F /* ContentModels.swift */, + ); + path = Models; sourceTree = ""; }; EA2F9F752CF406E800B9F454 = { isa = PBXGroup; children = ( EA1C835B2D43EA4900D8B78F /* Assets.xcassets */, + EA2469F72E22EFC3004A9D9F /* Models */, EA1C835D2D43EA4900D8B78F /* Extensions */, EA1C835E2D43EA4900D8B78F /* Info.plist */, EA1C83602D43EA4900D8B78F /* Managers */, - EA1C83652D43EA4900D8B78F /* Models */, EA1C83672D43EA4900D8B78F /* Preview Content */, EA1C836D2D43EA4900D8B78F /* Resources */, EA1C836E2D43EA4900D8B78F /* RTSDA.entitlements */, + EA2469C42E22EE2A004A9D9F /* Services */, EA1C836F2D43EA4900D8B78F /* RTSDA.xcodeproj */, EA1C83702D43EA4900D8B78F /* RTSDAApp.swift */, - EA1C837D2D43EA4900D8B78F /* Services */, - EA1C83902D43EA4900D8B78F /* ViewModels */, - EA1C83A02D43EA4900D8B78F /* Views */, EA07AAF92D43EF78002ACBF8 /* Frameworks */, EA2F9F7F2CF406E800B9F454 /* Products */, + EA24699D2E222470004A9D9F /* church_core.swift */, + EA2469B52E22DDD4004A9D9F /* libchurch_core.a */, + EA2469DF2E22EEEE004A9D9F /* Views */, + EA9A99AB2E4802EE0039692E /* ChurchCore.xcframework */, ); sourceTree = ""; }; @@ -282,7 +283,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1610; - LastUpgradeCheck = 1630; + LastUpgradeCheck = 2600; TargetAttributes = { EA2F9F7D2CF406E800B9F454 = { CreatedOnToolsVersion = 16.1; @@ -337,40 +338,35 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - EA1C83A42D43EA4900D8B78F /* SermonBrowserViewModel.swift in Sources */, EA1C83A52D43EA4900D8B78F /* RTSDAApp.swift in Sources */, - EA1C83A62D43EA4900D8B78F /* EventsViewModel.swift in Sources */, - EA1C83A72D43EA4900D8B78F /* LivestreamCard.swift in Sources */, - EA1C83A92D43EA4900D8B78F /* Event.swift in Sources */, - EA1C83AA2D43EA4900D8B78F /* MessagesView.swift in Sources */, - EA1C83AB2D43EA4900D8B78F /* EventsView.swift in Sources */, - EA1C83AC2D43EA4900D8B78F /* EventDetailView.swift in Sources */, - EA1C83AD2D43EA4900D8B78F /* CachedAsyncImage.swift in Sources */, - EA1C83AE2D43EA4900D8B78F /* ImageCache.swift in Sources */, - EA1C83AF2D43EA4900D8B78F /* OwncastView.swift in Sources */, - EA1C83B02D43EA4900D8B78F /* ContentView.swift in Sources */, - EA1C83B12D43EA4900D8B78F /* Config.swift in Sources */, - EA1C83B22D43EA4900D8B78F /* OwncastViewModel.swift in Sources */, - EA1C83B32D43EA4900D8B78F /* PocketBaseService.swift in Sources */, - EA1C83B42D43EA4900D8B78F /* ContactFormView.swift in Sources */, - EA1C83B52D43EA4900D8B78F /* JellyfinService.swift in Sources */, - EA1C83B62D43EA4900D8B78F /* EventCard.swift in Sources */, - EA1C83B72D43EA4900D8B78F /* ConfigService.swift in Sources */, - EA1C83B82D43EA4900D8B78F /* MessageCard.swift in Sources */, - EA1C83B92D43EA4900D8B78F /* Sermon.swift in Sources */, + EA9A99BB2E496B3E0039692E /* ConnectView.swift in Sources */, + EA2469F82E22EFC3004A9D9F /* ContentModels.swift in Sources */, + EA9A99B92E496A910039692E /* WatchView.swift in Sources */, EA1C83BA2D43EA4900D8B78F /* PermissionsManager.swift in Sources */, - EA1C83BC2D43EA4900D8B78F /* BeliefsView.swift in Sources */, - EA1C83BD2D43EA4900D8B78F /* BulletinView.swift in Sources */, - EA1C83BE2D43EA4900D8B78F /* Message.swift in Sources */, - EA1C83BF2D43EA4900D8B78F /* BibleService.swift in Sources */, + EA9A99B42E482DD50039692E /* SharedVideoPlayerView.swift in Sources */, + EA9A99A62E457D040039692E /* EventDetailView+iPhone.swift in Sources */, + EA9A99A22E457CF00039692E /* EventDetailShared.swift in Sources */, + EA2469C52E22EE2A004A9D9F /* ChurchDataService.swift in Sources */, EA1C83C02D43EA4900D8B78F /* Color+Extensions.swift in Sources */, - EA1C83C12D43EA4900D8B78F /* VideoPlayerView.swift in Sources */, - EA1C83C22D43EA4900D8B78F /* MessagesViewModel.swift in Sources */, - EA1C83C32D43EA4900D8B78F /* OwnCastService.swift in Sources */, - EA1C83C42D43EA4900D8B78F /* SplashScreenView.swift in Sources */, - EA019ECF2D978167002FC58F /* BulletinViewModel.swift in Sources */, - EA1C83C52D43EA4900D8B78F /* JellyfinPlayerView.swift in Sources */, - EA1C83C62D43EA4900D8B78F /* AppAvailabilityService.swift in Sources */, + EA9A999F2E4413410039692E /* ServiceTimeRow.swift in Sources */, + EA9A99A82E457D130039692E /* EventDetailViewWrapper.swift in Sources */, + EA2469E42E22EEEE004A9D9F /* VideoPlayerView.swift in Sources */, + EA2469E52E22EEEE004A9D9F /* MainAppView.swift in Sources */, + EAA1DEC12E4BAE27001414BF /* ContactActionRow.swift in Sources */, + EA246A042E2349B8004A9D9F /* EventsListView.swift in Sources */, + EA2469E92E22EEEE004A9D9F /* SplashScreenView.swift in Sources */, + EA2469EA2E22EEEE004A9D9F /* ContactFormView.swift in Sources */, + EA2469EB2E22EEEE004A9D9F /* CachedAsyncImage.swift in Sources */, + EA2469EC2E22EEEE004A9D9F /* FeedItemCard.swift in Sources */, + EA9A99C12E496CC70039692E /* ScriptureComponents.swift in Sources */, + EA2469F02E22EEEE004A9D9F /* ContentCards.swift in Sources */, + EA9A99BF2E496C0A0039692E /* FilterSheet.swift in Sources */, + EA2469F32E22EEEE004A9D9F /* BeliefsView.swift in Sources */, + EA2469F52E22EEEE004A9D9F /* BulletinDetailView.swift in Sources */, + EA9A99BD2E496C030039692E /* HomeFeedView.swift in Sources */, + EA9A99B72E4969DA0039692E /* BulletinsView.swift in Sources */, + EA9A99A42E457CFB0039692E /* EventDetailView+iPad.swift in Sources */, + EA24699E2E222470004A9D9F /* church_core.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -437,8 +433,10 @@ NEW_SETTING = ""; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; }; name = Debug; }; @@ -495,7 +493,9 @@ MTL_FAST_MATH = YES; NEW_SETTING = ""; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_PRECOMPILE_BRIDGING_HEADER = NO; VALIDATE_PRODUCT = YES; }; name = Release; @@ -509,6 +509,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Preview Content\""; + DEVELOPMENT_TEAM = TQMND62F2W; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Info.plist; @@ -532,10 +533,19 @@ "$(inherited)", "@executable_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = com.rtsda.appr; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "RTSDA-Bridging-Header.h"; SWIFT_VERSION = 5.0; SYSTEM_FRAMEWORK_SEARCH_PATHS = ""; TARGETED_DEVICE_FAMILY = "1,2"; @@ -551,6 +561,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Preview Content\""; + DEVELOPMENT_TEAM = TQMND62F2W; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Info.plist; @@ -574,10 +585,19 @@ "$(inherited)", "@executable_path/Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = com.rtsda.appr; PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = "RTSDA-Bridging-Header.h"; SWIFT_VERSION = 5.0; SYSTEM_FRAMEWORK_SEARCH_PATHS = ""; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/RTSDAApp.swift b/RTSDAApp.swift index 9ed4cc8..9729d35 100644 --- a/RTSDAApp.swift +++ b/RTSDAApp.swift @@ -9,20 +9,12 @@ import SwiftUI @main struct RTSDAApp: App { - @StateObject private var configService = ConfigService.shared - - init() { - // Enable standard orientations (portrait and landscape) - if #available(iOS 16.0, *) { - UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.forEach { windowScene in - windowScene.requestGeometryUpdate(.iOS(interfaceOrientations: [.portrait, .landscapeLeft, .landscapeRight])) - } - } - } + @State private var dataService = ChurchDataService() var body: some Scene { WindowGroup { SplashScreenView() + .environment(dataService) } } } diff --git a/RUST_CRATE_IMPROVEMENTS.md b/RUST_CRATE_IMPROVEMENTS.md new file mode 100644 index 0000000..22a109d --- /dev/null +++ b/RUST_CRATE_IMPROVEMENTS.md @@ -0,0 +1,44 @@ +# Rust Crate Improvements Needed + +## HTML Tag Stripping in Scripture Readings + +**Issue**: Scripture readings contain HTML tags that appear in the iOS UI. + +**Solution**: Update the Rust crate to strip HTML tags from scripture_reading fields in the ClientBulletin model. + +**Suggested Implementation**: +```rust +// In src/models/client_models.rs - ClientBulletin::from() implementation +impl From for ClientBulletin { + fn from(bulletin: Bulletin) -> Self { + Self { + // ... other fields + scripture_reading: strip_html_tags(&bulletin.scripture_reading), + // ... other fields + } + } +} + +// Helper function to strip HTML tags +fn strip_html_tags(text: &str) -> String { + // Use a regex or HTML parsing library to remove HTML tags + // This could use the `html2text` crate that's already a dependency + html2text::from_read(text.as_bytes(), 80) + .replace('\n', " ") + .split_whitespace() + .collect::>() + .join(" ") +} +``` + +**Benefits**: +- Clean data for all client platforms +- Consistent text rendering +- Better performance (processed once on server) +- Easier maintenance + +**Files to Update**: +- `src/models/client_models.rs` +- Possibly `src/models/bulletin.rs` if the cleaning should happen earlier + +**Priority**: Medium - affects user experience but doesn't break functionality \ No newline at end of file diff --git a/Services/AppAvailabilityService.swift b/Services/AppAvailabilityService.swift deleted file mode 100644 index 7299fe3..0000000 --- a/Services/AppAvailabilityService.swift +++ /dev/null @@ -1,178 +0,0 @@ -import Foundation -import UIKit - -class AppAvailabilityService { - static let shared = AppAvailabilityService() - - private var availabilityCache: [String: Bool] = [:] - - // Common URL schemes - struct Schemes { - static let bible = "youversion://" - static let egw = "egw-ios://" - static let hymnal = "adventisthymnarium://" - static let sabbathSchool = "com.googleusercontent.apps.443920152945-d0kf5h2dubt0jbcntq8l0qeg6lbpgn60://" - static let sabbathSchoolAlt = "https://sabbath-school.adventech.io" - static let egwWritingsWeb = "https://m.egwwritings.org/en/folders/2" - static let facebook = "https://www.facebook.com/rockvilletollandsdachurch/" - static let tiktok = "https://www.tiktok.com/@rockvilletollandsda" - static let spotify = "spotify://show/2ARQaUBaGnVTiF9syrKDvO" - static let podcasts = "podcasts://podcasts.apple.com/us/podcast/rockville-tolland-sda-church/id1630777684" - } - - // App Store fallback URLs - struct AppStoreURLs { - static let sabbathSchool = "https://apps.apple.com/us/app/sabbath-school/id895272167" - static let egwWritings = "https://apps.apple.com/us/app/egw-writings-2/id994076136" - static let egwWritingsWeb = "https://m.egwwritings.org/en/folders/2" - static let hymnal = "https://apps.apple.com/us/app/hymnal-adventist/id6738877733" - static let bible = "https://apps.apple.com/us/app/bible/id282935706" - static let facebook = "https://apps.apple.com/us/app/facebook/id284882215" - static let tiktok = "https://apps.apple.com/us/app/tiktok/id835599320" - static let spotify = "https://apps.apple.com/us/app/spotify-music-and-podcasts/id324684580" - static let podcasts = "https://apps.apple.com/us/app/apple-podcasts/id525463029" - } - - private init() { - // Check for common apps at launch - checkAvailability(urlScheme: Schemes.sabbathSchool) - checkAvailability(urlScheme: Schemes.egw) - checkAvailability(urlScheme: Schemes.bible) - checkAvailability(urlScheme: Schemes.hymnal) - checkAvailability(urlScheme: Schemes.facebook) - checkAvailability(urlScheme: Schemes.tiktok) - checkAvailability(urlScheme: Schemes.spotify) - checkAvailability(urlScheme: Schemes.podcasts) - } - - func isAppInstalled(urlScheme: String) -> Bool { - if let cached = availabilityCache[urlScheme] { - return cached - } - return checkAvailability(urlScheme: urlScheme) - } - - @discardableResult - private func checkAvailability(urlScheme: String) -> Bool { - guard let url = URL(string: urlScheme) else { - print("⚠️ Failed to create URL for scheme: \(urlScheme)") - return false - } - let isAvailable = UIApplication.shared.canOpenURL(url) - print("📱 App availability for \(urlScheme): \(isAvailable)") - availabilityCache[urlScheme] = isAvailable - return isAvailable - } - - func openApp(urlScheme: String, fallbackURL: String) { - // First try the URL scheme - if let appUrl = URL(string: urlScheme) { - print("🔗 Attempting to open URL: \(appUrl)") - - // Check if we can open the URL - if UIApplication.shared.canOpenURL(appUrl) { - print("✅ Opening app URL: \(appUrl)") - UIApplication.shared.open(appUrl) { success in - if !success { - print("❌ Failed to open app URL: \(appUrl)") - self.handleFallback(urlScheme: urlScheme, fallbackURL: fallbackURL) - } - } - } else { - print("❌ Cannot open app URL: \(appUrl)") - handleFallback(urlScheme: urlScheme, fallbackURL: fallbackURL) - } - } else { - print("⚠️ Failed to create URL: \(urlScheme)") - handleFallback(urlScheme: urlScheme, fallbackURL: fallbackURL) - } - } - - // Open a specific hymn by number - func openHymnByNumber(_ hymnNumber: Int) { - // Format: adventisthymnarium://hymn?number=123 - let hymnalScheme = "\(Schemes.hymnal)hymn?number=\(hymnNumber)" - print("🎵 Attempting to open hymn #\(hymnNumber): \(hymnalScheme)") - - if let hymnUrl = URL(string: hymnalScheme) { - if UIApplication.shared.canOpenURL(hymnUrl) { - UIApplication.shared.open(hymnUrl) { success in - if !success { - print("❌ Failed to open hymn #\(hymnNumber)") - self.openApp(urlScheme: Schemes.hymnal, fallbackURL: AppStoreURLs.hymnal) - } - } - } else { - print("❌ Cannot open hymn #\(hymnNumber)") - openApp(urlScheme: Schemes.hymnal, fallbackURL: AppStoreURLs.hymnal) - } - } else { - print("⚠️ Failed to create URL for hymn #\(hymnNumber)") - openApp(urlScheme: Schemes.hymnal, fallbackURL: AppStoreURLs.hymnal) - } - } - - // Open a specific responsive reading by number - func openResponsiveReadingByNumber(_ readingNumber: Int) { - // Format: adventisthymnarium://reading?number=123 - let hymnalScheme = "\(Schemes.hymnal)reading?number=\(readingNumber)" - print("📖 Attempting to open responsive reading #\(readingNumber): \(hymnalScheme)") - - if let readingUrl = URL(string: hymnalScheme) { - if UIApplication.shared.canOpenURL(readingUrl) { - UIApplication.shared.open(readingUrl) { success in - if !success { - print("❌ Failed to open responsive reading #\(readingNumber)") - self.openApp(urlScheme: Schemes.hymnal, fallbackURL: AppStoreURLs.hymnal) - } - } - } else { - print("❌ Cannot open responsive reading #\(readingNumber)") - openApp(urlScheme: Schemes.hymnal, fallbackURL: AppStoreURLs.hymnal) - } - } else { - print("⚠️ Failed to create URL for responsive reading #\(readingNumber)") - openApp(urlScheme: Schemes.hymnal, fallbackURL: AppStoreURLs.hymnal) - } - } - - private func handleFallback(urlScheme: String, fallbackURL: String) { - // Try the App Store URL first - if let fallback = URL(string: fallbackURL) { - print("⬇️ Opening App Store: \(fallback)") - UIApplication.shared.open(fallback) { success in - if !success { - print("❌ Failed to open App Store URL: \(fallback)") - - // Handle web fallbacks if App Store fails - if urlScheme == Schemes.egw { - if let webUrl = URL(string: Schemes.egwWritingsWeb) { - print("✅ Opening EGW mobile web URL: \(webUrl)") - UIApplication.shared.open(webUrl) - } - } else if urlScheme == Schemes.sabbathSchool { - if let altUrl = URL(string: Schemes.sabbathSchoolAlt) { - print("✅ Opening Sabbath School web app: \(altUrl)") - UIApplication.shared.open(altUrl) - } - } - } - } - } else { - print("❌ Failed to create App Store URL: \(fallbackURL)") - - // Handle web fallbacks if App Store URL is invalid - if urlScheme == Schemes.egw { - if let webUrl = URL(string: Schemes.egwWritingsWeb) { - print("✅ Opening EGW mobile web URL: \(webUrl)") - UIApplication.shared.open(webUrl) - } - } else if urlScheme == Schemes.sabbathSchool { - if let altUrl = URL(string: Schemes.sabbathSchoolAlt) { - print("✅ Opening Sabbath School web app: \(altUrl)") - UIApplication.shared.open(altUrl) - } - } - } - } -} diff --git a/Services/BibleService.swift b/Services/BibleService.swift deleted file mode 100644 index 5631d67..0000000 --- a/Services/BibleService.swift +++ /dev/null @@ -1,158 +0,0 @@ -import Foundation - -@MainActor -class BibleService { - static let shared = BibleService() - private let pocketBaseService = PocketBaseService.shared - - private init() {} - - struct Verse: Identifiable, Codable { - let id: String - let reference: String - let text: String - let isActive: Bool - - enum CodingKeys: String, CodingKey { - case id - case reference - case text - case isActive = "is_active" - } - } - - struct VersesRecord: Codable { - let collectionId: String - let collectionName: String - let created: String - let id: String - let updated: String - let verses: VersesData - - struct VersesData: Codable { - let id: String - let verses: [Verse] - } - } - - private var cachedVerses: [Verse]? - - func getRandomVerse() async throws -> (verse: String, reference: String) { - let verses = try await getVerses() - print("Total verses available: \(verses.count)") - - guard !verses.isEmpty else { - print("No verses available") - throw NSError(domain: "BibleService", code: -1, userInfo: [NSLocalizedDescriptionKey: "No verses available"]) - } - - let randomVerse = verses.randomElement()! - print("Selected random verse: \(randomVerse.reference)") - return (verse: randomVerse.text, reference: randomVerse.reference) - } - - func getVerse(reference: String) async throws -> (verse: String, reference: String) { - print("Looking up verse with reference: \(reference)") - - // Convert API-style reference (e.g., "JER.29.11") to display format ("Jeremiah 29:11") - let displayReference = reference - .replacingOccurrences(of: "\\.", with: " ", options: .regularExpression) - .replacingOccurrences(of: "([A-Z]+)", with: "$1 ", options: .regularExpression) - .trimmingCharacters(in: .whitespaces) - - print("Converted reference to: \(displayReference)") - - let verses = try await getVerses() - print("Found \(verses.count) verses") - - if let verse = verses.first(where: { $0.reference.lowercased() == displayReference.lowercased() }) { - print("Found matching verse: \(verse.reference)") - return (verse: verse.text, reference: verse.reference) - } - - print("No matching verse found for reference: \(displayReference)") - throw NSError(domain: "BibleService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Verse not found"]) - } - - private func getVerses() async throws -> [Verse] { - // Return cached verses if available - if let cached = cachedVerses { - print("Returning cached verses") - return cached - } - - print("Fetching verses from PocketBase") - // Fetch from PocketBase - let endpoint = "\(PocketBaseService.shared.baseURL)/bible_verses/records/nkf01o1q3456flr" - - guard let url = URL(string: endpoint) else { - print("Invalid URL: \(endpoint)") - throw URLError(.badURL) - } - - var request = URLRequest(url: url) - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - print("Making request to: \(endpoint)") - let (data, response) = try await URLSession.shared.data(for: request) - - guard let httpResponse = response as? HTTPURLResponse else { - print("Invalid response type") - throw URLError(.badServerResponse) - } - - print("Response status code: \(httpResponse.statusCode)") - - guard httpResponse.statusCode == 200 else { - if let errorString = String(data: data, encoding: .utf8) { - print("Error response from server: \(errorString)") - } - throw URLError(.badServerResponse) - } - - // Print raw response for debugging - if let rawResponse = String(data: data, encoding: .utf8) { - print("Raw response from PocketBase:") - print(rawResponse) - } - - let decoder = JSONDecoder() - do { - let versesRecord = try decoder.decode(VersesRecord.self, from: data) - - // Cache the verses - cachedVerses = versesRecord.verses.verses - print("Successfully fetched and cached \(versesRecord.verses.verses.count) verses") - - return versesRecord.verses.verses - } catch { - print("Failed to decode response: \(error)") - if let decodingError = error as? DecodingError { - switch decodingError { - case .keyNotFound(let key, let context): - print("Missing key: \(key.stringValue) in \(context.debugDescription)") - case .typeMismatch(let type, let context): - print("Type mismatch: expected \(type) in \(context.debugDescription)") - case .valueNotFound(let type, let context): - print("Value not found: expected \(type) in \(context.debugDescription)") - case .dataCorrupted(let context): - print("Data corrupted: \(context.debugDescription)") - @unknown default: - print("Unknown decoding error") - } - } - throw error - } - } - - func testAllVerses() async throws { - print("\n=== Testing All Verses ===\n") - let verses = try await getVerses() - for verse in verses { - print("Reference: \(verse.reference)") - print("Verse: \(verse.text)") - print("-------------------\n") - } - print("=== Test Complete ===\n") - } -} diff --git a/Services/BulletinService.swift b/Services/BulletinService.swift deleted file mode 100644 index c1f0ac6..0000000 --- a/Services/BulletinService.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Foundation - -class BulletinService { - static let shared = BulletinService() - private let pocketBaseService = PocketBaseService.shared - - private init() {} - - func getBulletins(activeOnly: Bool = true) async throws -> [Bulletin] { - var urlString = "\(pocketBaseService.baseURL)/api/collections/bulletins/records?sort=-date" - - if activeOnly { - urlString += "&filter=(is_active=true)" - } - - guard let url = URL(string: urlString) else { - throw URLError(.badURL) - } - - var request = URLRequest(url: url) - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let (data, response) = try await URLSession.shared.data(for: request) - - guard let httpResponse = response as? HTTPURLResponse, - (200...299).contains(httpResponse.statusCode) else { - throw URLError(.badServerResponse) - } - - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - - struct BulletinResponse: Codable { - let items: [Bulletin] - } - - let bulletinResponse = try decoder.decode(BulletinResponse.self, from: data) - return bulletinResponse.items - } - - func getBulletin(id: String) async throws -> Bulletin { - let urlString = "\(pocketBaseService.baseURL)/api/collections/bulletins/records/\(id)" - - guard let url = URL(string: urlString) else { - throw URLError(.badURL) - } - - var request = URLRequest(url: url) - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let (data, response) = try await URLSession.shared.data(for: request) - - guard let httpResponse = response as? HTTPURLResponse, - (200...299).contains(httpResponse.statusCode) else { - throw URLError(.badServerResponse) - } - - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - - return try decoder.decode(Bulletin.self, from: data) - } - - func getLatestBulletin() async throws -> Bulletin? { - let bulletins = try await getBulletins(activeOnly: true) - return bulletins.first - } -} \ No newline at end of file diff --git a/Services/ChurchDataService.swift b/Services/ChurchDataService.swift new file mode 100644 index 0000000..405695a --- /dev/null +++ b/Services/ChurchDataService.swift @@ -0,0 +1,403 @@ +import Foundation +import Observation + +// MARK: - Media Type Enum +enum MediaType: String, CaseIterable { + case sermons = "sermons" + case livestreams = "livestreams" + + var displayName: String { + switch self { + case .sermons: + return "Sermons" + case .livestreams: + return "Live Archives" + } + } + + var icon: String { + switch self { + case .sermons: + return "play.rectangle.fill" + case .livestreams: + return "dot.radiowaves.left.and.right" + } + } +} + +@Observable +class ChurchDataService { + // MARK: - Properties + + // State + var isLoading = false + var error: Error? + + // Content + var events: [ChurchEvent] = [] + var sermons: [Sermon] = [] + var livestreamArchives: [Sermon] = [] + var bulletins: [ChurchBulletin] = [] + var dailyVerse: BibleVerse? + + // Stream status + var isStreamLive = false + + // Media Type Selection + var currentMediaType: MediaType = .sermons + + // Feed + var feedItems: [FeedItem] = [] + + // MARK: - Public Methods + + /// Load all content for the home feed + func loadHomeFeed() async { + await withTaskGroup(of: Void.self) { group in + group.addTask { await self.loadEvents() } + group.addTask { await self.loadRecentSermons() } + group.addTask { await self.loadRecentBulletins() } + group.addTask { await self.loadDailyVerse() } + group.addTask { await self.loadStreamStatus() } + } + + await updateFeed() + } + + /// Load upcoming events (for home feed - limited) + func loadEvents() async { + do { + isLoading = true + error = nil + + let eventsJson = fetchEventsJson() + + // Use Rust function for JSON parsing - NO business logic in Swift! + let eventsArrayJson = parseEventsFromJson(eventsJson: eventsJson) + let data = eventsArrayJson.data(using: .utf8) ?? Data() + let allEvents = try JSONDecoder().decode([ChurchEvent].self, from: data) + + await MainActor.run { + // For home feed, limit to 5 events + self.events = Array(allEvents.prefix(5)) + self.isLoading = false + + } + } catch { + print("❌ Failed to load events: \(error)") + await MainActor.run { + self.error = error + self.isLoading = false + } + } + } + + /// Load all events (for events list view) + func loadAllEvents() async { + do { + isLoading = true + error = nil + + let eventsJson = fetchEventsJson() + + // Use Rust function for JSON parsing - NO business logic in Swift! + let eventsArrayJson = parseEventsFromJson(eventsJson: eventsJson) + let data = eventsArrayJson.data(using: .utf8) ?? Data() + let allEvents = try JSONDecoder().decode([ChurchEvent].self, from: data) + + await MainActor.run { + self.events = allEvents + self.isLoading = false + + } + } catch { + print("❌ Failed to load all events: \(error)") + await MainActor.run { + self.error = error + self.isLoading = false + } + } + } + + /// Load recent sermons + func loadRecentSermons() async { + do { + let sermonsJson = fetchSermonsJson() + + // Use Rust function for JSON parsing - NO business logic in Swift! + let validatedJson = parseSermonsFromJson(sermonsJson: sermonsJson) + let data = validatedJson.data(using: .utf8) ?? Data() + let sermonList = try JSONDecoder().decode([Sermon].self, from: data) + + await MainActor.run { + // Get the 5 most recent sermons + self.sermons = Array(sermonList.prefix(5)) + } + } catch { + await MainActor.run { + self.error = error + } + } + } + + /// Load all sermons (for Watch tab) + func loadAllSermons() async { + do { + isLoading = true + error = nil + + let sermonsJson = fetchSermonsJson() + + // Use Rust function for JSON parsing - NO business logic in Swift! + let validatedJson = parseSermonsFromJson(sermonsJson: sermonsJson) + let data = validatedJson.data(using: .utf8) ?? Data() + let sermonList = try JSONDecoder().decode([Sermon].self, from: data) + + await MainActor.run { + self.sermons = sermonList + self.isLoading = false + + } + } catch { + print("❌ Failed to load sermons: \(error)") + await MainActor.run { + self.error = error + self.isLoading = false + } + } + } + + /// Load all livestream archives (for Watch tab) + func loadAllLivestreamArchives() async { + do { + isLoading = true + error = nil + + let archivesJson = fetchLivestreamArchiveJson() + + // Use Rust function for JSON parsing - NO business logic in Swift! + let validatedJson = parseSermonsFromJson(sermonsJson: archivesJson) + let data = validatedJson.data(using: .utf8) ?? Data() + let archiveList = try JSONDecoder().decode([Sermon].self, from: data) + + await MainActor.run { + self.livestreamArchives = archiveList + self.isLoading = false + + } + } catch { + print("❌ Failed to load livestream archives: \(error)") + await MainActor.run { + self.error = error + self.isLoading = false + } + } + } + + /// Load content based on current media type + func loadCurrentMediaType() async { + switch currentMediaType { + case .sermons: + await loadAllSermons() + case .livestreams: + await loadAllLivestreamArchives() + } + } + + /// Switch media type and load appropriate content + func switchMediaType(to newType: MediaType) async { + await MainActor.run { + self.currentMediaType = newType + } + await loadCurrentMediaType() + } + + /// Get current content based on media type + var currentContent: [Sermon] { + switch currentMediaType { + case .sermons: + return sermons + case .livestreams: + return livestreamArchives + } + } + + /// Load recent bulletins + func loadRecentBulletins() async { + do { + let bulletinsJson = fetchBulletinsJson() + + // Use Rust function for JSON parsing - NO business logic in Swift! + let validatedJson = parseBulletinsFromJson(bulletinsJson: bulletinsJson) + let data = validatedJson.data(using: .utf8) ?? Data() + let bulletinList = try JSONDecoder().decode([ChurchBulletin].self, from: data) + + await MainActor.run { + // Get the 3 most recent bulletins + self.bulletins = Array(bulletinList.prefix(3)) + } + } catch { + await MainActor.run { + self.error = error + } + } + } + + /// Load all bulletins (for Discover tab) + func loadAllBulletins() async { + do { + isLoading = true + error = nil + + let bulletinsJson = fetchBulletinsJson() + + // Use Rust function for JSON parsing - NO business logic in Swift! + let validatedJson = parseBulletinsFromJson(bulletinsJson: bulletinsJson) + let data = validatedJson.data(using: .utf8) ?? Data() + let bulletinList = try JSONDecoder().decode([ChurchBulletin].self, from: data) + + await MainActor.run { + self.bulletins = bulletinList + self.isLoading = false + } + } catch { + await MainActor.run { + self.error = error + self.isLoading = false + } + } + } + + /// Load daily Bible verse + func loadDailyVerse() async { + do { + let verseJson = fetchRandomBibleVerseJson() + + // Use Rust function for JSON parsing - NO business logic in Swift! + let validatedJson = parseBibleVerseFromJson(verseJson: verseJson) + let data = validatedJson.data(using: .utf8) ?? Data() + let verse = try JSONDecoder().decode(BibleVerse.self, from: data) + + await MainActor.run { + self.dailyVerse = verse + + } + } catch { + print("❌ Failed to load daily verse: \(error)") + print("📖 Raw JSON was: \(fetchRandomBibleVerseJson())") + } + } + + /// Search Bible verses + func searchVerses(_ query: String) async -> [BibleVerse] { + do { + let versesJson = fetchBibleVerseJson(query: query) + + // Use Rust function for JSON parsing - NO business logic in Swift! + let validatedJson = parseBibleVerseFromJson(verseJson: versesJson) + let data = validatedJson.data(using: .utf8) ?? Data() + return try JSONDecoder().decode([BibleVerse].self, from: data) + } catch { + print("Failed to search verses: \(error)") + return [] + } + } + + /// Load stream status to check if live + func loadStreamStatus() async { + // Use Rust function directly - NO JSON parsing in Swift! + // Note: Using simple extraction until getStreamLiveStatus() binding is available + let statusJson = fetchStreamStatusJson() + let isLive = statusJson.contains("\"is_live\":true") + await MainActor.run { + self.isStreamLive = isLive + } + } + + /// Submit contact form + func submitContact(name: String, email: String, subject: String, message: String, phone: String = "") async -> Bool { + + + let resultJson = submitContactV2Json( + name: name, + email: email, + subject: subject, + message: message, + phone: phone + ) + + + + // Use Rust function for JSON parsing - NO business logic in Swift! + let validatedJson = parseContactResultFromJson(resultJson: resultJson) + let data = validatedJson.data(using: .utf8) ?? Data() + + do { + let result = try JSONDecoder().decode(ContactSubmissionResult.self, from: data) + return result.success + } catch { + + return false + } + } + + // MARK: - Private Methods + + /// Create unified feed from all content + private func updateFeed() async { + await MainActor.run { + guard let eventsJson = try? JSONEncoder().encode(events), + let sermonsJson = try? JSONEncoder().encode(sermons), + let bulletinsJson = try? JSONEncoder().encode(bulletins), + let verseJson = try? JSONEncoder().encode(dailyVerse), + let eventsJsonString = String(data: eventsJson, encoding: .utf8), + let sermonsJsonString = String(data: sermonsJson, encoding: .utf8), + let bulletinsJsonString = String(data: bulletinsJson, encoding: .utf8), + let verseJsonString = String(data: verseJson, encoding: .utf8) else { + // Simple fallback - empty feed + self.feedItems = [] + return + } + + let feedJson = generateHomeFeedJson( + eventsJson: eventsJsonString, + sermonsJson: sermonsJsonString, + bulletinsJson: bulletinsJsonString, + verseJson: verseJsonString + ) + + guard let data = feedJson.data(using: .utf8), + let rustFeedItems = try? JSONDecoder().decode([RustFeedItem].self, from: data) else { + // Simple fallback - empty feed + self.feedItems = [] + return + } + + // Convert to Swift FeedItem models + self.feedItems = rustFeedItems.compactMap { rustItem -> FeedItem? in + let timestamp = parseDate(rustItem.timestamp) ?? Date() + + switch rustItem.feedType { + case .event(let event): + return FeedItem(type: .event(event), timestamp: timestamp) + case .sermon(let sermon): + return FeedItem(type: .sermon(sermon), timestamp: timestamp) + case .bulletin(let bulletin): + return FeedItem(type: .bulletin(bulletin), timestamp: timestamp) + case .verse(let verse): + return FeedItem(type: .verse(verse), timestamp: timestamp) + } + } + } + } + + private func parseDate(_ dateString: String) -> Date? { + let formatter = ISO8601DateFormatter() + return formatter.date(from: dateString) + } +} + +// MARK: - Singleton Access +extension ChurchDataService { + static let shared = ChurchDataService() +} diff --git a/Services/ConfigService.swift b/Services/ConfigService.swift deleted file mode 100644 index 9069d69..0000000 --- a/Services/ConfigService.swift +++ /dev/null @@ -1,63 +0,0 @@ -import Foundation - -@MainActor -class ConfigService: ObservableObject { - static let shared = ConfigService() - private let pocketBaseService = PocketBaseService.shared - - @Published private(set) var config: Config? - @Published private(set) var error: Error? - @Published private(set) var isLoading = false - - private init() {} - - var bibleApiKey: String? { - config?.apiKeys.bibleApiKey - } - - var jellyfinApiKey: String? { - config?.apiKeys.jellyfinApiKey - } - - var churchName: String { - config?.churchName ?? "Rockville-Tolland SDA Church" - } - - var aboutText: String { - config?.aboutText ?? "" - } - - var contactEmail: String { - config?.contactEmail ?? "av@rockvilletollandsda.org" - } - - var contactPhone: String { - config?.contactPhone ?? "8608750450" - } - - var churchAddress: String { - config?.churchAddress ?? "9 Hartford Tpke Tolland CT 06084" - } - - var googleMapsUrl: String { - config?.googleMapsUrl ?? "https://maps.app.goo.gl/Ld4YZFPQGxGRBFJt8" - } - - func loadConfig() async { - isLoading = true - error = nil - - do { - config = try await pocketBaseService.fetchConfig() - } catch { - self.error = error - print("Failed to load app configuration: \(error)") - } - - isLoading = false - } - - func refreshConfig() async { - await loadConfig() - } -} \ No newline at end of file diff --git a/Services/ImageCache.swift b/Services/ImageCache.swift deleted file mode 100644 index e280cd3..0000000 --- a/Services/ImageCache.swift +++ /dev/null @@ -1,23 +0,0 @@ -import SwiftUI - -actor ImageCache { - static let shared = ImageCache() - - private let cache: NSCache = { - let cache = NSCache() - cache.countLimit = 100 // Maximum number of images to cache - return cache - }() - - private init() {} - - func image(for url: URL) -> UIImage? { - let key = url.absoluteString as NSString - return cache.object(forKey: key) - } - - func setImage(_ image: UIImage, for url: URL) { - let key = url.absoluteString as NSString - cache.setObject(image, forKey: key) - } -} \ No newline at end of file diff --git a/Services/JellyfinService.swift b/Services/JellyfinService.swift deleted file mode 100644 index 9577451..0000000 --- a/Services/JellyfinService.swift +++ /dev/null @@ -1,280 +0,0 @@ -import Foundation - -@MainActor -class JellyfinService { - static let shared = JellyfinService() - private let configService = ConfigService.shared - - private let baseUrl = "https://jellyfin.rockvilletollandsda.church" - private var apiKey: String? { - configService.jellyfinApiKey - } - private var libraryId: String? - private var currentType: MediaType = .sermons - - enum MediaType: String { - case sermons = "Sermons" - case livestreams = "LiveStreams" - } - - enum JellyfinError: Error { - case invalidURL - case networkError - case decodingError - case noVideosFound - case libraryNotFound - } - - struct JellyfinItem: Codable { - let id: String - let name: String - let tags: [String]? - let premiereDate: String? - let productionYear: Int? - let overview: String? - let mediaType: String - let type: String - let path: String - let dateCreated: String - - enum CodingKeys: String, CodingKey { - case id = "Id" - case name = "Name" - case tags = "Tags" - case premiereDate = "PremiereDate" - case productionYear = "ProductionYear" - case overview = "Overview" - case mediaType = "MediaType" - case type = "Type" - case path = "Path" - case dateCreated = "DateCreated" - } - } - - private struct LibraryResponse: Codable { - let items: [Library] - - enum CodingKeys: String, CodingKey { - case items = "Items" - } - } - - private struct Library: Codable { - let id: String - let name: String - let path: String - - enum CodingKeys: String, CodingKey { - case id = "Id" - case name = "Name" - case path = "Path" - } - } - - private struct ItemsResponse: Codable { - let items: [JellyfinItem] - - enum CodingKeys: String, CodingKey { - case items = "Items" - } - } - - private init() {} - - func setType(_ type: MediaType) { - self.currentType = type - self.libraryId = nil // Reset library ID when switching types - } - - private func fetchWithAuth(_ url: URL) async throws -> (Data, URLResponse) { - // Ensure config is loaded - if configService.config == nil { - await configService.loadConfig() - } - - guard let apiKey = self.apiKey else { - throw JellyfinError.networkError - } - - var request = URLRequest(url: url) - request.timeoutInterval = 30 - request.addValue(apiKey, forHTTPHeaderField: "X-MediaBrowser-Token") - request.addValue("MediaBrowser Client=\"RTSDA iOS\", Device=\"iOS\", DeviceId=\"rtsda-ios\", Version=\"1.0.0\", Token=\"\(apiKey)\"", - forHTTPHeaderField: "X-Emby-Authorization") - - do { - let (data, response) = try await URLSession.shared.data(for: request) - - // Check if task was cancelled - try Task.checkCancellation() - - guard let httpResponse = response as? HTTPURLResponse else { - throw JellyfinError.networkError - } - - guard (200...299).contains(httpResponse.statusCode) else { - if let responseString = String(data: data, encoding: .utf8) { - print("Jellyfin API error: \(responseString)") - } - throw JellyfinError.networkError - } - - return (data, response) - } catch is CancellationError { - throw URLError(.cancelled) - } catch { - print("Network request failed: \(error.localizedDescription)") - throw error - } - } - - private func getLibraryId() async throws -> String { - if let id = libraryId { return id } - - let url = URL(string: "\(baseUrl)/Library/MediaFolders")! - - do { - let (data, _) = try await fetchWithAuth(url) - - // Check if task was cancelled - try Task.checkCancellation() - - let response = try JSONDecoder().decode(LibraryResponse.self, from: data) - - let searchTerm = currentType == .sermons ? "Sermons" : "LiveStreams" - - // Try exact match on name - if let library = response.items.first(where: { $0.name == searchTerm }) { - libraryId = library.id - return library.id - } - - print("Library not found: \(searchTerm)") - print("Available libraries: \(response.items.map { $0.name }.joined(separator: ", "))") - throw JellyfinError.libraryNotFound - } catch is CancellationError { - throw URLError(.cancelled) - } catch { - print("Error fetching library ID: \(error.localizedDescription)") - throw error - } - } - - private func parseDate(_ dateString: String) -> Date? { - // Create formatters for different possible formats - let formatters = [ - { () -> DateFormatter in - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSZ" // PocketBase format - return formatter - }(), - { () -> ISO8601DateFormatter in - let formatter = ISO8601DateFormatter() - formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] - return formatter - }(), - { () -> ISO8601DateFormatter in - let formatter = ISO8601DateFormatter() - formatter.formatOptions = [.withInternetDateTime] - return formatter - }() - ] - - for formatter in formatters { - if let date = (formatter as? ISO8601DateFormatter)?.date(from: dateString) ?? - (formatter as? DateFormatter)?.date(from: dateString) { - return date - } - } - - return nil - } - - func fetchSermons(type: SermonType) async throws -> [Sermon] { - currentType = MediaType(rawValue: type.rawValue)! - let libraryId = try await getLibraryId() - - // Check if task was cancelled - try Task.checkCancellation() - - let urlString = "\(baseUrl)/Items?ParentId=\(libraryId)&Fields=Path,PremiereDate,ProductionYear,Overview,DateCreated&Recursive=true&IncludeItemTypes=Movie,Video,Episode&SortBy=DateCreated&SortOrder=Descending" - guard let url = URL(string: urlString) else { - throw JellyfinError.invalidURL - } - - let (data, _) = try await fetchWithAuth(url) - - // Check if task was cancelled - try Task.checkCancellation() - - let response = try JSONDecoder().decode(ItemsResponse.self, from: data) - - return response.items.map { item in - var title = item.name - var speaker = "Unknown Speaker" - - // Remove file extension if present - title = title.replacingOccurrences(of: #"\.(mp4|mov)$"#, with: "", options: .regularExpression) - - // Try to split into title and speaker - let parts = title.components(separatedBy: " - ") - if parts.count > 1 { - title = parts[0].trimmingCharacters(in: .whitespaces) - let speakerPart = parts[1].trimmingCharacters(in: .whitespaces) - speaker = speakerPart.replacingOccurrences( - of: #"\s+(?:January|February|March|April|May|June|July|August|September|October|November|December)\s+\d+(?:th|st|nd|rd)?\s*\d{4}$"#, - with: "", - options: .regularExpression - ).replacingOccurrences(of: "|", with: "").trimmingCharacters(in: .whitespaces) - } - - // Parse date with UTC handling - let rawDate = item.premiereDate ?? item.dateCreated - let utcDate = parseDate(rawDate) ?? Date() - - // Extract components in UTC and create a new date at noon UTC to avoid timezone issues - var calendar = Calendar.current - calendar.timeZone = TimeZone(identifier: "UTC")! - var components = calendar.dateComponents([.year, .month, .day], from: utcDate) - components.hour = 12 // Set to noon UTC to ensure date remains the same in all timezones - let localDate = calendar.date(from: components) ?? Date() - - return Sermon( - id: item.id, - title: title, - description: item.overview ?? "", - date: localDate, - speaker: speaker, - type: type, - videoUrl: getStreamUrl(itemId: item.id), - thumbnail: getImageUrl(itemId: item.id) - ) - } - } - - private func getStreamUrl(itemId: String) -> String { - var components = URLComponents(string: "\(baseUrl)/Videos/\(itemId)/master.m3u8")! - guard let apiKey = self.apiKey else { return "" } - components.queryItems = [ - URLQueryItem(name: "api_key", value: apiKey), - URLQueryItem(name: "MediaSourceId", value: itemId), - URLQueryItem(name: "TranscodingProtocol", value: "hls"), - URLQueryItem(name: "RequireAvc", value: "true"), - URLQueryItem(name: "MaxStreamingBitrate", value: "20000000"), - URLQueryItem(name: "VideoBitrate", value: "10000000"), - URLQueryItem(name: "AudioBitrate", value: "192000"), - URLQueryItem(name: "AudioCodec", value: "aac"), - URLQueryItem(name: "VideoCodec", value: "h264"), - URLQueryItem(name: "MaxAudioChannels", value: "2"), - URLQueryItem(name: "StartTimeTicks", value: "0"), - URLQueryItem(name: "SubtitleMethod", value: "Embed"), - URLQueryItem(name: "TranscodeReasons", value: "VideoCodecNotSupported") - ] - return components.url!.absoluteString - } - - private func getImageUrl(itemId: String) -> String { - guard let apiKey = self.apiKey else { return "" } - return "\(baseUrl)/Items/\(itemId)/Images/Primary?api_key=\(apiKey)" - } -} \ No newline at end of file diff --git a/Services/OwnCastService.swift b/Services/OwnCastService.swift deleted file mode 100644 index 402026c..0000000 --- a/Services/OwnCastService.swift +++ /dev/null @@ -1,78 +0,0 @@ -import Foundation - -class OwnCastService { - static let shared = OwnCastService() - - private let baseUrl = "https://stream.rockvilletollandsda.church" - - private init() {} - - struct StreamStatus: Codable { - let online: Bool - let streamTitle: String? - let lastConnectTime: String? - let lastDisconnectTime: String? - let serverTime: String? - let versionNumber: String? - - enum CodingKeys: String, CodingKey { - case online - case streamTitle = "name" - case lastConnectTime - case lastDisconnectTime - case serverTime - case versionNumber - } - } - - func getStreamStatus() async throws -> StreamStatus { - guard let url = URL(string: "\(baseUrl)/api/status") else { - throw URLError(.badURL) - } - - var request = URLRequest(url: url) - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let (data, response) = try await URLSession.shared.data(for: request) - - guard let httpResponse = response as? HTTPURLResponse else { - throw URLError(.badServerResponse) - } - - guard (200...299).contains(httpResponse.statusCode) else { - if let responseString = String(data: data, encoding: .utf8) { - print("Stream status error: \(responseString)") - } - throw URLError(.badServerResponse) - } - - do { - return try JSONDecoder().decode(StreamStatus.self, from: data) - } catch { - print("Failed to decode stream status: \(error)") - throw error - } - } - - func createLivestreamMessage(from status: StreamStatus) -> Message { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyyy-MM-dd" - dateFormatter.timeZone = TimeZone(identifier: "America/New_York") - let formattedDate = dateFormatter.string(from: Date()) - - return Message( - id: UUID().uuidString, - title: status.streamTitle ?? "Live Stream", - description: "Watch our live stream", - speaker: "Live Stream", - videoUrl: "\(baseUrl)/hls/stream.m3u8", - thumbnailUrl: "\(baseUrl)/thumbnail.jpg", - duration: 0, - isLiveStream: true, - isPublished: true, - isDeleted: false, - liveBroadcastStatus: "live", - date: formattedDate - ) - } -} \ No newline at end of file diff --git a/Services/PocketBaseService.swift b/Services/PocketBaseService.swift deleted file mode 100644 index 0af9c2f..0000000 --- a/Services/PocketBaseService.swift +++ /dev/null @@ -1,302 +0,0 @@ -import Foundation - -struct BulletinSection: Identifiable { - let id = UUID() - let title: String - let content: String -} - -struct Bulletin: Identifiable, Codable { - let id: String - let collectionId: String - let title: String - let date: Date - let divineWorship: String - let sabbathSchool: String - let scriptureReading: String - let sunset: String - let pdf: String? - let isActive: Bool - let created: Date - let updated: Date - - enum CodingKeys: String, CodingKey { - case id - case collectionId = "collectionId" - case title - case date - case divineWorship = "divine_worship" - case sabbathSchool = "sabbath_school" - case scriptureReading = "scripture_reading" - case sunset - case pdf - case isActive = "is_active" - case created - case updated - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: .id) - collectionId = try container.decode(String.self, forKey: .collectionId) - title = try container.decode(String.self, forKey: .title) - date = try container.decode(Date.self, forKey: .date) - divineWorship = try container.decode(String.self, forKey: .divineWorship) - sabbathSchool = try container.decode(String.self, forKey: .sabbathSchool) - scriptureReading = try container.decode(String.self, forKey: .scriptureReading) - sunset = try container.decode(String.self, forKey: .sunset) - pdf = try container.decodeIfPresent(String.self, forKey: .pdf) - isActive = try container.decode(Bool.self, forKey: .isActive) - created = try container.decode(Date.self, forKey: .created) - updated = try container.decode(Date.self, forKey: .updated) - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(collectionId, forKey: .collectionId) - try container.encode(title, forKey: .title) - try container.encode(date, forKey: .date) - try container.encode(divineWorship, forKey: .divineWorship) - try container.encode(sabbathSchool, forKey: .sabbathSchool) - try container.encode(scriptureReading, forKey: .scriptureReading) - try container.encode(sunset, forKey: .sunset) - try container.encodeIfPresent(pdf, forKey: .pdf) - try container.encode(isActive, forKey: .isActive) - try container.encode(created, forKey: .created) - try container.encode(updated, forKey: .updated) - } - - // Computed property to get the PDF URL - var pdfUrl: String { - if let pdf = pdf { - return "https://pocketbase.rockvilletollandsda.church/api/files/\(collectionId)/\(id)/\(pdf)" - } - return "" - } - - // Computed property to get formatted content - var content: String { - """ - Divine Worship - \(divineWorship) - - Sabbath School - \(sabbathSchool) - - Scripture Reading - \(scriptureReading) - - Sunset Information - \(sunset) - """ - } -} - -class PocketBaseService { - static let shared = PocketBaseService() - let baseURL = "https://pocketbase.rockvilletollandsda.church/api/collections" - - private init() {} - - private let dateFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSZ" - return formatter - }() - - private let decoder: JSONDecoder = { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .custom { decoder in - let container = try decoder.singleValueContainer() - let dateString = try container.decode(String.self) - - print("Attempting to decode date string: \(dateString)") - - // Try ISO8601 first - if let date = ISO8601DateFormatter().date(from: dateString) { - print("Successfully decoded ISO8601 date") - return date - } - - // Try various date formats - let formatters = [ - "yyyy-MM-dd'T'HH:mm:ss.SSSZ", - "yyyy-MM-dd'T'HH:mm:ssZ", - "yyyy-MM-dd HH:mm:ss.SSSZ", - "yyyy-MM-dd HH:mm:ssZ", - "yyyy-MM-dd" - ] - - for format in formatters { - let formatter = DateFormatter() - formatter.dateFormat = format - if let date = formatter.date(from: dateString) { - print("Successfully decoded date with format: \(format)") - return date - } - } - - print("Failed to decode date string with any format") - throw DecodingError.dataCorruptedError( - in: container, - debugDescription: "Date string '\(dateString)' does not match any expected format" - ) - } - return decoder - }() - - struct EventResponse: Codable { - let page: Int - let perPage: Int - let totalItems: Int - let totalPages: Int - let items: [Event] - - enum CodingKeys: String, CodingKey { - case page - case perPage - case totalItems - case totalPages - case items - } - } - - struct BulletinResponse: Codable { - let page: Int - let perPage: Int - let totalItems: Int - let totalPages: Int - let items: [Bulletin] - } - - @MainActor - func fetchConfig() async throws -> Config { - let recordId = "nn753t8o2t1iupd" - let urlString = "\(baseURL)/config/records/\(recordId)" - - guard let url = URL(string: urlString) else { - throw URLError(.badURL) - } - - var request = URLRequest(url: url) - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let (data, response) = try await URLSession.shared.data(for: request) - - guard let httpResponse = response as? HTTPURLResponse else { - throw URLError(.badServerResponse) - } - - guard httpResponse.statusCode == 200 else { - if let errorString = String(data: data, encoding: .utf8) { - print("Error fetching config: \(errorString)") - } - throw URLError(.badServerResponse) - } - - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - - do { - return try decoder.decode(Config.self, from: data) - } catch { - print("Failed to decode config: \(error)") - if let decodingError = error as? DecodingError { - switch decodingError { - case .keyNotFound(let key, let context): - print("Missing key: \(key.stringValue) in \(context.debugDescription)") - case .typeMismatch(let type, let context): - print("Type mismatch: expected \(type) in \(context.debugDescription)") - case .valueNotFound(let type, let context): - print("Value not found: expected \(type) in \(context.debugDescription)") - case .dataCorrupted(let context): - print("Data corrupted: \(context.debugDescription)") - @unknown default: - print("Unknown decoding error") - } - } - throw error - } - } - - @MainActor - func fetchEvents() async throws -> [Event] { - guard let url = URL(string: "\(baseURL)/events/records?sort=start_time") else { - throw URLError(.badURL) - } - - let (data, response) = try await URLSession.shared.data(from: url) - - guard let httpResponse = response as? HTTPURLResponse else { - throw URLError(.badServerResponse) - } - - guard httpResponse.statusCode == 200 else { - if let errorString = String(data: data, encoding: .utf8) { - print("Error fetching events: \(errorString)") - } - throw URLError(.badServerResponse) - } - - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - - do { - let eventResponse = try decoder.decode(EventResponse.self, from: data) - return eventResponse.items - } catch { - print("Failed to decode events: \(error)") - if let decodingError = error as? DecodingError { - switch decodingError { - case .keyNotFound(let key, let context): - print("Missing key: \(key.stringValue) in \(context.debugDescription)") - case .typeMismatch(let type, let context): - print("Type mismatch: expected \(type) in \(context.debugDescription)") - case .valueNotFound(let type, let context): - print("Value not found: expected \(type) in \(context.debugDescription)") - case .dataCorrupted(let context): - print("Data corrupted: \(context.debugDescription)") - @unknown default: - print("Unknown decoding error") - } - } - throw error - } - } - - func fetchBulletins(activeOnly: Bool = false) async throws -> BulletinResponse { - let endpoint = "\(baseURL)/bulletins/records" - var components = URLComponents(string: endpoint)! - - var queryItems = [URLQueryItem]() - if activeOnly { - queryItems.append(URLQueryItem(name: "filter", value: "is_active=true")) - } - queryItems.append(URLQueryItem(name: "sort", value: "-date")) - components.queryItems = queryItems - - guard let url = components.url else { - throw URLError(.badURL) - } - - let (data, response) = try await URLSession.shared.data(from: url) - - guard let httpResponse = response as? HTTPURLResponse, - httpResponse.statusCode == 200 else { - throw URLError(.badServerResponse) - } - - // Debug: Print the JSON response - if let jsonString = String(data: data, encoding: .utf8) { - print("Received JSON response: \(jsonString)") - } - - do { - return try decoder.decode(BulletinResponse.self, from: data) - } catch { - print("Failed to decode bulletins: \(error)") - throw error - } - } -} \ No newline at end of file diff --git a/ViewModels/BulletinViewModel.swift b/ViewModels/BulletinViewModel.swift deleted file mode 100644 index 39f677a..0000000 --- a/ViewModels/BulletinViewModel.swift +++ /dev/null @@ -1,87 +0,0 @@ -import Foundation -import SwiftUI - -@MainActor -class BulletinViewModel: ObservableObject { - @Published var latestBulletin: Bulletin? - @Published var bulletins: [Bulletin] = [] - @Published var isLoading = false - @Published var error: Error? - - private let pocketBaseService = PocketBaseService.shared - private var currentTask: Task? - - func loadLatestBulletin() async { - // Cancel any existing task - currentTask?.cancel() - - // Create new task - currentTask = Task { - guard !Task.isCancelled else { return } - - isLoading = true - error = nil - - do { - let response = try await pocketBaseService.fetchBulletins(activeOnly: true) - if !Task.isCancelled { - if let bulletin = response.items.first { - print("Loaded bulletin with ID: \(bulletin.id)") - print("PDF field value: \(bulletin.pdf ?? "nil")") - print("Generated PDF URL: \(bulletin.pdfUrl)") - latestBulletin = bulletin - } - } - } catch { - if !Task.isCancelled { - print("Error loading bulletin: \(error)") - self.error = error - } - } - - if !Task.isCancelled { - isLoading = false - } - } - - // Wait for the task to complete - await currentTask?.value - } - - @MainActor - func loadBulletins() async { - // Cancel any existing task - currentTask?.cancel() - - // Create new task - currentTask = Task { - guard !Task.isCancelled else { return } - - isLoading = true - error = nil - - do { - let response = try await PocketBaseService.shared.fetchBulletins(activeOnly: true) - if !Task.isCancelled { - self.bulletins = response.items - } - } catch { - if !Task.isCancelled { - print("Error loading bulletins: \(error)") - self.error = error - } - } - - if !Task.isCancelled { - isLoading = false - } - } - - // Wait for the task to complete - await currentTask?.value - } - - deinit { - currentTask?.cancel() - } -} diff --git a/ViewModels/EventsViewModel.swift b/ViewModels/EventsViewModel.swift deleted file mode 100644 index 809f738..0000000 --- a/ViewModels/EventsViewModel.swift +++ /dev/null @@ -1,57 +0,0 @@ -import SwiftUI - -@MainActor -class EventsViewModel: ObservableObject { - @Published private(set) var events: [Event] = [] - @Published private(set) var isLoading = false - @Published private(set) var error: Error? - - private let pocketBaseService = PocketBaseService.shared - - func loadEvents() async { - isLoading = true - error = nil - - do { - // Get current time - let now = Date() - print("🕒 Current time: \(now)") - - // Show all events that haven't ended yet - let allEvents = try await pocketBaseService.fetchEvents() - print("📋 Total events from PocketBase: \(allEvents.count)") - - for event in allEvents { - print("🗓️ Event: \(event.title)") - print(" Start: \(event.startDate)") - print(" End: \(event.endDate)") - print(" Category: \(event.category.rawValue)") - print(" Recurring: \(event.reoccuring.rawValue)") - print(" Published: \(event.isPublished)") - } - - events = allEvents - .filter { event in - // Subtract 5 hours from the current time to match the UTC offset - // Because PocketBase stores "5 AM Eastern" as "5 AM UTC" - // So when it's actually "10 AM UTC", PocketBase shows "5 AM UTC" - let utcOffset = -5 * 60 * 60 // -5 hours in seconds - let adjustedNow = now.addingTimeInterval(TimeInterval(utcOffset)) - let willShow = event.endDate > adjustedNow - print(" Compare - Event: \(event.title)") - print(" End time: \(event.endDate)") - print(" Current time (adjusted to UTC): \(adjustedNow)") - print(" Will show: \(willShow)") - return willShow - } - .sorted { $0.startDate < $1.startDate } - - print("✅ Filtered events count: \(events.count)") - } catch { - print("❌ Error loading events: \(error)") - self.error = error - } - - isLoading = false - } -} diff --git a/ViewModels/MessagesViewModel.swift b/ViewModels/MessagesViewModel.swift deleted file mode 100644 index a4914d1..0000000 --- a/ViewModels/MessagesViewModel.swift +++ /dev/null @@ -1,249 +0,0 @@ -import Foundation - -@MainActor -class MessagesViewModel: ObservableObject { - @Published var messages: [Message] = [] - @Published var filteredMessages: [Message] = [] - @Published var livestream: Message? - @Published var isLoading = false - @Published var error: Error? - @Published var availableYears: [String] = [] - @Published var availableMonths: [String] = [] - @Published var currentMediaType: JellyfinService.MediaType = .sermons - - private let jellyfinService = JellyfinService.shared - private let owncastService = OwnCastService.shared - private var currentTask: Task? - private var autoRefreshTask: Task? - - init() { - Task { - await loadContent() - startAutoRefresh() - } - } - - deinit { - autoRefreshTask?.cancel() - } - - private func startAutoRefresh() { - // Cancel any existing auto-refresh task - autoRefreshTask?.cancel() - - // Create new auto-refresh task - autoRefreshTask = Task { - while !Task.isCancelled { - // Wait for 5 seconds - try? await Task.sleep(nanoseconds: 5 * 1_000_000_000) - - // Check only livestream status - let streamStatus = try? await owncastService.getStreamStatus() - print("📺 Stream status: \(String(describing: streamStatus))") - if let status = streamStatus, status.online { - print("📺 Stream is online! Creating livestream message") - self.livestream = owncastService.createLivestreamMessage(from: status) - print("📺 Livestream message created: \(String(describing: self.livestream))") - } else { - print("📺 Stream is offline or status check failed") - self.livestream = nil - } - } - } - } - - func loadContent(mediaType: JellyfinService.MediaType = .sermons) async { - currentMediaType = mediaType - guard !isLoading else { return } - isLoading = true - error = nil - - // Cancel any existing task - currentTask?.cancel() - - // Create a new task for content loading - currentTask = Task { - do { - // Check OwnCast stream status - if !Task.isCancelled { - let streamStatus = try? await owncastService.getStreamStatus() - print("📺 Initial stream status: \(String(describing: streamStatus))") - if let status = streamStatus, status.online { - print("📺 Stream is online on initial load! Creating livestream message") - self.livestream = owncastService.createLivestreamMessage(from: status) - print("📺 Initial livestream message created: \(String(describing: self.livestream))") - } else { - print("📺 Stream is offline on initial load") - self.livestream = nil - } - } - - // Set media type and fetch content - if !Task.isCancelled { - jellyfinService.setType(mediaType) - let sermons = try await jellyfinService.fetchSermons(type: mediaType == .sermons ? .sermon : .liveArchive) - - // Create simple date formatter - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - formatter.timeZone = TimeZone(identifier: "America/New_York") - - // Convert sermons to messages - self.messages = sermons.map { sermon in - Message( - id: sermon.id, - title: sermon.title, - description: sermon.description, - speaker: sermon.speaker, - videoUrl: sermon.videoUrl ?? "", - thumbnailUrl: sermon.thumbnail ?? "", - duration: 0, - isLiveStream: sermon.type == .liveArchive, - isPublished: true, - isDeleted: false, - liveBroadcastStatus: sermon.type == .liveArchive ? "live" : "none", - date: formatter.string(from: sermon.date) - ) - } - .sorted { $0.date > $1.date } - - // Update available years and months - updateAvailableFilters() - - // Initialize filtered messages with all messages - self.filteredMessages = self.messages - - // Only show error if both content and livestream failed - if self.messages.isEmpty && self.livestream == nil { - self.error = JellyfinService.JellyfinError.noVideosFound - } - } - } catch { - if !Task.isCancelled { - self.error = error - print("Error loading content: \(error.localizedDescription)") - } - } - - if !Task.isCancelled { - isLoading = false - } - } - - // Wait for the task to complete - await currentTask?.value - } - - func refreshContent() async { - // Check stream status first - let streamStatus = try? await owncastService.getStreamStatus() - if let status = streamStatus, status.online { - self.livestream = owncastService.createLivestreamMessage(from: status) - } else { - self.livestream = nil - } - - // Then load the rest of the content - await loadContent(mediaType: currentMediaType) - } - - private func updateAvailableFilters() { - // Get messages for current media type - let currentMessages = messages.filter { message in - message.isLiveStream == (currentMediaType == .livestreams) - } - - // Create date formatter - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - - // Get unique years first - var years = Set() - var monthsByYear: [String: Set] = [:] - - for message in currentMessages { - if let date = formatter.date(from: message.date) { - let calendar = Calendar.current - let year = String(calendar.component(.year, from: date)) - let month = String(format: "%02d", calendar.component(.month, from: date)) - - years.insert(year) - - // Group months by year - if monthsByYear[year] == nil { - monthsByYear[year] = Set() - } - monthsByYear[year]?.insert(month) - } - } - - // Sort years descending (newest first) - availableYears = Array(years).sorted(by: >) - - // Get months only for selected year (first year by default) - if let selectedYear = availableYears.first, - let monthsForYear = monthsByYear[selectedYear] { - availableMonths = Array(monthsForYear).sorted() - } else { - availableMonths = [] - } - } - - // Add a method to update months when year changes - func updateMonthsForYear(_ year: String) { - // Get messages for current media type and year - let currentMessages = messages.filter { message in - message.isLiveStream == (currentMediaType == .livestreams) && - message.date.hasPrefix(year) - } - - // Create date formatter - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - - // Get months for the selected year - var months = Set() - - for message in currentMessages { - if let date = formatter.date(from: message.date) { - let calendar = Calendar.current - let month = String(format: "%02d", calendar.component(.month, from: date)) - months.insert(month) - } - } - - // Sort months ascending (Jan to Dec) - availableMonths = Array(months).sorted() - } - - func filterContent(year: String? = nil, month: String? = nil) { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy-MM-dd" - - // Filter by type first - let typeFiltered = messages.filter { message in - message.isLiveStream == (currentMediaType == .livestreams) - } - - // Then filter by date components - let dateFiltered = typeFiltered.filter { message in - guard let date = formatter.date(from: message.date) else { return false } - let calendar = Calendar.current - - if let year = year { - let messageYear = String(calendar.component(.year, from: date)) - if messageYear != year { return false } - } - - if let month = month { - let messageMonth = String(format: "%02d", calendar.component(.month, from: date)) - if messageMonth != month { return false } - } - - return true - } - - // Sort by date, newest first - filteredMessages = dateFiltered.sorted { $0.date > $1.date } - } -} diff --git a/ViewModels/OwncastViewModel.swift b/ViewModels/OwncastViewModel.swift deleted file mode 100644 index dd02421..0000000 --- a/ViewModels/OwncastViewModel.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation - -@MainActor -class OwncastViewModel: ObservableObject { - @Published var streamUrl: URL? - @Published var isLoading = false - private let owncastService = OwnCastService.shared - private let baseUrl = "https://stream.rockvilletollandsda.church" - - func checkStreamStatus() async { - isLoading = true - defer { isLoading = false } - - do { - let status = try await owncastService.getStreamStatus() - if status.online { - streamUrl = URL(string: "\(baseUrl)/hls/stream.m3u8") - } else { - streamUrl = nil - } - } catch { - print("Failed to check stream status:", error) - streamUrl = nil - } - } -} \ No newline at end of file diff --git a/ViewModels/SermonBrowserViewModel.swift b/ViewModels/SermonBrowserViewModel.swift deleted file mode 100644 index 8849809..0000000 --- a/ViewModels/SermonBrowserViewModel.swift +++ /dev/null @@ -1,148 +0,0 @@ -import Foundation -import AVKit - -@MainActor -class SermonBrowserViewModel: ObservableObject { - @Published private(set) var sermons: [Sermon] = [] - @Published private(set) var isLoading = false - @Published private(set) var error: Error? - @Published var selectedType: SermonType = .sermon - @Published var selectedYear: Int? - @Published var selectedMonth: Int? - - let jellyfinService: JellyfinService - - var organizedSermons: [Int: [Int: [Sermon]]] { - var filteredSermons = sermons.filter { $0.type == selectedType } - - // Apply year filter if selected - if let selectedYear = selectedYear { - filteredSermons = filteredSermons.filter { - Calendar.current.component(.year, from: $0.date) == selectedYear - } - } - - // Apply month filter if selected - if let selectedMonth = selectedMonth { - filteredSermons = filteredSermons.filter { - Calendar.current.component(.month, from: $0.date) == selectedMonth - } - } - - return Dictionary(grouping: filteredSermons) { sermon in - Calendar.current.component(.year, from: sermon.date) - }.mapValues { yearSermons in - Dictionary(grouping: yearSermons) { sermon in - Calendar.current.component(.month, from: sermon.date) - } - } - } - - var years: [Int] { - let filteredSermons = sermons.filter { $0.type == selectedType } - let allYears = Set(filteredSermons.map { - Calendar.current.component(.year, from: $0.date) - }) - return Array(allYears).sorted(by: >) - } - - func months(for year: Int) -> [Int] { - let yearSermons = sermons.filter { - $0.type == selectedType && - Calendar.current.component(.year, from: $0.date) == year - } - return Array(Set(yearSermons.map { - Calendar.current.component(.month, from: $0.date) - })).sorted(by: >) - } - - func sermons(for year: Int, month: Int) -> [Sermon] { - organizedSermons[year]?[month]?.sorted(by: { $0.date > $1.date }) ?? [] - } - - @MainActor - init(jellyfinService: JellyfinService) { - self.jellyfinService = jellyfinService - } - - @MainActor - convenience init() { - self.init(jellyfinService: JellyfinService.shared) - } - - @MainActor - func fetchSermons() async throws { - isLoading = true - error = nil - - do { - sermons = try await jellyfinService.fetchSermons(type: .sermon) - - if let firstYear = years.first { - selectedYear = firstYear - if let firstMonth = months(for: firstYear).first { - selectedMonth = firstMonth - } - } - } catch { - self.error = error - throw error - } - - isLoading = false - } - - func selectType(_ type: SermonType) { - selectedType = type - selectedYear = nil - selectedMonth = nil - - if let firstYear = years.first { - selectedYear = firstYear - if let firstMonth = months(for: firstYear).first { - selectedMonth = firstMonth - } - } - } - - func selectYear(_ year: Int?) { - selectedYear = year - selectedMonth = nil - - if let year = year, - let firstMonth = months(for: year).first { - selectedMonth = firstMonth - } - } - - func selectMonth(_ month: Int?) { - selectedMonth = month - } - - @MainActor - func loadSermons() async { - isLoading = true - error = nil - - do { - sermons = try await jellyfinService.fetchSermons(type: .sermon) - - if let firstYear = years.first { - selectedYear = firstYear - if let firstMonth = months(for: firstYear).first { - selectedMonth = firstMonth - } - } - } catch { - self.error = error - } - - isLoading = false - } - - @MainActor - func requestPermissions() async { - let permissionsManager = PermissionsManager.shared - permissionsManager.requestLocationAccess() - } -} diff --git a/Views/BeliefsView.swift b/Views/BeliefsView.swift index acbae35..ffb1b55 100644 --- a/Views/BeliefsView.swift +++ b/Views/BeliefsView.swift @@ -8,6 +8,7 @@ struct Belief: Identifiable { } struct BeliefsView: View { + @Environment(\.dismiss) private var dismiss let beliefs = [ Belief(id: 1, title: "The Holy Scriptures", summary: "The Holy Scriptures, Old and New Testaments, are the written Word of God, given by divine inspiration. The inspired authors spoke and wrote as they were moved by the Holy Spirit.", @@ -126,54 +127,6 @@ struct BeliefsView: View { @State private var selectedBelief: Belief? - private func formatVerseForURL(_ verse: String) -> String { - // Convert "Romans 4:11" to "rom.4.11" - let bookMap = [ - "Genesis": "gen", "Exodus": "exo", "Leviticus": "lev", "Numbers": "num", - "Deuteronomy": "deu", "Joshua": "jos", "Judges": "jdg", "Ruth": "rut", - "1 Samuel": "1sa", "2 Samuel": "2sa", "1 Kings": "1ki", "2 Kings": "2ki", - "1 Chronicles": "1ch", "2 Chronicles": "2ch", "Ezra": "ezr", "Nehemiah": "neh", - "Esther": "est", "Job": "job", "Psalm": "psa", "Psalms": "psa", "Proverbs": "pro", - "Ecclesiastes": "ecc", "Song of Solomon": "sng", "Isaiah": "isa", "Jeremiah": "jer", - "Lamentations": "lam", "Ezekiel": "ezk", "Daniel": "dan", "Hosea": "hos", - "Joel": "jol", "Amos": "amo", "Obadiah": "oba", "Jonah": "jon", - "Micah": "mic", "Nahum": "nam", "Habakkuk": "hab", "Zephaniah": "zep", - "Haggai": "hag", "Zechariah": "zec", "Malachi": "mal", "Matthew": "mat", - "Mark": "mrk", "Luke": "luk", "John": "jhn", "Acts": "act", - "Romans": "rom", "1 Corinthians": "1co", "2 Corinthians": "2co", "Galatians": "gal", - "Ephesians": "eph", "Philippians": "php", "Colossians": "col", "1 Thessalonians": "1th", - "2 Thessalonians": "2th", "1 Timothy": "1ti", "2 Timothy": "2ti", "Titus": "tit", - "Philemon": "phm", "Hebrews": "heb", "James": "jas", "1 Peter": "1pe", - "2 Peter": "2pe", "1 John": "1jn", "2 John": "2jn", "3 John": "3jn", - "Jude": "jud", "Revelation": "rev" - ] - - let components = verse.components(separatedBy: " ") - guard components.count >= 2 else { return verse.lowercased() } - - // Handle book name (including numbered books like "1 Corinthians") - var bookName = "" - var remainingComponents: [String] = components - - if let firstComponent = components.first, let _ = Int(firstComponent) { - if components.count >= 2 { - bookName = components[0] + " " + components[1] - remainingComponents = Array(components.dropFirst(2)) - } - } else { - bookName = components[0] - remainingComponents = Array(components.dropFirst()) - } - - guard let bookCode = bookMap[bookName] else { return verse.lowercased() } - - // Format chapter and verse - let reference = remainingComponents.joined(separator: "") - .replacingOccurrences(of: ":", with: ".") - .replacingOccurrences(of: "-", with: "-") - - return "\(bookCode).\(reference)" - } var body: some View { List { @@ -181,7 +134,9 @@ struct BeliefsView: View { .font(.subheadline) .foregroundColor(.secondary) .padding(.vertical, 8) + #if os(iOS) .listRowSeparator(.hidden) + #endif ForEach(beliefs) { belief in Button(action: { @@ -206,6 +161,13 @@ struct BeliefsView: View { } } .navigationTitle("Our Beliefs") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + dismiss() + } + } + } .sheet(item: $selectedBelief) { belief in NavigationStack { ScrollView { @@ -221,31 +183,25 @@ struct BeliefsView: View { .padding(.horizontal) ForEach(belief.verses, id: \.self) { verse in - Button(action: { - let formattedVerse = formatVerseForURL(verse) - if let url = URL(string: "https://www.bible.com/bible/1/\(formattedVerse)") { - UIApplication.shared.open(url) - } - }) { - Text(verse) - .foregroundColor(.primary) - Spacer() - Image(systemName: "book.fill") - .foregroundColor(.secondary) - } - .padding(.horizontal) - .padding(.vertical, 8) - .background(Color(.systemBackground)) + VerseDisplayView(reference: verse) + .padding(.horizontal) + .padding(.vertical, 8) } } .padding(.vertical) + #if os(iOS) .background(Color(.secondarySystemBackground)) + #else + .background(Color.secondary.opacity(0.1)) + #endif } } .padding(.vertical) } .navigationTitle(belief.title) + #if os(iOS) .navigationBarTitleDisplayMode(.inline) + #endif .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Done") { @@ -257,3 +213,112 @@ struct BeliefsView: View { } } } + +// MARK: - Bible Verse Integration + +struct BibleVerseModel: Codable { + let text: String + let reference: String +} + +struct VerseDisplayView: View { + let reference: String + @State private var verseText: String = "" + @State private var isLoading: Bool = false + @State private var showingFullVerse: Bool = false + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Button(action: { + if verseText.isEmpty { + loadVerse() + } else { + showingFullVerse.toggle() + } + }) { + HStack { + Text(reference) + .font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.primary) + + Spacer() + + if isLoading { + ProgressView() + .scaleEffect(0.8) + } else if !verseText.isEmpty { + Image(systemName: showingFullVerse ? "chevron.up" : "chevron.down") + .font(.caption) + .foregroundColor(.secondary) + } else { + Image(systemName: "book.fill") + .foregroundColor(Color(hex: "fb8b23")) + } + } + } + .buttonStyle(.plain) + + if showingFullVerse && !verseText.isEmpty { + Text(verseText) + .font(.subheadline) + .foregroundColor(.secondary) + .padding(.top, 4) + .fixedSize(horizontal: false, vertical: true) + } + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8)) + } + + private func loadVerse() { + isLoading = true + + Task { + let versesJson = fetchBibleVerseJson(query: reference) + + await MainActor.run { + isLoading = false + + // Use Rust function for JSON parsing and text extraction - NO business logic in Swift! + let extractedText = extractFullVerseText(versesJson: versesJson) + if !extractedText.isEmpty { + verseText = extractedText + showingFullVerse = true + } else { + verseText = "Unable to load verse text" + showingFullVerse = true + } + } + } + } +} + +// MARK: - Three Angels Messages + +struct ThreeAngelsMessages { + static func getMessages() -> [(title: String, reference: String, description: String)] { + return [ + ( + title: "First Angel's Message", + reference: "Revelation 14:6-7", + description: "Fear God and give glory to Him, for the hour of His judgment has come" + ), + ( + title: "Second Angel's Message", + reference: "Revelation 14:8", + description: "Babylon is fallen, is fallen, that great city" + ), + ( + title: "Third Angel's Message", + reference: "Revelation 14:9-12", + description: "Keep the commandments of God and the faith of Jesus" + ) + ] + } + + static func getAllReferences() -> String { + return "Revelation 14:6-7,Revelation 14:8,Revelation 14:9-12" + } +} diff --git a/Views/BulletinView.swift b/Views/BulletinView.swift deleted file mode 100644 index a30a7f2..0000000 --- a/Views/BulletinView.swift +++ /dev/null @@ -1,823 +0,0 @@ -import SwiftUI -import Foundation -import PDFKit - -struct PDFViewer: UIViewRepresentable { - let url: URL - @Binding var isLoading: Bool - @Binding var error: Error? - @Binding var hasInteracted: Bool - @State private var downloadTask: Task? - @State private var documentTask: Task? - @State private var pageCount: Int = 0 - @State private var currentPage: Int = 1 - @State private var pdfData: Data? - - func makeUIView(context: Context) -> PDFView { - let pdfView = PDFView() - pdfView.autoScales = true - pdfView.displayMode = .twoUpContinuous - pdfView.displayDirection = .horizontal - pdfView.backgroundColor = .systemBackground - pdfView.usePageViewController(true) - pdfView.delegate = context.coordinator - return pdfView - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - func updateUIView(_ uiView: PDFView, context: Context) { - // Only start a new download if we don't have the data yet - if pdfData == nil { - Task { @MainActor in - await startDownload(for: uiView) - } - } else if uiView.document == nil && documentTask == nil { - Task { @MainActor in - await createDocument(for: uiView) - } - } - } - - class Coordinator: NSObject, PDFViewDelegate { - var parent: PDFViewer - - init(_ parent: PDFViewer) { - self.parent = parent - } - - func pdfViewPageChanged(_ notification: Notification) { - if !parent.hasInteracted { - parent.hasInteracted = true - } - } - } - - private func createDocument(for pdfView: PDFView) async { - documentTask?.cancel() - - documentTask = Task { - do { - guard let data = pdfData else { return } - let document = try await createPDFDocument(from: data) - - if !Task.isCancelled { - await MainActor.run { - pdfView.document = document - pageCount = document.pageCount - isLoading = false - } - } - } catch { - if !Task.isCancelled { - await MainActor.run { - self.error = error - isLoading = false - } - } - } - await MainActor.run { - documentTask = nil - } - } - } - - private func startDownload(for pdfView: PDFView) async { - // Cancel any existing task - downloadTask?.cancel() - - // Create new task - downloadTask = Task { - await MainActor.run { - isLoading = true - error = nil - } - - do { - // Download PDF data - let (data, _) = try await downloadPDFData() - - // Check if task was cancelled - if Task.isCancelled { return } - - // Store the data - await MainActor.run { - self.pdfData = data - } - - } catch { - if !Task.isCancelled { - await MainActor.run { - self.error = error - isLoading = false - } - } - } - } - } - - private func createPDFDocument(from data: Data) async throws -> PDFDocument { - return try await Task.detached { - guard let document = PDFDocument(data: data) else { - throw URLError(.cannotDecodeContentData) - } - return document - }.value - } - - private func downloadPDFData() async throws -> (Data, URLResponse) { - var request = URLRequest(url: url) - request.timeoutInterval = 30 - request.setValue("application/pdf", forHTTPHeaderField: "Accept") - request.setValue("application/pdf", forHTTPHeaderField: "Content-Type") - - if let token = UserDefaults.standard.string(forKey: "authToken") { - request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - } - - return try await URLSession.shared.data(for: request) - } - - static func dismantleUIView(_ uiView: PDFView, coordinator: ()) { - uiView.document = nil - } -} - -struct BulletinListView: View { - @StateObject private var viewModel = BulletinViewModel() - - var body: some View { - NavigationView { - Group { - if viewModel.isLoading && viewModel.bulletins.isEmpty { - ProgressView() - } else if let error = viewModel.error { - VStack { - Text("Error loading bulletins") - .font(.headline) - .foregroundColor(.red) - Text(error.localizedDescription) - .font(.subheadline) - .foregroundColor(.secondary) - Button("Retry") { - Task { - await viewModel.loadBulletins() - } - } - .padding() - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(8) - } - .padding() - } else if viewModel.bulletins.isEmpty { - VStack(spacing: 16) { - Image(systemName: "doc.text") - .font(.system(size: 50)) - .foregroundColor(.secondary) - Text("No Bulletins Available") - .font(.headline) - Text("Check back later for bulletins.") - .font(.subheadline) - .foregroundColor(.secondary) - Button("Refresh") { - Task { - await viewModel.loadBulletins() - } - } - .padding() - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(8) - } - .padding() - } else { - List { - ForEach(viewModel.bulletins) { bulletin in - NavigationLink(destination: BulletinDetailView(bulletin: bulletin)) { - VStack(alignment: .leading, spacing: 4) { - Text(bulletin.date.formatted(date: .long, time: .omitted)) - .font(.headline) - Text(bulletin.title) - .font(.subheadline) - .foregroundColor(.secondary) - } - .padding(.vertical, 4) - } - } - } - .refreshable { - await viewModel.loadBulletins() - } - } - } - .navigationTitle("Church Bulletins") - .task { - if viewModel.bulletins.isEmpty { - await viewModel.loadBulletins() - } - } - } - } -} - -struct BulletinDetailView: View { - let bulletin: Bulletin - @State private var isLoading = false - @State private var error: Error? - @State private var showPDFViewer = false - @State private var showScrollIndicator = true - @State private var hasInteracted = false - - var body: some View { - ScrollView { - VStack(spacing: 24) { - // Header - VStack(spacing: 8) { - Text(bulletin.date.formatted(date: .long, time: .omitted)) - .font(.subheadline) - .foregroundColor(.secondary) - - Text(bulletin.title) - .font(.title) - .fontWeight(.bold) - .multilineTextAlignment(.center) - } - .padding(.vertical, 16) - .frame(maxWidth: .infinity) - .background( - RoundedRectangle(cornerRadius: 15) - .fill(Color(.systemBackground)) - .shadow(color: .black.opacity(0.1), radius: 10, x: 0, y: 5) - ) - .padding(.horizontal) - - // PDF Button - if bulletin.pdf != nil { - Button(action: { - showPDFViewer = true - showScrollIndicator = true - hasInteracted = false - }) { - HStack { - Image(systemName: "doc.fill") - .font(.title3) - Text("View PDF") - .font(.headline) - } - .frame(maxWidth: .infinity) - .padding() - .background( - LinearGradient( - gradient: Gradient(colors: [Color.blue, Color.blue.opacity(0.8)]), - startPoint: .leading, - endPoint: .trailing - ) - ) - .foregroundColor(.white) - .cornerRadius(12) - .shadow(color: .blue.opacity(0.3), radius: 8, x: 0, y: 4) - } - .padding(.horizontal) - .padding(.bottom, 16) - } - - // Content - BulletinContentView(bulletin: bulletin) - .padding() - .background( - RoundedRectangle(cornerRadius: 15) - .fill(Color(.systemBackground)) - .shadow(color: .black.opacity(0.1), radius: 10, x: 0, y: 5) - ) - .padding(.horizontal) - } - .padding(.vertical) - } - .background(Color(.systemGroupedBackground)) - .sheet(isPresented: $showPDFViewer) { - if let url = URL(string: bulletin.pdfUrl) { - NavigationStack { - ZStack { - PDFViewer(url: url, isLoading: $isLoading, error: $error, hasInteracted: $hasInteracted) - .ignoresSafeArea() - - if isLoading { - ProgressView() - .progressViewStyle(.circular) - .scaleEffect(1.5) - .tint(.white) - } - - if let error = error { - VStack(spacing: 16) { - Text("Error loading PDF") - .font(.headline) - .foregroundColor(.red) - Text(error.localizedDescription) - .font(.subheadline) - .foregroundColor(.secondary) - Button("Try Again") { - self.error = nil - } - .buttonStyle(.bordered) - } - .padding() - .background(Color(.systemBackground)) - .cornerRadius(10) - } - - if showScrollIndicator && !hasInteracted { - VStack { - Spacer() - HStack { - Spacer() - VStack(spacing: 8) { - Image(systemName: "arrow.left.and.right") - .font(.title) - .foregroundColor(.white) - Text("Swipe to navigate") - .font(.caption) - .foregroundColor(.white) - Button("Got it") { - withAnimation { - showScrollIndicator = false - hasInteracted = true - } - } - .font(.caption) - .foregroundColor(.white) - .padding(.top, 4) - } - .padding() - .background(Color.black.opacity(0.7)) - .cornerRadius(10) - .padding() - } - } - } - } - .navigationTitle("PDF Viewer") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button("Done") { - showPDFViewer = false - } - } - } - } - } - } - } -} - -// Update the main BulletinView to use BulletinListView -struct BulletinView: View { - var body: some View { - BulletinListView() - } -} - -struct BulletinContentView: View { - let bulletin: Bulletin - - // Tuple to represent processed content segments - typealias ContentSegment = (id: UUID, text: String, type: ContentType, reference: String?) - - enum ContentType { - case text - case hymn(number: Int) - case responsiveReading(number: Int) - case bibleVerse - case sectionHeader - } - - private let sectionOrder = [ - ("Sabbath School", \Bulletin.sabbathSchool), - ("Divine Worship", \Bulletin.divineWorship), - ("Scripture Reading", \Bulletin.scriptureReading), - ("Sunset", \Bulletin.sunset) - ] - - private func cleanHTML(_ text: String) -> String { - // Remove HTML tags - var cleaned = text.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression) - - // Replace common HTML entities - let entities = [ - "&": "&", - "<": "<", - ">": ">", - """: "\"", - "'": "'", - "'": "'", - " ": " ", - "–": "–", - "—": "—", - "•": "•", - "æ": "æ", - "\\u003c": "<", - "\\u003e": ">", - "\\r\\n": " " - ] - - for (entity, replacement) in entities { - cleaned = cleaned.replacingOccurrences(of: entity, with: replacement) - } - - // Clean up whitespace and normalize spaces - cleaned = cleaned.replacingOccurrences(of: #"\s+"#, with: " ", options: .regularExpression) - cleaned = cleaned.replacingOccurrences(of: #"(\d+)\s*:\s*(\d+)"#, with: "$1:$2", options: .regularExpression) - cleaned = cleaned.trimmingCharacters(in: .whitespacesAndNewlines) - - return cleaned - } - - private func processLine(_ line: String) -> [ContentSegment] { - // Clean HTML first - let cleanedLine = cleanHTML(line) - var segments: [ContentSegment] = [] - let nsLine = cleanedLine as NSString - - // Check for section headers - if let headerSegment = processHeader(cleanedLine, nsLine) { - segments.append(headerSegment) - return segments - } - - // Process hymn numbers - if let hymnSegments = processHymns(cleanedLine, nsLine) { - segments.append(contentsOf: hymnSegments) - return segments - } - - // Process responsive readings - if let readingSegments = processResponsiveReadings(cleanedLine, nsLine) { - segments.append(contentsOf: readingSegments) - return segments - } - - // Process Bible verses - if let verseSegments = processBibleVerses(cleanedLine, nsLine) { - segments.append(contentsOf: verseSegments) - return segments - } - - // If no special processing was done, add as regular text - segments.append((id: UUID(), text: cleanedLine, type: .text, reference: nil)) - return segments - } - - private func processHeader(_ line: String, _ nsLine: NSString) -> ContentSegment? { - let headerPatterns = [ - // Sabbath School headers - #"^(Sabbath School):?"#, - #"^(Song Service):?"#, - #"^(Leadership):?"#, - #"^(Lesson Study):?"#, - #"^(Mission Story):?"#, - #"^(Welcome):?"#, - #"^(Opening Song):?"#, - #"^(Opening Prayer):?"#, - #"^(Mission Spotlight):?"#, - #"^(Bible Study):?"#, - #"^(Closing Song):?"#, - #"^(Closing Prayer):?"#, - // Divine Worship headers - #"^(Announcements):?"#, - #"^(Call To Worship):?"#, - #"^(Opening Hymn):?"#, - #"^(Prayer & Praises):?"#, - #"^(Prayer Song):?"#, - #"^(Offering):?"#, - #"^(Children's Story):?"#, - #"^(Special Music):?"#, - #"^(Scripture Reading):?"#, - #"^(Sermon):?"#, - #"^(Closing Hymn):?"#, - #"^(Benediction):?"# - ] - - for pattern in headerPatterns { - let headerRegex = try! NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) - let headerMatches = headerRegex.matches(in: line, range: NSRange(location: 0, length: nsLine.length)) - - if !headerMatches.isEmpty { - let headerText = nsLine.substring(with: headerMatches[0].range(at: 1)) - .trimmingCharacters(in: .whitespaces) - return (id: UUID(), text: headerText, type: .sectionHeader, reference: nil) - } - } - - return nil - } - - private func processHymns(_ line: String, _ nsLine: NSString) -> [ContentSegment]? { - let hymnPattern = #"(?:Hymn(?:al)?\s+(?:#\s*)?|#\s*)(\d+)(?:\s+["""]([^"""]*)[""])?.*"# - let hymnRegex = try! NSRegularExpression(pattern: hymnPattern, options: [.caseInsensitive]) - let hymnMatches = hymnRegex.matches(in: line, range: NSRange(location: 0, length: nsLine.length)) - - if hymnMatches.isEmpty { return nil } - - var segments: [ContentSegment] = [] - var lastIndex = 0 - - for match in hymnMatches { - if match.range.location > lastIndex { - let text = nsLine.substring(with: NSRange(location: lastIndex, length: match.range.location - lastIndex)) - if !text.isEmpty { - segments.append((id: UUID(), text: text.trimmingCharacters(in: .whitespaces), type: .text, reference: nil)) - } - } - - let hymnNumber = Int(nsLine.substring(with: match.range(at: 1)))! - let fullHymnText = nsLine.substring(with: match.range) - segments.append((id: UUID(), text: fullHymnText, type: .hymn(number: hymnNumber), reference: nil)) - - lastIndex = match.range.location + match.range.length - } - - if lastIndex < nsLine.length { - let text = nsLine.substring(from: lastIndex) - if !text.isEmpty { - segments.append((id: UUID(), text: text.trimmingCharacters(in: .whitespaces), type: .text, reference: nil)) - } - } - - return segments - } - - private func processResponsiveReadings(_ line: String, _ nsLine: NSString) -> [ContentSegment]? { - let responsivePattern = #"(?:Responsive\s+Reading\s+(?:#\s*)?|#\s*)(\d+)(?:\s+["""]([^"""]*)[""])?.*"# - let responsiveRegex = try! NSRegularExpression(pattern: responsivePattern, options: [.caseInsensitive]) - let responsiveMatches = responsiveRegex.matches(in: line, range: NSRange(location: 0, length: nsLine.length)) - - if responsiveMatches.isEmpty { return nil } - - var segments: [ContentSegment] = [] - var lastIndex = 0 - - for match in responsiveMatches { - if match.range.location > lastIndex { - let text = nsLine.substring(with: NSRange(location: lastIndex, length: match.range.location - lastIndex)) - if !text.isEmpty { - segments.append((id: UUID(), text: text.trimmingCharacters(in: .whitespaces), type: .text, reference: nil)) - } - } - - let readingNumber = Int(nsLine.substring(with: match.range(at: 1)))! - let fullReadingText = nsLine.substring(with: match.range) - segments.append((id: UUID(), text: fullReadingText, type: .responsiveReading(number: readingNumber), reference: nil)) - - lastIndex = match.range.location + match.range.length - } - - if lastIndex < nsLine.length { - let text = nsLine.substring(from: lastIndex) - if !text.isEmpty { - segments.append((id: UUID(), text: text.trimmingCharacters(in: .whitespaces), type: .text, reference: nil)) - } - } - - return segments - } - - private func processBibleVerses(_ line: String, _ nsLine: NSString) -> [ContentSegment]? { - let versePattern = #"(?:^|\s|[,;])\s*(?:(?:1|2|3|I|II|III|First|Second|Third)\s+)?(?:Genesis|Exodus|Leviticus|Numbers|Deuteronomy|Joshua|Judges|Ruth|(?:1st|2nd|1|2)\s*Samuel|(?:1st|2nd|1|2)\s*Kings|(?:1st|2nd|1|2)\s*Chronicles|Ezra|Nehemiah|Esther|Job|Psalms?|Proverbs|Ecclesiastes|Song\s+of\s+Solomon|Isaiah|Jeremiah|Lamentations|Ezekiel|Daniel|Hosea|Joel|Amos|Obadiah|Jonah|Micah|Nahum|Habakkuk|Zephaniah|Haggai|Zechariah|Malachi|Matthew|Mark|Luke|John|Acts|Romans|(?:1st|2nd|1|2)\s*Corinthians|Galatians|Ephesians|Philippians|Colossians|(?:1st|2nd|1|2)\s*Thessalonians|(?:1st|2nd|1|2)\s*Timothy|Titus|Philemon|Hebrews|James|(?:1st|2nd|1|2)\s*Peter|(?:1st|2nd|3rd|1|2|3)\s*John|Jude|Revelation)s?\s+\d+(?:[:.]\d+(?:-\d+)?)?(?:\s*,\s*\d+(?:[:.]\d+(?:-\d+)?)?)*"# - let verseRegex = try! NSRegularExpression(pattern: versePattern, options: [.caseInsensitive]) - let verseMatches = verseRegex.matches(in: line, range: NSRange(location: 0, length: nsLine.length)) - - if verseMatches.isEmpty { return nil } - - var segments: [ContentSegment] = [] - var lastIndex = 0 - - for match in verseMatches { - if match.range.location > lastIndex { - let text = nsLine.substring(with: NSRange(location: lastIndex, length: match.range.location - lastIndex)) - if !text.isEmpty { - segments.append((id: UUID(), text: text.trimmingCharacters(in: .whitespaces), type: .text, reference: nil)) - } - } - - let verseText = nsLine.substring(with: match.range) - .trimmingCharacters(in: .whitespaces) - - // Extract the complete verse reference including chapter and verse - let referencePattern = #"(?:(?:1|2|3|I|II|III|First|Second|Third)\s+)?(?:Genesis|Exodus|Leviticus|Numbers|Deuteronomy|Joshua|Judges|Ruth|(?:1st|2nd|1|2)\s*Samuel|(?:1st|2nd|1|2)\s*Kings|(?:1st|2nd|1|2)\s*Chronicles|Ezra|Nehemiah|Esther|Job|Psalms?|Proverbs|Ecclesiastes|Song\s+of\s+Solomon|Isaiah|Jeremiah|Lamentations|Ezekiel|Daniel|Hosea|Joel|Amos|Obadiah|Jonah|Micah|Nahum|Habakkuk|Zephaniah|Haggai|Zechariah|Malachi|Matthew|Mark|Luke|John|Acts|Romans|(?:1st|2nd|1|2)\s*Corinthians|Galatians|Ephesians|Philippians|Colossians|(?:1st|2nd|1|2)\s*Thessalonians|(?:1st|2nd|1|2)\s*Timothy|Titus|Philemon|Hebrews|James|(?:1st|2nd|1|2)\s*Peter|(?:1st|2nd|3rd|1|2|3)\s*John|Jude|Revelation)s?\s+\d+(?:[:.]\d+(?:-\d+)?)?(?:\s*,\s*\d+(?:[:.]\d+(?:-\d+)?)?)*"# - let referenceRegex = try! NSRegularExpression(pattern: referencePattern, options: [.caseInsensitive]) - if let referenceMatch = referenceRegex.firstMatch(in: verseText, range: NSRange(location: 0, length: verseText.count)) { - let reference = (verseText as NSString).substring(with: referenceMatch.range) - segments.append((id: UUID(), text: verseText, type: .bibleVerse, reference: reference)) - } - - lastIndex = match.range.location + match.range.length - } - - if lastIndex < nsLine.length { - let text = nsLine.substring(from: lastIndex) - if !text.isEmpty { - segments.append((id: UUID(), text: text.trimmingCharacters(in: .whitespaces), type: .text, reference: nil)) - } - } - - return segments - } - - private func formatBibleVerse(_ verse: String) -> String { - // Strip out translation references (e.g., "KJV") - let cleanVerse = verse.replacingOccurrences(of: #"(?:\s+(?:KJV|NIV|ESV|NKJV|NLT|RSV|ASV|CEV|GNT|MSG|NET|NRSV|WEB|YLT|DBY|WNT|BBE|DARBY|WBS|KJ21|AKJV|ASV1901|CEB|CJB|CSB|ERV|EHV|EXB|GNV|GW|ICB|ISV|JUB|LEB|MEV|MOUNCE|NOG|OJB|RGT|TLV|VOICE|WYC|WYNN|YLT1898))"#, with: "", options: [.regularExpression, .caseInsensitive]) - .trimmingCharacters(in: .whitespaces) - - // Convert "Romans 4:11" to "rom.4.11" for Bible.com links - let bookMap = [ - "Genesis": "gen", "Exodus": "exo", "Leviticus": "lev", "Numbers": "num", - "Deuteronomy": "deu", "Joshua": "jos", "Judges": "jdg", "Ruth": "rut", - "1 Samuel": "1sa", "2 Samuel": "2sa", "1 Kings": "1ki", "2 Kings": "2ki", - "1 Chronicles": "1ch", "2 Chronicles": "2ch", "Ezra": "ezr", "Nehemiah": "neh", - "Esther": "est", "Job": "job", "Psalm": "psa", "Psalms": "psa", "Proverbs": "pro", - "Ecclesiastes": "ecc", "Song of Solomon": "sng", "Isaiah": "isa", "Jeremiah": "jer", - "Lamentations": "lam", "Ezekiel": "ezk", "Daniel": "dan", "Hosea": "hos", - "Joel": "jol", "Amos": "amo", "Obadiah": "oba", "Jonah": "jon", - "Micah": "mic", "Nahum": "nam", "Habakkuk": "hab", "Zephaniah": "zep", - "Haggai": "hag", "Zechariah": "zec", "Malachi": "mal", "Matthew": "mat", - "Mark": "mrk", "Luke": "luk", "John": "jhn", "Acts": "act", - "Romans": "rom", "1 Corinthians": "1co", "2 Corinthians": "2co", "Galatians": "gal", - "Ephesians": "eph", "Philippians": "php", "Colossians": "col", "1 Thessalonians": "1th", - "2 Thessalonians": "2th", "1 Timothy": "1ti", "2 Timothy": "2ti", "Titus": "tit", - "Philemon": "phm", "Hebrews": "heb", "James": "jas", "1 Peter": "1pe", - "2 Peter": "2pe", "1 John": "1jn", "2 John": "2jn", "3 John": "3jn", - "Jude": "jud", "Revelation": "rev" - ] - - let components = cleanVerse.components(separatedBy: " ") - guard components.count >= 2 else { return cleanVerse.lowercased() } - - // Handle book name (including numbered books like "1 Corinthians") - var bookName = "" - var remainingComponents: [String] = components - - if let firstComponent = components.first, let _ = Int(firstComponent) { - if components.count >= 2 { - bookName = components[0] + " " + components[1] - remainingComponents = Array(components.dropFirst(2)) - } - } else { - bookName = components[0] - remainingComponents = Array(components.dropFirst()) - } - - guard let bookCode = bookMap[bookName] else { return cleanVerse.lowercased() } - - // Format chapter and verse - let reference = remainingComponents.joined(separator: "") - .replacingOccurrences(of: ":", with: ".") - .replacingOccurrences(of: "-", with: "-") - .replacingOccurrences(of: ",", with: ".") - .replacingOccurrences(of: " ", with: "") - - return "\(bookCode).\(reference)" - } - - private func renderTextSegment(_ segment: ContentSegment) -> some View { - Text(segment.text) - .fixedSize(horizontal: false, vertical: true) - .multilineTextAlignment(.center) - .foregroundColor(.primary) - } - - private func renderHymnSegment(_ segment: ContentSegment, number: Int) -> some View { - Button(action: { - AppAvailabilityService.shared.openHymnByNumber(number) - }) { - HStack(spacing: 6) { - Image(systemName: "music.note") - .foregroundColor(.blue) - Text(segment.text) - .foregroundColor(.blue) - } - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background( - RoundedRectangle(cornerRadius: 8) - .fill(Color.blue.opacity(0.1)) - ) - } - } - - private func renderResponsiveReadingSegment(_ segment: ContentSegment, number: Int) -> some View { - Button(action: { - AppAvailabilityService.shared.openResponsiveReadingByNumber(number) - }) { - HStack { - Image(systemName: "book") - .foregroundColor(.blue) - Text("Responsive Reading #\(number)") - .foregroundColor(.blue) - } - } - } - - private func renderBibleVerseSegment(_ segment: ContentSegment, reference: String) -> some View { - Button(action: { - let formattedVerse = formatBibleVerse(reference) - if let url = URL(string: "https://www.bible.com/bible/1/\(formattedVerse)") { - UIApplication.shared.open(url) - } - }) { - HStack(spacing: 6) { - Image(systemName: "book.fill") - .foregroundColor(.blue) - Text(segment.text) - .foregroundColor(.blue) - } - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background( - RoundedRectangle(cornerRadius: 8) - .fill(Color.blue.opacity(0.1)) - ) - } - } - - private func renderSectionHeaderSegment(_ segment: ContentSegment) -> some View { - Text(segment.text) - .font(.headline) - .fontWeight(.semibold) - .foregroundColor(.primary) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.top, 16) - .padding(.bottom, 4) - .padding(.horizontal, 8) - } - - private func renderLine(_ line: String) -> some View { - let trimmedLine = line.trimmingCharacters(in: .whitespaces) - guard !trimmedLine.isEmpty else { return AnyView(EmptyView()) } - - return AnyView( - HStack(alignment: .center, spacing: 4) { - ForEach(processLine(trimmedLine), id: \.id) { segment in - switch segment.type { - case .text: - renderTextSegment(segment) - case .hymn(let number): - renderHymnSegment(segment, number: number) - case .responsiveReading(let number): - renderResponsiveReadingSegment(segment, number: number) - case .bibleVerse: - if let reference = segment.reference { - renderBibleVerseSegment(segment, reference: reference) - } - case .sectionHeader: - renderSectionHeaderSegment(segment) - } - } - } - .frame(maxWidth: .infinity, alignment: .center) - ) - } - - private func renderSection(_ title: String, content: String) -> some View { - VStack(alignment: .center, spacing: 16) { - Text(title) - .font(.title) - .fontWeight(.bold) - .foregroundColor(.primary) - .frame(maxWidth: .infinity, alignment: .center) - .padding(.bottom, 8) - - VStack(alignment: .center, spacing: 12) { - ForEach(Array(zip(content.components(separatedBy: .newlines).indices, - content.components(separatedBy: .newlines))), id: \.0) { _, line in - renderLine(line) - } - } - } - .padding(.vertical, 12) - } - - var body: some View { - VStack(alignment: .center, spacing: 24) { - ForEach(sectionOrder, id: \.0) { (title, keyPath) in - let content = bulletin[keyPath: keyPath] - if !content.isEmpty { - renderSection(title, content: content) - - if title != sectionOrder.last?.0 { - Divider() - .padding(.vertical, 8) - } - } - } - } - .frame(maxWidth: .infinity) - } -} - -#Preview { - BulletinView() -} diff --git a/Views/BulletinViews.swift b/Views/BulletinViews.swift deleted file mode 100644 index 694b7f5..0000000 --- a/Views/BulletinViews.swift +++ /dev/null @@ -1,151 +0,0 @@ -import SwiftUI - -struct BulletinListView: View { - @StateObject private var viewModel = BulletinViewModel() - - var body: some View { - NavigationView { - Group { - if viewModel.isLoading { - ProgressView() - } else if let error = viewModel.error { - VStack { - Text("Error loading bulletins") - .font(.headline) - .foregroundColor(.red) - Text(error.localizedDescription) - .font(.subheadline) - .foregroundColor(.secondary) - Button("Retry") { - Task { - await viewModel.loadBulletins() - } - } - .padding() - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(8) - } - .padding() - } else if viewModel.bulletins.isEmpty { - VStack { - Text("No Bulletins") - .font(.headline) - Text("No bulletins are available at this time.") - .font(.subheadline) - .foregroundColor(.secondary) - } - .padding() - } else { - List(viewModel.bulletins) { bulletin in - NavigationLink(destination: BulletinDetailView(bulletin: bulletin)) { - BulletinRowView(bulletin: bulletin) - } - } - } - } - .navigationTitle("Church Bulletins") - .task { - await viewModel.loadBulletins() - } - } - } -} - -struct BulletinSectionView: View { - let section: BulletinSection - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - Text(section.title) - .font(.headline) - - if section.title == "Scripture Reading" { - ScriptureReadingView(content: section.content) - } else { - BulletinContentText(content: section.content) - } - } - .padding() - .background(Color(.systemBackground)) - .cornerRadius(10) - .shadow(radius: 2) - } -} - -struct ScriptureReadingView: View { - let content: String - - var body: some View { - VStack(spacing: 8) { - ForEach(content.components(separatedBy: .newlines), id: \.self) { line in - if !line.isEmpty { - Text(line) - .font(.body) - .foregroundColor(line.contains("Acts") ? .primary : .secondary) - } - } - } - } -} - -struct BulletinContentText: View { - let content: String - - var formattedContent: [(label: String?, value: String)] { - content.components(separatedBy: .newlines) - .map { line -> (String?, String) in - let parts = line.split(separator: ":", maxSplits: 1).map(String.init) - if parts.count == 2 { - return (parts[0].trimmingCharacters(in: .whitespaces), - parts[1].trimmingCharacters(in: .whitespaces)) - } - return (nil, line) - } - .filter { !$0.1.isEmpty } - } - - var body: some View { - VStack(spacing: 12) { - ForEach(formattedContent, id: \.1) { item in - if let label = item.label { - VStack(spacing: 4) { - Text(label) - .font(.headline) - .foregroundColor(.primary) - Text(item.value) - .font(.body) - .foregroundColor(.secondary) - } - } else { - Text(item.value) - .font(.body) - .foregroundColor(.secondary) - } - } - } - } -} - -struct BulletinRowView: View { - let bulletin: Bulletin - - var body: some View { - VStack(alignment: .leading, spacing: 8) { - Text(bulletin.date.formatted(date: .long, time: .omitted)) - .font(.subheadline) - .foregroundColor(.secondary) - - Text(bulletin.title) - .font(.headline) - .lineLimit(2) - - if let pdf = bulletin.pdf, !pdf.isEmpty { - Label("PDF Available", systemImage: "doc.fill") - .font(.caption) - .foregroundColor(.blue) - } - } - .padding(.vertical, 8) - } -} \ No newline at end of file diff --git a/Views/BulletinsView.swift b/Views/BulletinsView.swift new file mode 100644 index 0000000..cb4b40b --- /dev/null +++ b/Views/BulletinsView.swift @@ -0,0 +1,206 @@ +import SwiftUI + +struct BulletinsView: View { + @Environment(ChurchDataService.self) private var dataService + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + List { + if dataService.isLoading { + HStack { + Spacer() + VStack { + ProgressView("Loading bulletins...") + Text("Fetching church bulletins") + .font(.caption) + .foregroundColor(.secondary) + .padding(.top, 8) + } + Spacer() + } + .listRowSeparator(.hidden) + } else if dataService.bulletins.isEmpty { + HStack { + Spacer() + VStack(spacing: 16) { + Image(systemName: "newspaper.badge.exclamationmark") + .font(.system(size: 50)) + .foregroundColor(.secondary) + + Text("No Bulletins Available") + .font(.headline) + .foregroundColor(.secondary) + + Text("Check back later for weekly church bulletins and announcements.") + .font(.subheadline) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal) + } + .padding() + Spacer() + } + .listRowSeparator(.hidden) + } else { + ForEach(dataService.bulletins) { bulletin in + NavigationLink { + BulletinDetailView(bulletin: bulletin) + } label: { + BulletinRowView(bulletin: bulletin) + } + } + } + } + .refreshable { + await dataService.loadAllBulletins() + } + .navigationTitle("Bulletins") + .navigationBarTitleDisplayMode(.large) + .task { + await dataService.loadAllBulletins() + } + } +} + +struct BulletinRowView: View { + let bulletin: ChurchBulletin + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + if horizontalSizeClass == .regular { + // iPad: Enhanced layout with more information + HStack(spacing: 20) { + // Bulletin icon + Image(systemName: "newspaper.fill") + .font(.title) + .foregroundColor(Color(hex: "fb8b23")) + .frame(width: 50, height: 50) + .background(Color(hex: "fb8b23").opacity(0.1), in: RoundedRectangle(cornerRadius: 12)) + + VStack(alignment: .leading, spacing: 8) { + HStack { + Spacer() + + Text(bulletin.formattedDate) + .font(.subheadline) + .foregroundColor(.secondary) + .fontWeight(.medium) + + if bulletin.pdfPath != nil { + Image(systemName: "arrow.down.circle.fill") + .font(.title3) + .foregroundColor(.green) + } + } + + Text(bulletin.title) + .font(.system(size: 20, weight: .semibold)) + .lineLimit(2) + .multilineTextAlignment(.leading) + + // Service details in grid layout for iPad + HStack(spacing: 40) { + VStack(alignment: .leading, spacing: 4) { + Text("Sabbath School") + .font(.caption2) + .fontWeight(.medium) + .foregroundColor(.secondary) + .textCase(.uppercase) + Text(bulletin.sabbathSchool) + .font(.subheadline) + .lineLimit(2) + } + .frame(maxWidth: .infinity, alignment: .leading) + + VStack(alignment: .leading, spacing: 4) { + Text("Divine Worship") + .font(.caption2) + .fontWeight(.medium) + .foregroundColor(.secondary) + .textCase(.uppercase) + Text(bulletin.divineWorship) + .font(.subheadline) + .lineLimit(2) + } + .frame(maxWidth: .infinity, alignment: .leading) + + VStack(alignment: .leading, spacing: 4) { + Text("Scripture Reading") + .font(.caption2) + .fontWeight(.medium) + .foregroundColor(.secondary) + .textCase(.uppercase) + Text(bulletin.scriptureReading) + .font(.subheadline) + .lineLimit(2) + } + .frame(maxWidth: .infinity, alignment: .leading) + } + } + + Image(systemName: "chevron.right") + .font(.caption) + .foregroundColor(.secondary) + } + .padding(.vertical, 16) + } else { + // iPhone: Compact layout (current design) + HStack(spacing: 12) { + // Bulletin icon + Image(systemName: "newspaper.fill") + .font(.title2) + .foregroundColor(Color(hex: "fb8b23")) + .frame(width: 40, height: 40) + .background(Color(hex: "fb8b23").opacity(0.1), in: Circle()) + + VStack(alignment: .leading, spacing: 6) { + HStack { + Spacer() + + if bulletin.pdfPath != nil { + Image(systemName: "arrow.down.circle.fill") + .font(.title3) + .foregroundColor(.green) + } + } + + Text(bulletin.title) + .font(.system(size: 16, weight: .semibold)) + .lineLimit(2) + .multilineTextAlignment(.leading) + + Text(bulletin.formattedDate) + .font(.system(size: 13)) + .foregroundColor(.secondary) + + // Service preview + HStack { + VStack(alignment: .leading, spacing: 2) { + Text("Sabbath School:") + .font(.caption2) + .foregroundColor(.secondary) + Text(bulletin.sabbathSchool) + .font(.caption) + .lineLimit(1) + } + + Spacer() + + VStack(alignment: .leading, spacing: 2) { + Text("Divine Worship:") + .font(.caption2) + .foregroundColor(.secondary) + Text(bulletin.divineWorship) + .font(.caption) + .lineLimit(1) + } + } + .padding(.top, 4) + } + + Spacer() + } + .padding(.vertical, 8) + } + } +} diff --git a/Views/CachedAsyncImage.swift b/Views/CachedAsyncImage.swift index abf0581..1d303ee 100644 --- a/Views/CachedAsyncImage.swift +++ b/Views/CachedAsyncImage.swift @@ -1,4 +1,14 @@ import SwiftUI +import Foundation + +// Response structure for cached image data +struct CachedImageResponse: Codable { + let success: Bool + let data: String? + let contentType: String? + let cached: Bool? + let error: String? +} struct CachedAsyncImage: View { private let url: URL? @@ -6,6 +16,10 @@ struct CachedAsyncImage: View { private let content: (Image) -> Content private let placeholder: () -> Placeholder + @State private var image: UIImage? + @State private var isLoading = false + @State private var loadError: Error? + init( url: URL?, scale: CGFloat = 1.0, @@ -20,45 +34,89 @@ struct CachedAsyncImage: View { var body: some View { Group { - if let url = url { - AsyncImage( - url: url, - scale: scale, - transaction: Transaction(animation: .easeInOut) - ) { phase in - switch phase { - case .empty: - placeholder() - case .success(let image): - content(image) - .task { - await storeImageInCache(image: image, url: url) - } - case .failure(_): - placeholder() - @unknown default: - placeholder() - } - } - .task { - await loadImageFromCache(url: url) - } + if let image = image { + content(Image(uiImage: image)) + } else if isLoading { + placeholder() + } else if loadError != nil { + placeholder() } else { placeholder() } } - } - - private func loadImageFromCache(url: URL) async { - guard let cachedImage = await ImageCache.shared.image(for: url) else { return } - _ = content(Image(uiImage: cachedImage)) - } - - private func storeImageInCache(image: Image, url: URL) async { - // Convert SwiftUI Image to UIImage and cache it - let renderer = ImageRenderer(content: content(image)) - if let uiImage = renderer.uiImage { - await ImageCache.shared.setImage(uiImage, for: url) + .onAppear { + loadCachedImage() + } + .onChange(of: url) { _, newURL in + image = nil + loadError = nil + loadCachedImage() } } + + private func loadCachedImage() { + guard let url = url else { return } + + isLoading = true + loadError = nil + + // Use background queue for image processing + DispatchQueue.global(qos: .userInitiated).async { + do { + // Call Rust crate function for cached image + let jsonResponse = fetchCachedImageBase64(url: url.absoluteString) + + // Parse JSON response + guard let jsonData = jsonResponse.data(using: .utf8) else { + DispatchQueue.main.async { + self.isLoading = false + self.loadError = NSError(domain: "CachedImageError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid JSON response"]) + } + return + } + + let response = try JSONDecoder().decode(CachedImageResponse.self, from: jsonData) + + if response.success, let base64Data = response.data { + // Decode base64 to image + if let imageData = Data(base64Encoded: base64Data), + let uiImage = UIImage(data: imageData) { + DispatchQueue.main.async { + withAnimation(.easeInOut(duration: 0.3)) { + self.image = uiImage + } + self.isLoading = false + } + } else { + DispatchQueue.main.async { + self.isLoading = false + self.loadError = NSError(domain: "CachedImageError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to decode image data"]) + } + } + } else { + DispatchQueue.main.async { + self.isLoading = false + self.loadError = NSError(domain: "CachedImageError", code: 3, userInfo: [NSLocalizedDescriptionKey: response.error ?? "Unknown error"]) + } + } + } catch { + DispatchQueue.main.async { + self.isLoading = false + self.loadError = error + } + } + } + } +} + +// Convenience initializer for common use case +extension CachedAsyncImage where Content == Image, Placeholder == ProgressView { + init(url: URL?, scale: CGFloat = 1.0) { + self.init( + url: url, + scale: scale, + content: { $0 }, + placeholder: { ProgressView() } + ) + } } \ No newline at end of file diff --git a/Views/Components/ContactActionRow.swift b/Views/Components/ContactActionRow.swift new file mode 100644 index 0000000..606e865 --- /dev/null +++ b/Views/Components/ContactActionRow.swift @@ -0,0 +1,98 @@ +import SwiftUI + +struct ContactActionRow: View { + let icon: String + let title: String + let subtitle: String + let iconColor: Color + let action: (() -> Void)? + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + init(icon: String, title: String, subtitle: String, iconColor: Color, action: (() -> Void)? = nil) { + self.icon = icon + self.title = title + self.subtitle = subtitle + self.iconColor = iconColor + self.action = action + } + + var body: some View { + Group { + if let action = action { + Button(action: action) { + content + } + .buttonStyle(.plain) + } else { + content + } + } + } + + private var content: some View { + HStack(spacing: 16) { + Image(systemName: icon) + .font(.title2) + .foregroundColor(iconColor) + .frame(width: 24) + + VStack(alignment: .leading, spacing: 4) { + Text(title) + .font(.system(size: horizontalSizeClass == .regular ? 16 : 14, weight: .semibold)) + .foregroundColor(.primary) + + Text(subtitle) + .font(.system(size: horizontalSizeClass == .regular ? 15 : 13, weight: .regular)) + .foregroundColor(.secondary) + .multilineTextAlignment(.leading) + } + + Spacer() + + if action != nil { + Image(systemName: "arrow.up.right.square") + .font(.caption) + .foregroundColor(.secondary) + } else { + Image(systemName: "chevron.right") + .font(.caption) + .foregroundColor(.secondary) + } + } + .padding(.vertical, 12) + .contentShape(Rectangle()) + } +} + +// MARK: - Contact Actions Helper +struct ContactActions { + static func callAction(phoneNumber: String) -> (() -> Void)? { + guard UIDevice.current.userInterfaceIdiom == .phone else { return nil } + + return { + let digitsOnly = phoneNumber.filter { $0.isNumber } + let phoneURL = URL(string: "tel://\(digitsOnly)") + + if let url = phoneURL, UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } + } + } + + static func emailAction(email: String) -> () -> Void { + return { + if let url = URL(string: "mailto:\(email)") { + UIApplication.shared.open(url) + } + } + } + + static func directionsAction(address: String) -> () -> Void { + return { + let encodedAddress = address.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? address + if let url = URL(string: "https://maps.apple.com/?address=\(encodedAddress)") { + UIApplication.shared.open(url) + } + } + } +} diff --git a/Views/Components/ContentCards.swift b/Views/Components/ContentCards.swift new file mode 100644 index 0000000..e57e1ba --- /dev/null +++ b/Views/Components/ContentCards.swift @@ -0,0 +1,476 @@ +import SwiftUI + +#if os(iOS) +struct ShareSheet: UIViewControllerRepresentable { + let activityItems: [Any] + + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} +} +#endif + +// MARK: - Sermon Card (for Watch view) + +enum SermonCardStyle { + case watch // For Watch tab (compact) + case feed // For Home feed (prominent with large thumbnail) +} + +struct SermonCard: View { + let sermon: Sermon + let style: SermonCardStyle + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @State private var showingScriptureSheet = false + @State private var showingShareSheet = false + @State private var scriptureText = "" + + init(sermon: Sermon, style: SermonCardStyle = .watch) { + self.sermon = sermon + self.style = style + } + + var body: some View { + Button { + if let videoUrl = sermon.videoUrl { + let optimalUrl = getOptimalStreamingUrl(mediaId: sermon.id) + let urlToUse = !optimalUrl.isEmpty ? optimalUrl : videoUrl + + if let url = URL(string: urlToUse) { + SharedVideoManager.shared.playVideo(url: url, title: sermon.title, artworkURL: sermon.thumbnail) + } + } + } label: { + switch style { + case .watch: + watchStyleCard + case .feed: + feedStyleCard + } + } + .buttonStyle(.plain) + .contextMenu { + Button("View Scripture", systemImage: "book.fill") { + Task { + let verses = fetchScriptureVersesForSermonJson(sermonId: sermon.id) + await MainActor.run { + scriptureText = verses + showingScriptureSheet = true + } + } + } + + Button("Share Sermon", systemImage: "square.and.arrow.up") { + showingShareSheet = true + } + + if sermon.audioUrl != nil && sermon.videoUrl == nil { + Button("Play Audio Only", systemImage: "speaker.wave.2") { + // TODO: Implement audio-only playback + } + } + } + .sheet(isPresented: $showingScriptureSheet) { + ScriptureSheet(scriptureText: $scriptureText) + } + #if os(iOS) + .sheet(isPresented: $showingShareSheet) { + ShareSheet(activityItems: createShareItems(for: sermon)) + } + #endif + .shadow(color: .black.opacity(0.1), radius: style == .feed ? 8 : 6, x: 0, y: style == .feed ? 4 : 2) + } + + // MARK: - Watch Style (Compact) + private var watchStyleCard: some View { + Group { + if horizontalSizeClass == .regular { + // iPad: Horizontal layout + HStack(spacing: 16) { + thumbnailView() + .frame(width: 120, height: 90) + + watchContentView + + Spacer() + + playButton + } + .padding(16) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12)) + } else { + // FIXED: Proper layout with real components + VStack(alignment: .leading, spacing: 16) { + thumbnailView(durationAlignment: .bottomTrailing) + .frame(height: 160) + + watchContentView + .padding(.horizontal, 16) + .padding(.bottom, 16) + } + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12)) + } + } + } + + // MARK: - Feed Style (Prominent) + private var feedStyleCard: some View { + VStack(alignment: .leading, spacing: 0) { + thumbnailView(durationAlignment: .bottomTrailing) + .frame(height: horizontalSizeClass == .regular ? 200 : 180) + + // Content section + feedContentView + .padding(16) + } + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + } + + private func thumbnailView(durationAlignment: Alignment = .bottomTrailing) -> some View { + // FIXED: Use .fill but force proper clipping with frame + Rectangle() + .fill(.clear) + .overlay { + CachedAsyncImage(url: URL(string: sermon.thumbnail ?? sermon.image ?? "")) { image in + image + .resizable() + .aspectRatio(contentMode: .fill) + } placeholder: { + Rectangle() + .fill(.gray) + .overlay { + Text("Loading...") + .foregroundColor(.white) + } + } + } + .clipped() // Force clipping BEFORE clipShape + .clipShape(RoundedRectangle(cornerRadius: 8)) + .overlay(alignment: durationAlignment) { + if let duration = sermon.durationFormatted { + Text(duration) + .font(.caption2) + .fontWeight(.medium) + .padding(.horizontal, 6) + .padding(.vertical, 3) + .background(.black.opacity(0.7), in: Capsule()) + .foregroundStyle(.white) + .padding(4) + } + } + } + + // MARK: - Content Views + + private var watchContentView: some View { + VStack(alignment: .leading, spacing: 8) { + Text(sermon.title.trimmingCharacters(in: .whitespacesAndNewlines)) + .font(.headline) + .fontWeight(.semibold) + .lineLimit(2) + .multilineTextAlignment(.leading) + + if !sermon.speaker.isEmpty { + Text("by \(sermon.speaker.trimmingCharacters(in: .whitespacesAndNewlines))") + .font(.subheadline) + .foregroundStyle(.secondary) + .lineLimit(1) + } + + if !sermon.formattedDate.isEmpty && sermon.formattedDate != "Date unknown" { + Text(sermon.formattedDate) + .font(.caption) + .foregroundStyle(.secondary) + } + } + } + + private var feedContentView: some View { + VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + Text(sermon.title.trimmingCharacters(in: .whitespacesAndNewlines)) + .font(.headline) + .fontWeight(.semibold) + .lineLimit(2) + .multilineTextAlignment(.leading) + .fixedSize(horizontal: false, vertical: true) + + if !sermon.speaker.isEmpty { + Text("by \(sermon.speaker.trimmingCharacters(in: .whitespacesAndNewlines))") + .font(.subheadline) + .foregroundStyle(.secondary) + } + } + + if !sermon.formattedDate.isEmpty && sermon.formattedDate != "Date unknown" { + Text(sermon.formattedDate) + .font(.caption) + .foregroundStyle(.secondary) + } + } + } + + private var playButton: some View { + Button { + // Play sermon + } label: { + Image(systemName: "play.circle.fill") + .font(.system(size: 24)) + .foregroundStyle(Color(hex: "fb8b23")) + } + } +} + +// MARK: - Event Card (for Connect view) + +struct EventCard: View { + let event: ChurchEvent + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + NavigationLink { + EventDetailViewWrapper(event: event) + } label: { + VStack(alignment: .leading, spacing: 0) { + // Event image or gradient + Group { + if let imageUrl = event.thumbnail ?? event.image { + CachedAsyncImage(url: URL(string: imageUrl)) { image in + image + .resizable() + .aspectRatio(contentMode: .fill) + } placeholder: { + eventGradient + } + } else { + eventGradient + } + } + .frame(height: horizontalSizeClass == .regular ? 160 : 140) + .clipped() + + // Content + VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + Text(event.title) + .font(.headline) + .fontWeight(.semibold) + .lineLimit(2) + .multilineTextAlignment(.leading) + + Text(event.formattedDate) + .font(.subheadline) + .foregroundStyle(.secondary) + } + + if !event.location.isEmpty { + Label { + Text(event.location) + .font(.caption) + .lineLimit(1) + } icon: { + Image(systemName: "location.fill") + .font(.caption) + } + .foregroundStyle(.secondary) + } + + if !event.category.isEmpty { + Text(event.category) + .font(.caption) + .fontWeight(.medium) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(.blue.opacity(0.1), in: Capsule()) + .foregroundStyle(.blue) + } + } + .padding(16) + } + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12)) + .shadow(color: .black.opacity(0.1), radius: 6, x: 0, y: 2) + } + .buttonStyle(.plain) + + } + + private var eventGradient: some View { + LinearGradient( + colors: [.blue, .blue.opacity(0.7)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + .overlay { + Image(systemName: "calendar") + .font(.system(size: 32)) + .foregroundStyle(.white.opacity(0.7)) + } + } +} + +// MARK: - Bulletin Card (for Discover view) + +struct BulletinCard: View { + let bulletin: ChurchBulletin + + var body: some View { + NavigationLink { + BulletinDetailView(bulletin: bulletin) + } label: { + VStack(alignment: .leading, spacing: 0) { + // Header with PDF icon + HStack { + Image(systemName: "doc.text.fill") + .font(.system(size: 32)) + .foregroundStyle(Color(hex: "fb8b23")) + + VStack(alignment: .leading, spacing: 4) { + Text("Church Bulletin") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + + Text(bulletin.formattedDate) + .font(.caption) + .foregroundStyle(.secondary) + } + + Spacer() + + if bulletin.pdfPath != nil { + Image(systemName: "arrow.down.circle.fill") + .font(.title3) + .foregroundStyle(.green) + } + } + .padding(16) + .background(.regularMaterial) + + Divider() + + // Content preview + VStack(alignment: .leading, spacing: 12) { + Text(bulletin.title) + .font(.headline) + .fontWeight(.semibold) + .lineLimit(2) + .multilineTextAlignment(.leading) + + VStack(alignment: .leading, spacing: 4) { + if !bulletin.sabbathSchool.isEmpty { + Text("Sabbath School: \(bulletin.sabbathSchool)") + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + } + if !bulletin.divineWorship.isEmpty { + Text("Divine Worship: \(bulletin.divineWorship)") + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + } + } + + HStack { + Label("Tap to view", systemImage: "eye.fill") + .font(.caption) + .foregroundStyle(.blue) + + Spacer() + + if bulletin.pdfPath != nil { + Label("PDF Available", systemImage: "doc.fill") + .font(.caption) + .foregroundStyle(.green) + } + } + } + .padding(16) + } + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12)) + .shadow(color: .black.opacity(0.1), radius: 6, x: 0, y: 2) + } + .buttonStyle(.plain) + + } +} + +// MARK: - Quick Action Card + +struct QuickActionCard: View { + let title: String + let icon: String + let color: Color + let action: () -> Void + + var body: some View { + Button(action: action) { + VStack(spacing: 12) { + Image(systemName: icon) + .font(.system(size: 24)) + .foregroundStyle(color) + + Text(title) + .font(.subheadline) + .fontWeight(.medium) + .foregroundStyle(.primary) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity) + .padding(20) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12)) + .shadow(color: .black.opacity(0.1), radius: 6, x: 0, y: 2) + } + .buttonStyle(.plain) + + } +} + +#Preview { + ScrollView { + VStack(spacing: 20) { + SermonCard(sermon: Sermon.sampleSermon()) + + EventCard(event: ChurchEvent( + id: "2", + title: "Community Potluck Dinner", + description: "Join us for fellowship", + startTime: "2025-01-15T18:00:00-05:00", + endTime: "2025-01-15T20:00:00-05:00", + formattedTime: "6:00 PM - 8:00 PM", + formattedDate: "January 15, 2025", + formattedDateTime: "January 15, 2025 at 6:00 PM", + dayOfMonth: "15", + monthAbbreviation: "JAN", + timeString: "6:00 PM - 8:00 PM", + isMultiDay: false, + detailedTimeDisplay: "6:00 PM - 8:00 PM", + location: "Fellowship Hall", + locationUrl: nil, + image: nil, + thumbnail: nil, + category: "Social", + isFeatured: false, + recurringType: nil, + createdAt: "2025-01-10T09:00:00-05:00", + updatedAt: "2025-01-10T09:00:00-05:00" + )) + + BulletinCard(bulletin: ChurchBulletin( + id: "3", + title: "January 11, 2025", + date: "Saturday, January 11, 2025", + sabbathSchool: "The Book of Romans", + divineWorship: "Walking in Faith", + scriptureReading: "Romans 8:28-39", + sunset: "5:47 PM", + pdfPath: "https://example.com/bulletin.pdf", + coverImage: nil, + isActive: true + )) + } + .padding() + } +} diff --git a/Views/Components/FeedItemCard.swift b/Views/Components/FeedItemCard.swift new file mode 100644 index 0000000..19f123a --- /dev/null +++ b/Views/Components/FeedItemCard.swift @@ -0,0 +1,289 @@ +import SwiftUI + +struct FeedItemCard: View { + let item: FeedItem + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + Group { + switch item.type { + case .sermon(let sermon): + SermonCard(sermon: sermon, style: .feed) + case .event(let event): + EventFeedCard(event: event) + case .bulletin(let bulletin): + BulletinFeedCard(bulletin: bulletin) + case .verse(let verse): + VerseFeedCard(verse: verse) + } + } + .containerRelativeFrame(.horizontal) { width, _ in + horizontalSizeClass == .regular ? min(width * 0.9, 600) : width * 0.95 + } + } +} + + +// MARK: - Event Feed Card + +struct EventFeedCard: View { + let event: ChurchEvent + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + NavigationLink { + EventDetailViewWrapper(event: event) + } label: { + HStack(spacing: 16) { + // Date indicator + VStack(spacing: 4) { + Text(event.dayOfMonth) + .font(.system(size: 18, weight: .bold)) + .foregroundColor(Color(hex: "fb8b23")) + + Text(event.monthAbbreviation) + .font(.system(size: 10, weight: .semibold)) + .foregroundColor(.secondary) + .textCase(.uppercase) + } + .frame(width: 50) + .padding(.vertical, 8) + .background(Color(hex: "fb8b23").opacity(Double(0.1)), in: RoundedRectangle(cornerRadius: 8)) + + // Event content + VStack(alignment: .leading, spacing: 8) { + Text(event.title) + .font(.system(size: 16, weight: .semibold)) + .lineLimit(2) + + if !event.description.isEmpty { + Text(event.description.stripHtml()) + .font(.system(size: 14)) + .foregroundColor(.secondary) + .lineLimit(2) + } + + HStack(spacing: 8) { + Image(systemName: "clock.fill") + .font(.caption) + .foregroundColor(Color(hex: "fb8b23")) + + Text(event.timeString) + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.secondary) + + if !event.location.isEmpty { + Image(systemName: "location.fill") + .font(.caption) + .foregroundColor(Color(hex: "fb8b23")) + + Text(event.location) + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.secondary) + .lineLimit(1) + } + } + } + + Spacer() + + // Event image (if available) + if let imageUrl = event.thumbnail ?? event.image { + AsyncImage(url: URL(string: imageUrl)) { image in + image + .resizable() + .aspectRatio(contentMode: .fill) + } placeholder: { + RoundedRectangle(cornerRadius: 8) + .fill(.secondary.opacity(0.3)) + } + .frame(width: 60, height: 60) + .clipShape(RoundedRectangle(cornerRadius: 8)) + } + } + .padding(16) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) + } + .buttonStyle(.plain) + } + + private func dayFromDate(_ dateString: String) -> String { + let formatter = ISO8601DateFormatter() + if let date = formatter.date(from: dateString) { + let dayFormatter = DateFormatter() + dayFormatter.dateFormat = "d" + return dayFormatter.string(from: date) + } + return "?" + } + + private func monthFromDate(_ dateString: String) -> String { + let formatter = ISO8601DateFormatter() + if let date = formatter.date(from: dateString) { + let monthFormatter = DateFormatter() + monthFormatter.dateFormat = "MMM" + return monthFormatter.string(from: date).uppercased() + } + return "???" + } +} + +// MARK: - Bulletin Feed Card + +struct BulletinFeedCard: View { + let bulletin: ChurchBulletin + + var body: some View { + NavigationLink { + BulletinDetailView(bulletin: bulletin) + } label: { + HStack(spacing: 16) { + // PDF icon + Image(systemName: "doc.text.fill") + .font(.system(size: 32)) + .foregroundStyle(Color(hex: "fb8b23")) + .frame(width: 50) + + // Bulletin content + VStack(alignment: .leading, spacing: 8) { + Text(bulletin.title) + .font(.headline) + .fontWeight(.semibold) + .lineLimit(2) + .multilineTextAlignment(.leading) + + Text(bulletin.formattedDate) + .font(.subheadline) + .foregroundStyle(.secondary) + + Label("Church Bulletin", systemImage: "newspaper.fill") + .font(.caption) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(.green.opacity(0.1), in: Capsule()) + .foregroundStyle(.green) + } + + Spacer() + + Image(systemName: "chevron.right") + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(16) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) + } + .buttonStyle(.plain) + } +} + +// MARK: - Bible Verse Feed Card + +struct VerseFeedCard: View { + let verse: BibleVerse + + var body: some View { + VStack(alignment: .leading, spacing: 16) { + HStack { + Image(systemName: "book.fill") + .foregroundStyle(Color(hex: "fb8b23")) + + Text("Verse of the Day") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + + Spacer() + } + + Text("\"\(verse.text)\"") + .font(.body) + .fontWeight(.medium) + .italic() + .lineLimit(4) + .multilineTextAlignment(.leading) + + HStack { + Text(verse.reference) + .font(.caption) + .fontWeight(.semibold) + .foregroundStyle(Color(hex: "fb8b23")) + + if let version = verse.version { + Text(version) + .font(.caption) + .foregroundStyle(.secondary) + } + + Spacer() + + Button { + // Share verse + } label: { + Image(systemName: "square.and.arrow.up") + .font(.caption) + .foregroundStyle(.secondary) + } + } + } + .padding(16) + .background( + LinearGradient( + colors: [Color(hex: "fb8b23").opacity(0.1), Color(hex: "fb8b23").opacity(0.05)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ), + in: RoundedRectangle(cornerRadius: 16) + ) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color(hex: "fb8b23").opacity(0.2), lineWidth: 1) + ) + } +} + +// MARK: - Color Extension + +extension Color { + init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let a, r, g, b: UInt64 + switch hex.count { + case 3: // RGB (12-bit) + (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (a, r, g, b) = (1, 1, 1, 0) + } + + self.init( + .sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: Double(a) / 255 + ) + } +} + +#Preview { + VStack(spacing: 20) { + FeedItemCard(item: FeedItem( + type: .sermon(Sermon.sampleSermon()), + timestamp: Date() + )) + + FeedItemCard(item: FeedItem( + type: .event(ChurchEvent.sampleEvent()), + timestamp: Date() + )) + } + .padding() +} \ No newline at end of file diff --git a/Views/Components/ScriptureComponents.swift b/Views/Components/ScriptureComponents.swift new file mode 100644 index 0000000..a3bd5f0 --- /dev/null +++ b/Views/Components/ScriptureComponents.swift @@ -0,0 +1,212 @@ +import SwiftUI + +// MARK: - Scripture Sheet Components + +struct ScriptureSheet: View { + @Binding var scriptureText: String + @Environment(\.dismiss) private var dismiss + + init(scriptureText: Binding) { + self._scriptureText = scriptureText + } + + var body: some View { + NavigationStack { + ScrollView { + VStack(alignment: .leading, spacing: 20) { + if scriptureText.isEmpty { + Text("No scripture text available") + .foregroundStyle(.secondary) + .padding() + } else { + let sections = formatScriptureText(scriptureText) + + ForEach(Array(sections.enumerated()), id: \.offset) { index, section in + VStack(alignment: .leading, spacing: 8) { + // Verse text + Text(section.verse) + .font(.body) + .lineSpacing(4) + #if os(iOS) + .textSelection(.enabled) + #endif + + // Reference + if !section.reference.isEmpty { + Text(section.reference) + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .padding(.top, 4) + } + } + .padding(.horizontal) + + // Add divider between sections (except last) + if index < sections.count - 1 { + Divider() + .padding(.horizontal) + } + } + + Spacer(minLength: 20) + } + } + .padding(.vertical) + } + .navigationTitle("Scripture Reading") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + dismiss() + } + } + } + } + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.visible) + } + + private func formatScriptureText(_ text: String) -> [ScriptureSection] { + // Use the Rust implementation for consistent formatting across platforms + let jsonString = formatScriptureTextJson(scriptureText: text) + guard let data = jsonString.data(using: .utf8), + let sections = try? JSONDecoder().decode([ScriptureSection].self, from: data) else { + // Fallback if JSON parsing fails + return [ScriptureSection(verse: text, reference: "")] + } + return sections + } +} + +// MARK: - Scripture Sheet with Sermon ID (loads data automatically) + +struct ScriptureSheetForSermon: View { + let sermonId: String + @Environment(\.dismiss) private var dismiss + @State private var scriptureText: String = "" + @State private var isLoading = true + + var body: some View { + NavigationStack { + ScrollView { + VStack(alignment: .leading, spacing: 20) { + if isLoading { + ProgressView("Loading scripture...") + .padding() + } else if scriptureText.isEmpty { + Text("No scripture text available for this sermon") + .foregroundStyle(.secondary) + .padding() + } else { + let sections = formatScriptureText(scriptureText) + + ForEach(Array(sections.enumerated()), id: \.offset) { index, section in + VStack(alignment: .leading, spacing: 8) { + // Verse text + Text(section.verse) + .font(.body) + .lineSpacing(4) + #if os(iOS) + .textSelection(.enabled) + #endif + + // Reference + if !section.reference.isEmpty { + Text(section.reference) + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .padding(.top, 4) + } + } + .padding(.horizontal) + + // Add divider between sections (except last) + if index < sections.count - 1 { + Divider() + .padding(.horizontal) + } + } + + Spacer(minLength: 20) + } + } + .padding(.vertical) + } + .navigationTitle("Scripture Reading") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + dismiss() + } + } + } + } + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.visible) + .task { + isLoading = true + scriptureText = fetchScriptureVersesForSermonJson(sermonId: sermonId) + isLoading = false + } + } + + private func formatScriptureText(_ text: String) -> [ScriptureSection] { + // Use the Rust implementation for consistent formatting across platforms + let jsonString = formatScriptureTextJson(scriptureText: text) + guard let data = jsonString.data(using: .utf8), + let sections = try? JSONDecoder().decode([ScriptureSection].self, from: data) else { + // Fallback if JSON parsing fails + return [ScriptureSection(verse: text, reference: "")] + } + return sections + } +} + +struct ScriptureSection: Codable { + let verse: String + let reference: String +} + +// MARK: - Scripture Utilities + +func extractScriptureReferences(from text: String) -> String { + // Use the Rust implementation for consistent parsing across platforms + return extractScriptureReferencesString(scriptureText: text) +} + +// MARK: - Share Utilities + +func createShareItems(for sermon: Sermon) -> [Any] { + // Use the Rust implementation for consistent share text across platforms + let jsonString = createSermonShareItemsJson( + title: sermon.title, + speaker: sermon.speaker, + videoUrl: sermon.videoUrl, + audioUrl: sermon.audioUrl + ) + + guard let data = jsonString.data(using: .utf8), + let shareStrings = try? JSONDecoder().decode([String].self, from: data) else { + // Fallback + return ["Check out this sermon: \"\(sermon.title)\" by \(sermon.speaker)"] + } + + var items: [Any] = [] + for shareString in shareStrings { + if let url = URL(string: shareString), shareString.hasPrefix("http") { + items.append(url) + } else { + items.append(shareString) + } + } + + return items +} diff --git a/Views/Components/ServiceTimeRow.swift b/Views/Components/ServiceTimeRow.swift new file mode 100644 index 0000000..64a463c --- /dev/null +++ b/Views/Components/ServiceTimeRow.swift @@ -0,0 +1,30 @@ +import SwiftUI + +struct ServiceTimeRow: View { + let day: String + let time: String + let service: String + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(day) + .font(.caption) + .foregroundStyle(.secondary) + .textCase(.uppercase) + + Text(time) + .font(.system(size: horizontalSizeClass == .regular ? 18 : 16, weight: .semibold)) + .foregroundStyle(Color(hex: "fb8b23")) + } + + Spacer() + + Text(service) + .font(.system(size: horizontalSizeClass == .regular ? 16 : 14, weight: .medium)) + .foregroundStyle(.primary) + } + .padding(.vertical, 4) + } +} \ No newline at end of file diff --git a/Views/ConnectView.swift b/Views/ConnectView.swift new file mode 100644 index 0000000..9f75122 --- /dev/null +++ b/Views/ConnectView.swift @@ -0,0 +1,454 @@ +import SwiftUI +import MapKit +import CoreLocation + +struct ConnectView: View { + @Environment(ChurchDataService.self) private var dataService + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @State private var churchConfig: ChurchConfig? + @State private var showingContactForm = false + + var body: some View { + ScrollView { + LazyVStack(spacing: 0) { + // Hero Section + VStack(spacing: 24) { + VStack(spacing: 16) { + Text(getChurchName()) + .font(.system(size: horizontalSizeClass == .regular ? 42 : 32, weight: .bold, design: .serif)) + .foregroundColor(.primary) + + Text("Proclaiming the Three Angels' Messages") + .font(.system(size: horizontalSizeClass == .regular ? 18 : 16, weight: .medium)) + .foregroundColor(Color(hex: getBrandColor())) + .italic() + } + .padding(.top, 32) + } + .padding(.bottom, 40) + + // Three Angels Messages + VStack(alignment: .leading, spacing: 24) { + Text("The Three Angels' Messages") + .font(.system(size: horizontalSizeClass == .regular ? 32 : 28, weight: .bold, design: .serif)) + .padding(.horizontal, 20) + + Text("Our mission is centered on the prophetic messages of Revelation 14, calling people to worship God, proclaim His truth, and prepare for Christ's second coming.") + .font(.system(size: horizontalSizeClass == .regular ? 18 : 16, weight: .regular)) + .lineSpacing(4) + .padding(.horizontal, 20) + .foregroundColor(.secondary) + + if horizontalSizeClass == .regular { + // iPad: Horizontal layout + HStack(spacing: 16) { + ThreeAngelsMessagesView() + } + .padding(.horizontal, 20) + } else { + // iPhone: Vertical layout + VStack(spacing: 16) { + ThreeAngelsMessagesView() + } + .padding(.horizontal, 20) + } + } + .padding(.bottom, 40) + + // Worship With Us Section + VStack(alignment: .leading, spacing: 16) { + Text("Worship With Us") + .font(.system(size: horizontalSizeClass == .regular ? 28 : 24, weight: .bold)) + .padding(.horizontal, 20) + + ServiceTimesSection() + .padding(20) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .padding(.horizontal, 20) + } + .padding(.bottom, 32) + + // Mission Statement + VStack(alignment: .leading, spacing: 16) { + Text("Our Mission") + .font(.system(size: horizontalSizeClass == .regular ? 28 : 24, weight: .bold)) + .padding(.horizontal, 20) + + Text(getAboutText()) + .font(.system(size: horizontalSizeClass == .regular ? 18 : 16, weight: .regular)) + .lineSpacing(4) + .padding(24) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .padding(.horizontal, 20) + + NavigationLink { + BeliefsView() + } label: { + HStack { + Text("Our 28 Fundamental Beliefs") + .font(.system(size: horizontalSizeClass == .regular ? 18 : 16, weight: .semibold)) + Spacer() + Image(systemName: "chevron.right") + } + .foregroundColor(.white) + .padding(20) + .background(Color(hex: getBrandColor()), in: RoundedRectangle(cornerRadius: 12)) + } + .padding(.horizontal, 20) + } + .padding(.bottom, 32) + + // Visit & Connect Section + VStack(alignment: .leading, spacing: 16) { + Text("Visit & Connect") + .font(.system(size: horizontalSizeClass == .regular ? 28 : 24, weight: .bold)) + .padding(.horizontal, 20) + + // Interactive Map + ChurchMapView() + .padding(.horizontal, 20) + + // Contact Information + VStack(spacing: 12) { + ContactActionRow( + icon: "location.fill", + title: "Visit Us", + subtitle: getChurchAddress(), + iconColor: .red, + action: ContactActions.directionsAction(address: getChurchAddress().replacingOccurrences(of: " ", with: "+")) + ) + + if UIDevice.current.userInterfaceIdiom == .phone { + ContactActionRow( + icon: "phone.fill", + title: "Call Us", + subtitle: getContactPhone(), + iconColor: .green, + action: ContactActions.callAction(phoneNumber: getContactPhone()) + ) + } + + ContactActionRow( + icon: "envelope.fill", + title: "Email Us", + subtitle: getContactEmail(), + iconColor: .blue, + action: ContactActions.emailAction(email: getContactEmail()) + ) + } + .padding(20) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .padding(.horizontal, 20) + + // Send Message Button + Button { + showingContactForm = true + } label: { + HStack { + Image(systemName: "paperplane.fill") + Text("Send Us a Message") + .fontWeight(.semibold) + } + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding(16) + .background(Color(hex: getBrandColor()), in: RoundedRectangle(cornerRadius: 12)) + } + .padding(.horizontal, 20) + } + .padding(.bottom, 32) + + // Support Our Ministry Section + VStack(alignment: .leading, spacing: 16) { + Text("Support Our Ministry") + .font(.system(size: horizontalSizeClass == .regular ? 28 : 24, weight: .bold)) + .padding(.horizontal, 20) + + Text("Your generous gifts help us share the Three Angels' Messages and serve our community.") + .font(.system(size: horizontalSizeClass == .regular ? 18 : 16, weight: .regular)) + .lineSpacing(4) + .padding(.horizontal, 20) + .foregroundColor(.secondary) + + Link(destination: URL(string: getDonationUrl())!) { + HStack { + Image(systemName: "heart.fill") + Text("Give Securely Online") + .fontWeight(.semibold) + } + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding(16) + .background(.green, in: RoundedRectangle(cornerRadius: 12)) + } + .padding(.horizontal, 20) + + HStack { + Image(systemName: "lock.shield.fill") + .foregroundColor(.green) + Text("Secure giving powered by Adventist Giving") + .font(.caption) + .foregroundColor(.secondary) + } + .padding(.horizontal, 20) + } + .padding(.bottom, 100) + } + } + .navigationTitle("About Us") + .navigationBarTitleDisplayMode(.inline) + .onAppear { + loadChurchConfig() + } + .sheet(isPresented: $showingContactForm) { + ContactFormView(isModal: true) + .environment(dataService) + } + } + + private func loadChurchConfig() { + // Use Rust functions directly - NO JSON parsing in Swift! + self.churchConfig = ChurchConfig( + contactPhone: getContactPhone(), + contactEmail: getContactEmail(), + churchAddress: getChurchAddress(), + churchName: getChurchName() + ) + } +} + +struct ChurchMapView: View { + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + // Church location coordinates from Rust + private var churchLocation: CLLocationCoordinate2D { + let coords = getCoordinates() + return CLLocationCoordinate2D( + latitude: coords[0], + longitude: coords[1] + ) + } + + @State private var cameraPosition: MapCameraPosition = .region( + MKCoordinateRegion( + center: CLLocationCoordinate2D(latitude: 41.8703594, longitude: -72.4077036), + span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) + ) + ) + + var body: some View { + Map(position: $cameraPosition) { + Annotation("Rockville Tolland SDA Church", coordinate: churchLocation) { + VStack { + Image(systemName: "house.fill") + .font(.title2) + .foregroundColor(.white) + .padding(8) + .background(Color(hex: getBrandColor()), in: Circle()) + .shadow(radius: 4) + + Text("RTSDA") + .font(.caption) + .fontWeight(.bold) + .foregroundColor(.white) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(Color.black.opacity(0.8), in: Capsule()) + .shadow(radius: 3) + } + } + } + .frame(height: horizontalSizeClass == .regular ? 250 : 200) + .cornerRadius(16) + .onTapGesture { + // Open in Apple Maps + let placemark = MKPlacemark(coordinate: churchLocation) + let mapItem = MKMapItem(placemark: placemark) + mapItem.name = "Rockville Tolland SDA Church" + mapItem.openInMaps(launchOptions: [ + MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving + ]) + } + .onAppear { + // Update camera position when config loads + cameraPosition = .region( + MKCoordinateRegion( + center: churchLocation, + span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01) + ) + ) + } + } +} + +struct AngelMessageCard: View { + let number: Int + let title: String + let reference: String + let description: String + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + VStack(spacing: 12) { + // Number circle + Text("\(number)") + .font(.system(size: horizontalSizeClass == .regular ? 28 : 24, weight: .bold)) + .foregroundColor(.white) + .frame(width: horizontalSizeClass == .regular ? 50 : 44, height: horizontalSizeClass == .regular ? 50 : 44) + .background(Color(hex: getBrandColor()), in: Circle()) + + VStack(spacing: 8) { + Text(title) + .font(.system(size: horizontalSizeClass == .regular ? 18 : 16, weight: .semibold)) + .multilineTextAlignment(.center) + + Text(reference) + .font(.system(size: horizontalSizeClass == .regular ? 14 : 12, weight: .medium)) + .foregroundColor(Color(hex: getBrandColor())) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(Color(hex: getBrandColor()).opacity(0.1), in: Capsule()) + + Text(description) + .font(.system(size: horizontalSizeClass == .regular ? 15 : 13)) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + .lineLimit(4) + } + } + .padding(horizontalSizeClass == .regular ? 20 : 16) + .frame(maxWidth: .infinity) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + } +} + +struct ThreeAngelsMessagesView: View { + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @State private var messages: [(title: String, reference: String, description: String)] = [ + ("First Angel's Message", "Revelation 14:6-7", "Fear God and give glory to Him, for the hour of His judgment has come"), + ("Second Angel's Message", "Revelation 14:8", "Babylon is fallen, is fallen, that great city"), + ("Third Angel's Message", "Revelation 14:9-12", "Keep the commandments of God and the faith of Jesus") + ] + @State private var isLoading = true + + private let angelMessages = [ + (title: "First Angel's Message", reference: "Revelation 14:6-7"), + (title: "Second Angel's Message", reference: "Revelation 14:8"), + (title: "Third Angel's Message", reference: "Revelation 14:9-12") + ] + + var body: some View { + ForEach(Array(messages.enumerated()), id: \.offset) { index, message in + NavigationLink { + AngelMessageDetailView( + number: index + 1, + title: message.title, + reference: message.reference + ) + } label: { + AngelMessageCard( + number: index + 1, + title: message.title, + reference: message.reference, + description: message.description + ) + } + .buttonStyle(PlainButtonStyle()) + } + .task { + await loadMessages() + } + } + + private func loadMessages() async { + var loadedMessages: [(String, String, String)] = [] + + for angel in angelMessages { + // Use church-core to fetch the actual Bible verse text (following BeliefsView pattern) + let versesJson = fetchBibleVerseJson(query: angel.reference) + + // Use Rust function for JSON parsing and description generation - NO business logic in Swift! + let description = generateVerseDescription(versesJson: versesJson) + + loadedMessages.append((angel.title, angel.reference, description)) + } + + await MainActor.run { + messages = loadedMessages + isLoading = false + } + } +} + +struct AngelMessageDetailView: View { + let number: Int + let title: String + let reference: String + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @State private var verseText: String = "" + @State private var isLoading: Bool = true + + var body: some View { + ScrollView { + VStack(spacing: 24) { + // Header with number circle + VStack(spacing: 16) { + Text("\(number)") + .font(.system(size: horizontalSizeClass == .regular ? 48 : 40, weight: .bold)) + .foregroundColor(.white) + .frame(width: horizontalSizeClass == .regular ? 80 : 70, height: horizontalSizeClass == .regular ? 80 : 70) + .background(Color(hex: getBrandColor()), in: Circle()) + + Text(title) + .font(.system(size: horizontalSizeClass == .regular ? 32 : 28, weight: .bold, design: .serif)) + .multilineTextAlignment(.center) + + Text(reference) + .font(.system(size: horizontalSizeClass == .regular ? 20 : 18, weight: .medium)) + .foregroundColor(Color(hex: getBrandColor())) + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(Color(hex: getBrandColor()).opacity(0.1), in: Capsule()) + } + + // Verse content + VStack(alignment: .leading, spacing: 16) { + if isLoading { + ProgressView("Loading verse...") + } else if !verseText.isEmpty { + Text(verseText) + .font(.system(size: horizontalSizeClass == .regular ? 20 : 18, weight: .regular)) + .lineSpacing(6) + .padding(24) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + } else { + Text("Unable to load verse text") + .font(.system(size: horizontalSizeClass == .regular ? 18 : 16)) + .foregroundColor(.secondary) + .padding(24) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + } + } + } + .padding(.horizontal, 20) + .padding(.bottom, 100) + } + .navigationTitle("Angel's Message") + .navigationBarTitleDisplayMode(.inline) + .task { + await loadVerse() + } + } + + private func loadVerse() async { + let versesJson = fetchBibleVerseJson(query: reference) + + await MainActor.run { + isLoading = false + + // Use Rust function for JSON parsing and text extraction - NO business logic in Swift! + verseText = extractFullVerseText(versesJson: versesJson) + } + } +} + diff --git a/Views/ContactFormView.swift b/Views/ContactFormView.swift index 0d6d2f2..81a7696 100644 --- a/Views/ContactFormView.swift +++ b/Views/ContactFormView.swift @@ -1,208 +1,717 @@ import SwiftUI -struct ContactFormView: View { - @Environment(\.dismiss) private var dismiss - @StateObject private var viewModel = ContactFormViewModel() - @FocusState private var focusedField: Field? - var isModal: Bool = false +// MARK: - Rust Integration Data Models + +struct ContactFormData: Codable { + let name: String + let email: String + let phone: String + let message: String + let subject: String +} + +struct ValidationResult: Codable { + let isValid: Bool + let errors: [String] - enum Field { - case firstName, lastName, email, phone, message + enum CodingKeys: String, CodingKey { + case isValid = "is_valid" + case errors + } +} + +struct ChurchConfig: Codable { + let contactPhone: String + let contactEmail: String + let churchAddress: String + let churchName: String + + enum CodingKeys: String, CodingKey { + case contactPhone = "contact_phone" + case contactEmail = "contact_email" + case churchAddress = "church_address" + case churchName = "church_name" + } +} + +struct ContactFormView: View { + let isModal: Bool + + @State private var name = "" + @State private var email = "" + @State private var phone = "" + @State private var subject = "General Inquiry" + @State private var message = "" + @State private var isSubmitting = false + @State private var showingSuccess = false + @State private var showingError = false + @State private var showingAlert = false + @State private var errorMessage = "" + @State private var hasAttemptedSubmit = false + @State private var churchConfig: ChurchConfig? + @FocusState private var focusedField: Bool + + private let subjectOptions = [ + "General Inquiry", + "Prayer Request", + "Bible Study Interest", + "Membership Information", + "Pastoral Care", + "Adventist Youth", + "Other" + ] + + @Environment(\.dismiss) private var dismiss + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @Environment(ChurchDataService.self) private var dataService + + init(isModal: Bool = false) { + self.isModal = isModal } + private var alertMessage: String { + showingSuccess ? "Thank you for reaching out! We'll get back to you as soon as possible." : errorMessage + } + + private var headerSection: some View { + VStack(alignment: .leading, spacing: 8) { + Text("We'd love to hear from you! Send us a message and we'll get back to you as soon as possible.") + .font(.subheadline) + .foregroundStyle(.secondary) + .lineSpacing(4) + } + } + + + private var loadingOverlay: some View { + Color.black.opacity(0.3) + .ignoresSafeArea() + .overlay { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(1.5) + .tint(.orange) + } + } + var body: some View { NavigationStack { Form { Section { - Text("Use this form to get in touch with us for any reason - whether you have questions, need prayer, want to request Bible studies, learn more about our church, or would like to connect with our pastoral team.") - .foregroundColor(.secondary) + headerSection } - Section { - TextField("First Name (Required)", text: $viewModel.firstName) - .focused($focusedField, equals: .firstName) - .textContentType(.givenName) - - TextField("Last Name (Required)", text: $viewModel.lastName) - .focused($focusedField, equals: .lastName) - .textContentType(.familyName) - - TextField("Email (Required)", text: $viewModel.email) - .focused($focusedField, equals: .email) - .keyboardType(.emailAddress) - .textContentType(.emailAddress) - .autocapitalization(.none) - if !viewModel.email.isEmpty && !viewModel.isValidEmail(viewModel.email) { - Text("Please enter a valid email address") - .foregroundColor(.red) + Section("Your Information") { + VStack(alignment: .leading, spacing: 4) { + CustomTextField(title: "Name", text: $name, placeholder: "Enter your full name") + .focused($focusedField) + + if let nameError = getFieldError(for: "name") { + HStack { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.red) + .font(.caption) + Text(nameError) + .font(.caption) + .foregroundColor(.red) + } + } } - TextField("Phone", text: $viewModel.phone) - .focused($focusedField, equals: .phone) - .keyboardType(.phonePad) - .textContentType(.telephoneNumber) - .onChange(of: viewModel.phone) { oldValue, newValue in - viewModel.phone = viewModel.formatPhoneNumber(newValue) + VStack(alignment: .leading, spacing: 4) { + CustomTextField(title: "Email", text: $email, placeholder: "Enter your email address") + .keyboardType(.emailAddress) + .autocapitalization(.none) + .focused($focusedField) + + if let emailError = getFieldError(for: "email") { + HStack { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.red) + .font(.caption) + Text(emailError) + .font(.caption) + .foregroundColor(.red) + } + } + } + + VStack(alignment: .leading, spacing: 4) { + PhoneTextField( + title: "Phone (Optional)", + text: $phone, + placeholder: "(555) 123-4567", + icon: "phone.fill" + ) + .focused($focusedField) + + if let phoneError = getFieldError(for: "phone") { + HStack { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.red) + .font(.caption) + Text(phoneError) + .font(.caption) + .foregroundColor(.red) + } } - if !viewModel.phone.isEmpty && !viewModel.isValidPhone(viewModel.phone) { - Text("Please enter a valid phone number") - .foregroundColor(.red) } } - Section(header: Text("Message (Required)")) { - TextEditor(text: $viewModel.message) - .focused($focusedField, equals: .message) - .frame(minHeight: 100) - } - - Section { - Button(action: { - Task { - focusedField = nil // Dismiss keyboard - await viewModel.submit() + Section("Subject") { + Menu { + ForEach(subjectOptions, id: \.self) { option in + Button(option) { + subject = option + } } - }) { + } label: { HStack { + Text(subject) + .foregroundColor(.primary) Spacer() - Text("Submit") - Spacer() + Image(systemName: "chevron.down") + .foregroundColor(.secondary) + .font(.caption) } } - .disabled(!viewModel.isValid || viewModel.isSubmitting) + } + + Section("Message") { + VStack(alignment: .leading, spacing: 4) { + TextEditor(text: $message) + .frame(minHeight: 120) + .focused($focusedField) + + if let messageError = getFieldError(for: "message") { + HStack { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.red) + .font(.caption) + Text(messageError) + .font(.caption) + .foregroundColor(.red) + } + } + } + } + + Section { + customSubmitButton + + if hasAttemptedSubmit && !validationResult.isValid { + VStack(alignment: .leading, spacing: 8) { + ForEach(validationResult.errors, id: \.self) { error in + HStack { + Image(systemName: "exclamationmark.triangle.fill") + .foregroundColor(.red) + Text(error) + .font(.caption) + .foregroundColor(.red) + Spacer() + } + } + } + .padding() + .background(.red.opacity(0.1), in: RoundedRectangle(cornerRadius: 8)) + } } } - .navigationTitle("Contact Us") - .navigationBarTitleDisplayMode(.inline) + .navigationTitle(isModal ? "Contact Us" : "Get in Touch") + .navigationBarTitleDisplayMode(isModal ? .inline : .large) .toolbar { if isModal { - ToolbarItem(placement: .navigationBarTrailing) { - Button("Done") { - focusedField = nil + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { dismiss() } } } } - .alert("Error", isPresented: .constant(viewModel.error != nil)) { - Button("OK") { - viewModel.error = nil - } - } message: { - Text(viewModel.error ?? "") + } + .disabled(isSubmitting) + .overlay { + if isSubmitting { + loadingOverlay } - .alert("Success", isPresented: $viewModel.isSubmitted) { - Button("OK") { - viewModel.reset() + } + .alert("Success", isPresented: $showingSuccess) { + Button("OK") { + if isModal { + dismiss() + } else { + clearForm() } - } message: { - Text("Thank you for your message! We'll get back to you soon.") + } + } message: { + Text("Thank you for reaching out! We'll get back to you as soon as possible.") + } + .alert("Error", isPresented: $showingError) { + Button("OK") { } + } message: { + Text(errorMessage) + } + .onAppear { + loadChurchConfig() + } + .onDisappear { + focusedField = false + } + } + + + private var customSubmitButton: some View { + Button { + submitForm() + } label: { + HStack { + if isSubmitting { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + .scaleEffect(0.8) + .tint(.white) + Text("Sending...") + } else { + Image(systemName: "paperplane.fill") + Text("Send Message") + } + } + .font(.headline) + .fontWeight(.semibold) + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .foregroundStyle(.white) + .background( + (isFormValid && !isSubmitting) ? Color(hex: getBrandColor()) : Color(.systemGray3), + in: RoundedRectangle(cornerRadius: 12) + ) + .shadow(color: isFormValid && !isSubmitting ? Color(hex: getBrandColor()).opacity(0.3) : Color.clear, radius: 4, y: 2) + } + .disabled(!isFormValid || isSubmitting) + } + + private var quickContactOptions: some View { + VStack(spacing: 16) { + Divider() + .padding(.vertical, 12) + + Text("Other Ways to Reach Us") + .font(.subheadline) + .fontWeight(.medium) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 16) + + VStack(spacing: 12) { + // Only show Call Us on iPhone - iPads can't make phone calls + if UIDevice.current.userInterfaceIdiom == .phone { + QuickContactRow( + icon: "phone.fill", + title: "Call Us", + subtitle: churchConfig?.contactPhone ?? "(860) 875-0450", + color: .green + ) { + let phoneNumber = churchConfig?.contactPhone ?? "(860) 875-0450" + let digitsOnly = phoneNumber.filter { $0.isNumber } + let phoneURL = URL(string: "tel://\(digitsOnly)") + + // iPhone - make the call + if let url = phoneURL, UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } + } + } + + QuickContactRow( + icon: "envelope.fill", + title: "Email Us", + subtitle: getContactEmail(), + color: .blue + ) { + if let url = URL(string: "mailto:\(getContactEmail())") { + UIApplication.shared.open(url) + } + } + + QuickContactRow( + icon: "location.fill", + title: "Visit Us", + subtitle: "9 Hartford Turnpike, Tolland, CT", + color: .red + ) { + if let url = URL(string: "https://maps.apple.com/?address=9+Hartford+Turnpike,+Tolland,+CT+06084") { + UIApplication.shared.open(url) + } + } + } + .padding(.horizontal, 16) + } + .listRowInsets(EdgeInsets()) + } + + private var isFormValid: Bool { + return validationResult.isValid + } + + private var validationResult: ValidationResult { + let formData = ContactFormData( + name: name.trimmingCharacters(in: .whitespacesAndNewlines), + email: email.trimmingCharacters(in: .whitespacesAndNewlines), + phone: phone.trimmingCharacters(in: .whitespacesAndNewlines), + message: message.trimmingCharacters(in: .whitespacesAndNewlines), + subject: subject.trimmingCharacters(in: .whitespacesAndNewlines) + ) + + guard let jsonData = try? JSONEncoder().encode(formData), + let jsonString = String(data: jsonData, encoding: .utf8) else { + return ValidationResult(isValid: false, errors: ["System error"]) + } + + let resultJson = validateContactFormJson(formJson: jsonString) + + guard let data = resultJson.data(using: .utf8), + let result = try? JSONDecoder().decode(ValidationResult.self, from: data) else { + return ValidationResult(isValid: false, errors: ["System error"]) + } + + return result + } + + private var validationErrors: [String] { + return validationResult.errors + } + + private func getFieldError(for field: String) -> String? { + // Only show errors after user has attempted to submit or field has content + guard hasAttemptedSubmit || hasFieldContent(field) else { return nil } + + let errors = validationResult.errors + return errors.first { error in + switch field { + case "name": + return error.lowercased().contains("name") + case "email": + return error.lowercased().contains("email") + case "phone": + return error.lowercased().contains("phone") + case "message": + return error.lowercased().contains("message") + default: + return false + } + } + } + + private func hasFieldContent(_ field: String) -> Bool { + switch field { + case "name": + return !name.isEmpty + case "email": + return !email.isEmpty + case "phone": + return !phone.isEmpty + case "message": + return !message.isEmpty + default: + return false + } + } + + private func submitForm() { + hasAttemptedSubmit = true + guard isFormValid else { return } + + isSubmitting = true + + Task { + let success = await dataService.submitContact( + name: name.trimmingCharacters(in: .whitespacesAndNewlines), + email: email.trimmingCharacters(in: .whitespacesAndNewlines), + subject: subject.trimmingCharacters(in: .whitespacesAndNewlines), + message: message.trimmingCharacters(in: .whitespacesAndNewlines), + phone: phone.trimmingCharacters(in: .whitespacesAndNewlines) + ) + + await MainActor.run { + isSubmitting = false + + if success { + showingSuccess = true + } else { + errorMessage = "Failed to send message. Please try again or contact us directly." + showingError = true + } + } + } + } + + private func clearForm() { + name = "" + email = "" + phone = "" + subject = "General Inquiry" + message = "" + hasAttemptedSubmit = false + } + + private func loadChurchConfig() { + Task { + let configJson = fetchConfigJson() + + // Parse the API response structure + guard let data = configJson.data(using: .utf8), + let response = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let configData = response["data"] as? [String: Any], + let configDataJson = try? JSONSerialization.data(withJSONObject: configData), + let config = try? JSONDecoder().decode(ChurchConfig.self, from: configDataJson) else { + return + } + + await MainActor.run { + self.churchConfig = config } } } } -class ContactFormViewModel: ObservableObject { - @Published var firstName = "" - @Published var lastName = "" - @Published var email = "" - @Published var phone = "" - @Published var message = "" - @Published var error: String? - @Published var isSubmitting = false - @Published var isSubmitted = false +// MARK: - Phone Text Field with Formatting + +// MARK: - Custom Text Field +struct CustomTextField: View { + let title: String + @Binding var text: String + let placeholder: String - var isValid: Bool { - !firstName.isEmpty && - !lastName.isEmpty && - !email.isEmpty && - isValidEmail(email) && - !message.isEmpty && - (phone.isEmpty || isValidPhone(phone)) - } - - func reset() { - // Reset all fields - firstName = "" - lastName = "" - email = "" - phone = "" - message = "" - - // Reset state - error = nil - isSubmitting = false - isSubmitted = false - } - - func formatPhoneNumber(_ value: String) -> String { - // Remove all non-digits - let digits = value.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression) - - // Format the number - if digits.isEmpty { - return "" - } else if digits.count < 10 { - return digits - } else { - let areaCode = digits.prefix(3) - let middle = digits.dropFirst(3).prefix(3) - let last = digits.dropFirst(6).prefix(4) - return "(\(areaCode)) \(middle)-\(last)" + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Text(title) + .font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.secondary) + + TextField(placeholder, text: $text) + .textFieldStyle(.plain) + .padding() + .background(.background, in: RoundedRectangle(cornerRadius: 8)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(.quaternary, lineWidth: 1) + ) } } +} + +struct PhoneTextField: View { + let title: String + @Binding var text: String + let placeholder: String + let icon: String + var errorMessage: String? = nil - func isValidEmail(_ email: String) -> Bool { - let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" - let emailPredicate = NSPredicate(format:"SELF MATCHES %@", emailRegex) - return emailPredicate.evaluate(with: email) + var body: some View { + VStack(alignment: .leading, spacing: 12) { + HStack { + Image(systemName: icon) + .foregroundStyle(Color(hex: getBrandColor())) + .frame(width: 20) + + Text(title) + .font(.subheadline) + .fontWeight(.medium) + } + .padding(.top, 16) + .padding(.leading, 16) + + TextField(placeholder, text: $text) + .keyboardType(.phonePad) + .textContentType(.telephoneNumber) + .padding(16) + .background(Color(.systemGray6), in: RoundedRectangle(cornerRadius: 8)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(errorMessage != nil ? Color.red : Color(.systemGray4), lineWidth: errorMessage != nil ? 1.0 : 0.5) + ) + .onChange(of: text) { _, newValue in + text = formatPhoneNumber(newValue) + } + + if let errorMessage = errorMessage { + HStack { + Image(systemName: "exclamationmark.circle.fill") + .foregroundColor(.red) + .font(.caption) + Text(errorMessage) + .font(.caption) + .foregroundColor(.red) + } + .padding(.leading, 20) + } + } + .listRowInsets(EdgeInsets()) } - func isValidPhone(_ phone: String) -> Bool { - let phoneRegex = "^\\([0-9]{3}\\) [0-9]{3}-[0-9]{4}$" - let phonePredicate = NSPredicate(format:"SELF MATCHES %@", phoneRegex) - return phonePredicate.evaluate(with: phone) + private func formatPhoneNumber(_ input: String) -> String { + // Remove all non-numeric characters + let digitsOnly = input.filter { $0.isNumber } + + // Don't format if too long + if digitsOnly.count > 10 { + return String(digitsOnly.prefix(10)) + } + + // Format based on length + switch digitsOnly.count { + case 0...3: + return digitsOnly + case 4...6: + let area = String(digitsOnly.prefix(3)) + let next = String(digitsOnly.dropFirst(3)) + return "(\(area)) \(next)" + case 7...10: + let area = String(digitsOnly.prefix(3)) + let middle = String(digitsOnly.dropFirst(3).prefix(3)) + let last = String(digitsOnly.dropFirst(6)) + return "(\(area)) \(middle)-\(last)" + default: + return digitsOnly + } } +} + + +// MARK: - Custom Subject Picker + +struct CustomSubjectPicker: View { + let title: String + @Binding var selection: String + let options: [String] + let icon: String - @MainActor - func submit() async { - guard isValid else { return } - - isSubmitting = true - error = nil - - do { - let url = URL(string: "https://contact.rockvilletollandsda.church/api/contact")! - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - let payload = [ - "first_name": firstName, - "last_name": lastName, - "email": email, - "phone": phone, - "message": message - ] - - request.httpBody = try JSONSerialization.data(withJSONObject: payload) - - let (_, response) = try await URLSession.shared.data(for: request) - - guard let httpResponse = response as? HTTPURLResponse, - (200...299).contains(httpResponse.statusCode) else { - throw URLError(.badServerResponse) + var body: some View { + VStack(alignment: .leading, spacing: 8) { + HStack { + Image(systemName: icon) + .foregroundStyle(Color(hex: getBrandColor())) + .frame(width: 20) + + Text(title) + .font(.subheadline) + .fontWeight(.medium) } - isSubmitted = true - reset() - - } catch { - self.error = "There was an error submitting your message. Please try again." - print("Error submitting form:", error) - reset() + Menu { + ForEach(options, id: \.self) { option in + Button(option) { + selection = option + } + } + } label: { + HStack { + Text(selection) + .foregroundStyle(.primary) + + Spacer() + + Image(systemName: "chevron.up.chevron.down") + .font(.caption2) + .foregroundStyle(.secondary) + } + .padding(12) + .background(Color(.systemGray6), in: RoundedRectangle(cornerRadius: 8)) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color(.systemGray4), lineWidth: 0.5) + ) + } } - - isSubmitting = false } -} \ No newline at end of file +} + +// MARK: - Quick Contact Row + +struct QuickContactRow: View { + let icon: String + let title: String + let subtitle: String + let color: Color + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack(spacing: 12) { + Image(systemName: icon) + .font(.title3) + .foregroundStyle(color) + .frame(width: 24) + + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.subheadline) + .fontWeight(.medium) + .foregroundStyle(.primary) + + Text(subtitle) + .font(.caption) + .foregroundStyle(.secondary) + } + + Spacer() + + Image(systemName: "arrow.up.right.square") + .font(.caption) + .foregroundStyle(.secondary) + } + .padding(.vertical, 12) + .padding(.horizontal, 16) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8)) + } +} + +// MARK: - Service Times Section + +struct ServiceTimesSection: View { + var body: some View { + VStack(alignment: .leading, spacing: 16) { + Text("Service Times") + .font(.headline) + .fontWeight(.semibold) + + VStack(spacing: 12) { + ServiceTimeRow( + day: "Saturday", + time: "9:15 AM", + service: "Sabbath School" + ) + + ServiceTimeRow( + day: "Saturday", + time: "11:00 AM", + service: "Worship Service" + ) + + ServiceTimeRow( + day: "Wednesday", + time: "6:30 PM", + service: "Prayer Meeting" + ) + } + } + } +} + + +#Preview("Contact Form") { + NavigationStack { + ContactFormView() + .environment(ChurchDataService.shared) + } +} + +#Preview("Modal Contact Form") { + NavigationStack { + ContactFormView(isModal: true) + .environment(ChurchDataService.shared) + } +} \ No newline at end of file diff --git a/Views/ContentView.swift b/Views/ContentView.swift deleted file mode 100644 index 772c6b8..0000000 --- a/Views/ContentView.swift +++ /dev/null @@ -1,583 +0,0 @@ -import SwiftUI -import SafariServices -import AVKit - -struct ContentView: View { - @State private var selectedTab = 0 - - var body: some View { - TabView(selection: $selectedTab) { - HomeView() - .tabItem { - Label("Home", systemImage: "house.fill") - } - .tag(0) - - BulletinView() - .tabItem { - Label("Bulletin", systemImage: "newspaper.fill") - } - .tag(1) - - NavigationStack { - EventsView() - } - .tabItem { - Label("Events", systemImage: "calendar") - } - .tag(2) - - NavigationStack { - MessagesView() - } - .tabItem { - Label("Messages", systemImage: "video.fill") - } - .tag(3) - - MoreView() - .tabItem { - Label("More", systemImage: "ellipsis") - } - .tag(4) - } - .navigationBarHidden(true) - } -} - -// MARK: - Constants -enum ChurchContact { - static let email = "info@rockvilletollandsda.org" - static var emailUrl: String { - "mailto:\(email)" - } - static let phone = "860-875-0450" - static var phoneUrl: String { - "tel://\(phone.replacingOccurrences(of: "-", with: ""))" - } - static let facebook = "https://www.facebook.com/rockvilletollandsdachurch/" -} - -struct HomeView: View { - @State private var scrollTarget: ScrollTarget? - @State private var showingSafariView = false - @State private var safariURL: URL? - @State private var showSheet = false - @State private var sheetContent: AnyView? - @State private var showSuccessAlert = false - @Environment(\.horizontalSizeClass) private var horizontalSizeClass - - enum ScrollTarget { - case serviceTimes - } - - var body: some View { - GeometryReader { geometry in - ScrollViewReader { proxy in - ScrollView { - VStack(spacing: 0) { - // Hero Image Section - VStack(spacing: 0) { - Image("church_hero") - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: geometry.size.width) - .frame(height: horizontalSizeClass == .compact ? 350 : geometry.size.height * 0.45) - .offset(y: horizontalSizeClass == .compact ? 30 : 0) - .clipped() - .overlay( - LinearGradient( - gradient: Gradient(colors: [.clear, .black.opacity(0.5)]), - startPoint: .top, - endPoint: .bottom - ) - ) - } - .edgesIgnoringSafeArea(.top) - - // Content Section - if horizontalSizeClass == .compact { - VStack(spacing: 16) { - quickLinksSection - aboutUsSection - } - .padding() - } else { - HStack(alignment: .top, spacing: 32) { - VStack(alignment: .leading, spacing: 24) { - quickLinksSection - .frame(maxWidth: geometry.size.width * 0.35) - - Image("church_logo") - .resizable() - .scaledToFit() - .frame(width: geometry.size.width * 0.25) - .padding(.top, 24) - } - - aboutUsSection - .padding(.top, 8) - } - .padding(32) - } - } - .frame(minHeight: geometry.size.height) - } - .onChange(of: scrollTarget) { _, target in - if let target { - withAnimation { - proxy.scrollTo(target, anchor: .top) - } - scrollTarget = nil - } - } - } - } - .navigationTitle("") - .toolbar { - ToolbarItem(placement: .principal) { - Image("church_logo") - .resizable() - .scaledToFit() - .frame(height: 40) - } - } - .sheet(isPresented: $showingSafariView) { - if let url = safariURL { - SafariView(url: url) - .ignoresSafeArea() - } - } - .sheet(isPresented: $showSheet) { - if let content = sheetContent { - content - } - } - .alert("Success", isPresented: $showSuccessAlert) { - Button("OK", role: .cancel) { } - } message: { - Text("Thank you for your message! We'll get back to you soon.") - } - } - - private var quickLinksSection: some View { - VStack(alignment: .leading, spacing: horizontalSizeClass == .compact ? 16 : 8) { - Text("Quick Links") - .font(.custom("Montserrat-Bold", size: horizontalSizeClass == .compact ? 24 : 20)) - - quickLinksGrid - } - } - - private var quickLinksGrid: some View { - LazyVGrid( - columns: [ - GridItem(.flexible(), spacing: horizontalSizeClass == .compact ? 16 : 8), - GridItem(.flexible(), spacing: horizontalSizeClass == .compact ? 16 : 8) - ], - spacing: horizontalSizeClass == .compact ? 16 : 8 - ) { - QuickLinkButton(title: "Contact Us", icon: "envelope.fill") { - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first, - let rootViewController = window.rootViewController { - let contactFormView = ContactFormView(isModal: true) - let hostingController = UIHostingController(rootView: NavigationStack { contactFormView }) - rootViewController.present(hostingController, animated: true) - } - } - - QuickLinkButton(title: "Directions", icon: "location.fill") { - if let url = URL(string: "https://maps.apple.com/?address=9+Hartford+Turnpike,+Tolland,+CT+06084") { - UIApplication.shared.open(url) - } - } - - QuickLinkButton(title: "Call Us", icon: "phone.fill") { - if let url = URL(string: ChurchContact.phoneUrl) { - UIApplication.shared.open(url) - } - } - - QuickLinkButton(title: "Give Online", icon: "heart.fill") { - if let url = URL(string: "https://adventistgiving.org/donate/AN4MJG") { - UIApplication.shared.open(url, options: [:], completionHandler: nil) - } - } - } - } - - private var aboutUsSection: some View { - VStack(alignment: .leading, spacing: horizontalSizeClass == .compact ? 16 : 8) { - Text("About Us") - .font(.custom("Montserrat-Bold", size: horizontalSizeClass == .compact ? 24 : 20)) - - aboutUsContent - } - } - - private var aboutUsContent: some View { - VStack(alignment: .leading, spacing: horizontalSizeClass == .compact ? 16 : 8) { - Text("We are a vibrant, welcoming Seventh-day Adventist church community located in Tolland, Connecticut. Our mission is to share God's love through worship, fellowship, and service.") - .font(.body) - .foregroundColor(.secondary) - - Divider() - .padding(.vertical, 8) - - VStack(alignment: .leading, spacing: 16) { - Text("Service Times") - .font(.custom("Montserrat-Bold", size: 20)) - .id(ScrollTarget.serviceTimes) - - VStack(spacing: 12) { - ServiceTimeRow(day: "Saturday", time: "9:15 AM", name: "Sabbath School") - ServiceTimeRow(day: "Saturday", time: "11:00 AM", name: "Worship Service") - ServiceTimeRow(day: "Wednesday", time: "6:30 PM", name: "Prayer Meeting") - } - } - } - } -} - -struct QuickLinkButton: View { - let title: String - let icon: String - var color: Color = Color(hex: "fb8b23") - var action: () -> Void - @Environment(\.horizontalSizeClass) private var horizontalSizeClass - - var body: some View { - VStack { - Image(systemName: icon) - .font(.system(size: horizontalSizeClass == .compact ? 24 : 32)) - .foregroundColor(color) - Text(title) - .font(.custom("Montserrat-Medium", size: horizontalSizeClass == .compact ? 14 : 16)) - .foregroundColor(.primary) - .multilineTextAlignment(.center) - } - .frame(maxWidth: .infinity) - .padding(horizontalSizeClass == .compact ? 16 : 24) - .background(Color(UIColor.secondarySystemBackground)) - .cornerRadius(12) - .onTapGesture(perform: action) - } -} - -struct ServiceTimeRow: View { - let day: String - let time: String - let name: String - @Environment(\.horizontalSizeClass) private var horizontalSizeClass - - var body: some View { - HStack { - VStack(alignment: .leading) { - Text(day) - .font(.custom("Montserrat-Regular", size: horizontalSizeClass == .compact ? 14 : 16)) - .foregroundColor(.secondary) - Text(time) - .font(.custom("Montserrat-SemiBold", size: horizontalSizeClass == .compact ? 16 : 18)) - } - - Spacer() - - Text(name) - .font(.custom("Montserrat-Regular", size: horizontalSizeClass == .compact ? 16 : 18)) - .foregroundColor(.secondary) - } - .padding(.vertical, horizontalSizeClass == .compact ? 4 : 8) - } -} - -struct FilterChip: View { - let title: String - let isSelected: Bool - let action: () -> Void - - var body: some View { - Button(action: action) { - Text(title) - .font(.custom("Montserrat-Medium", size: 14)) - .padding(.horizontal, 16) - .padding(.vertical, 8) - .background( - RoundedRectangle(cornerRadius: 8) - .fill(isSelected ? Color.accentColor : Color.clear) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(isSelected ? Color.accentColor : Color.secondary.opacity(0.5), lineWidth: 1) - ) - ) - .foregroundColor(isSelected ? .white : .primary) - } - .buttonStyle(.plain) - } -} - -struct FilterSection: View { - let title: String - let items: [String] - let selectedItem: String? - let onSelect: (String?) -> Void - - var body: some View { - VStack(alignment: .leading, spacing: 12) { - HStack { - Text(title) - .font(.custom("Montserrat-SemiBold", size: 14)) - .foregroundColor(.secondary) - - Spacer() - - if selectedItem != nil { - Button("Clear") { - onSelect(nil) - } - .font(.custom("Montserrat-Regular", size: 12)) - .foregroundColor(.accentColor) - } - } - - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 8) { - FilterChip( - title: "All", - isSelected: selectedItem == nil, - action: { onSelect(nil) } - ) - - ForEach(items, id: \.self) { item in - FilterChip( - title: item, - isSelected: selectedItem == item, - action: { onSelect(item) } - ) - } - } - .padding(.bottom, 4) // Extra padding for shadow - } - } - } -} - -struct FilterPicker: View { - @Binding var selectedYear: String? - @Binding var selectedMonth: String? - @Binding var selectedMediaType: JellyfinService.MediaType - let availableYears: [String] - let availableMonths: [String] - - var body: some View { - VStack(spacing: 16) { - // Media Type Toggle - Picker("Media Type", selection: $selectedMediaType) { - Text("Sermons").tag(JellyfinService.MediaType.sermons) - Text("Live Archives").tag(JellyfinService.MediaType.livestreams) - } - .pickerStyle(.segmented) - - // Filters - VStack(spacing: 16) { - FilterSection( - title: "YEAR", - items: availableYears, - selectedItem: selectedYear, - onSelect: { year in - selectedYear = year - selectedMonth = nil - } - ) - - if selectedYear != nil { - FilterSection( - title: "MONTH", - items: availableMonths, - selectedItem: selectedMonth, - onSelect: { month in - selectedMonth = month - } - ) - } - } - - // Active Filters Summary - if selectedYear != nil || selectedMonth != nil { - HStack { - Text("Showing:") - .font(.custom("Montserrat-Regular", size: 12)) - .foregroundColor(.secondary) - - if let month = selectedMonth { - Text(month) - .font(.custom("Montserrat-Medium", size: 12)) - } - - if let year = selectedYear { - Text(year) - .font(.custom("Montserrat-Medium", size: 12)) - } - - Spacer() - - Button("Clear All") { - selectedYear = nil - selectedMonth = nil - } - .font(.custom("Montserrat-Medium", size: 12)) - .foregroundColor(.accentColor) - } - .padding(.top, -8) - } - } - } -} - -struct SafariView: UIViewControllerRepresentable { - let url: URL - @Environment(\.dismiss) private var dismiss - - func makeUIViewController(context: Context) -> SFSafariViewController { - let config = SFSafariViewController.Configuration() - config.entersReaderIfAvailable = false - let controller = SFSafariViewController(url: url, configuration: config) - controller.preferredControlTintColor = UIColor(named: "AccentColor") - controller.dismissButtonStyle = .done - return controller - } - - func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) { - } -} - -struct MoreView: View { - @State private var showingSafariView = false - @State private var safariURL: URL? - @State private var showSheet = false - @State private var sheetContent: AnyView? - @State private var showSuccessAlert = false - - var body: some View { - NavigationStack { - List { - Section("Resources") { - Button { - AppAvailabilityService.shared.openApp( - urlScheme: AppAvailabilityService.Schemes.bible, - fallbackURL: AppAvailabilityService.AppStoreURLs.bible - ) - } label: { - Label("Bible", systemImage: "book.fill") - } - - Button { - AppAvailabilityService.shared.openApp( - urlScheme: AppAvailabilityService.Schemes.sabbathSchool, - fallbackURL: AppAvailabilityService.AppStoreURLs.sabbathSchool - ) - } label: { - Label("Sabbath School", systemImage: "book.fill") - } - - Button { - AppAvailabilityService.shared.openApp( - urlScheme: AppAvailabilityService.Schemes.egw, - fallbackURL: AppAvailabilityService.AppStoreURLs.egwWritings - ) - } label: { - Label("EGW Writings", systemImage: "book.closed.fill") - } - - Button { - AppAvailabilityService.shared.openApp( - urlScheme: AppAvailabilityService.Schemes.hymnal, - fallbackURL: AppAvailabilityService.AppStoreURLs.hymnal - ) - } label: { - Label("Adventist Hymnal", systemImage: "music.note") - } - } - - Section("Connect") { - NavigationLink { - ContactFormView() - } label: { - Label("Contact Us", systemImage: "envelope.fill") - } - - Link(destination: URL(string: ChurchContact.phoneUrl)!) { - Label("Call Us", systemImage: "phone.fill") - } - - Link(destination: URL(string: ChurchContact.facebook)!) { - Label("Facebook", systemImage: "link") - } - - Link(destination: URL(string: "https://maps.apple.com/?address=9+Hartford+Turnpike,+Tolland,+CT+06084")!) { - Label("Directions", systemImage: "map.fill") - } - } - - Section("About") { - NavigationLink { - BeliefsView() - } label: { - Label("Our Beliefs", systemImage: "heart.text.square.fill") - } - } - - Section("App Info") { - HStack { - Label("Version", systemImage: "info.circle.fill") - Spacer() - Text(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0") - .foregroundColor(.secondary) - } - } - } - .navigationTitle("More") - .sheet(isPresented: $showingSafariView) { - if let url = safariURL { - SafariView(url: url) - .ignoresSafeArea() - } - } - .sheet(isPresented: $showSheet) { - if let content = sheetContent { - content - } - } - .alert("Success", isPresented: $showSuccessAlert) { - Button("OK", role: .cancel) { } - } message: { - Text("Thank you for your message! We'll get back to you soon.") - } - } - } -} - -extension UIApplication { - var scrollView: UIScrollView? { - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, - let window = windowScene.windows.first { - return window.rootViewController?.view.subviews.first { $0 is UIScrollView } as? UIScrollView - } - return nil - } -} - -enum NavigationDestination { - case prayerRequest -} - -extension UIScrollView { - func scrollToBottom() { - let bottomPoint = CGPoint(x: 0, y: contentSize.height - bounds.size.height) - setContentOffset(bottomPoint, animated: true) - } -} - -#Preview { - ContentView() -} diff --git a/Views/Detail/BulletinDetailView.swift b/Views/Detail/BulletinDetailView.swift new file mode 100644 index 0000000..86aebba --- /dev/null +++ b/Views/Detail/BulletinDetailView.swift @@ -0,0 +1,794 @@ +import SwiftUI +import PDFKit + +struct BulletinDetailView: View { + let bulletin: ChurchBulletin + @State private var showingPDFViewer = false + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + ScrollView { + VStack(spacing: 20) { + // Header Card + headerCard + + // Content Sections + if !bulletin.sabbathSchool.isEmpty || !bulletin.divineWorship.isEmpty { + contentSectionsCard + } + + // Scripture & Sunset Card + scriptureAndSunsetCard + + // Actions Card + actionsCard + } + .padding() + } + .navigationTitle("") + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + .toolbarBackground(.visible, for: .navigationBar) + .toolbarBackground(.regularMaterial, for: .navigationBar) + .sheet(isPresented: $showingPDFViewer) { + if let pdfUrl = bulletin.pdfPath, let url = URL(string: pdfUrl) { + PDFViewerSheet(url: url, title: bulletin.title) + } + } + } + + // MARK: - Header Card + private var headerCard: some View { + VStack(alignment: .leading, spacing: 16) { + // Top section with icon and metadata + HStack { + Image(systemName: "doc.text.fill") + .font(.system(size: 28)) + .foregroundStyle(Color(hex: "fb8b23")) + + VStack(alignment: .leading, spacing: 4) { + Text("Church Bulletin") + .font(.caption) + .fontWeight(.medium) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(.green.opacity(0.1), in: Capsule()) + .foregroundStyle(.green) + + Text(bulletin.formattedDate) + .font(.caption) + .foregroundStyle(.secondary) + } + + Spacer() + + if bulletin.pdfPath != nil { + Image(systemName: "arrow.down.circle.fill") + .font(.title2) + .foregroundStyle(.green) + } + } + + // Title + Text(bulletin.title) + .font(.title2) + .fontWeight(.bold) + .multilineTextAlignment(.leading) + + // Main PDF Action + if bulletin.pdfPath != nil { + Button(action: { showingPDFViewer = true }) { + HStack { + Image(systemName: "doc.text.viewfinder") + .font(.title3) + + Text("View PDF Bulletin") + .font(.headline) + .fontWeight(.semibold) + + Spacer() + + Image(systemName: "arrow.right.circle.fill") + .font(.title3) + } + .foregroundStyle(.white) + .padding() + .background(Color(hex: "fb8b23"), in: RoundedRectangle(cornerRadius: 12)) + } + .buttonStyle(.plain) + .shadow(color: Color(hex: "fb8b23").opacity(0.3), radius: 4, x: 0, y: 2) + } + } + .padding(20) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) + } + + // MARK: - Content Sections Card + private var contentSectionsCard: some View { + VStack(alignment: .leading, spacing: 20) { + // Header + HStack { + Image(systemName: "list.bullet.clipboard") + .font(.title3) + .foregroundStyle(Color(hex: "fb8b23")) + + Text("Service Order") + .font(.headline) + .fontWeight(.semibold) + + Spacer() + } + + VStack(spacing: 16) { + if !bulletin.sabbathSchool.isEmpty { + ServiceSectionView( + title: "Sabbath School", + content: bulletin.sabbathSchool, + icon: "book.fill", + color: .blue + ) + } + + if !bulletin.divineWorship.isEmpty { + ServiceSectionView( + title: "Divine Worship", + content: bulletin.divineWorship, + icon: "hands.sparkles.fill", + color: .purple + ) + } + } + } + .padding(20) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) + } + + // MARK: - Scripture & Sunset Card + private var scriptureAndSunsetCard: some View { + VStack(spacing: 16) { + if !bulletin.scriptureReading.isEmpty { + ScriptureReadingView(scripture: bulletin.scriptureReading) + } + + SunsetTimeView(sunset: bulletin.sunset) + } + .padding(20) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) + } + + // MARK: - Actions Card + private var actionsCard: some View { + VStack(spacing: 16) { + HStack { + Image(systemName: "square.and.arrow.up") + .font(.title3) + .foregroundStyle(.blue) + + Text("Quick Actions") + .font(.headline) + .fontWeight(.semibold) + + Spacer() + } + + HStack(spacing: 12) { + QuickActionButton( + title: "Share", + icon: "square.and.arrow.up", + color: .blue + ) { + shareBulletin() + } + + if bulletin.pdfPath != nil { + QuickActionButton( + title: "Download", + icon: "arrow.down.circle", + color: .green + ) { + downloadBulletin() + } + } + + QuickActionButton( + title: "Contact", + icon: "phone.fill", + color: .orange + ) { + if let url = URL(string: "tel://8608750450") { + UIApplication.shared.open(url) + } + } + } + } + .padding(20) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) + } + + // MARK: - Helper Functions + + private func shareBulletin() { + var items: [Any] = ["Check out this week's church bulletin: \(bulletin.title)"] + + if let pdfPath = bulletin.pdfPath, let url = URL(string: pdfPath) { + items.append(url) + } + + #if os(iOS) + let activityVC = UIActivityViewController(activityItems: items, applicationActivities: nil) + + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first { + window.rootViewController?.present(activityVC, animated: true) + } + #endif + } + + private func downloadBulletin() { + guard let pdfPath = bulletin.pdfPath, let url = URL(string: pdfPath) else { return } + + // For remote PDFs, open in Safari which handles downloads properly + if url.scheme == "http" || url.scheme == "https" { + UIApplication.shared.open(url) + } + } +} + +// MARK: - PDF Viewer Sheet + +struct PDFViewerSheet: View { + let url: URL + let title: String + @Environment(\.dismiss) private var dismiss + @State private var isLoading = true + @State private var error: Error? + + var body: some View { + NavigationStack { + ZStack { + if isLoading { + VStack(spacing: 16) { + ProgressView() + .scaleEffect(1.2) + + Text("Loading PDF...") + .font(.subheadline) + .foregroundStyle(.secondary) + } + } else if let error = error { + VStack(spacing: 16) { + Image(systemName: "exclamationmark.triangle") + .font(.system(size: 48)) + .foregroundStyle(.red) + + Text("Error Loading PDF") + .font(.headline) + .fontWeight(.semibold) + + Text(error.localizedDescription) + .font(.subheadline) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + + Button("Try Again") { + loadPDF() + } + .buttonStyle(.borderedProminent) + } + .padding() + } else { + PDFKitView(url: url, isLoading: $isLoading, error: $error) + } + } + .navigationTitle(title) + #if os(iOS) + .navigationBarTitleDisplayMode(.inline) + #endif + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button("Done") { + dismiss() + } + } + + ToolbarItem(placement: .navigationBarTrailing) { + Menu { + Button { + // Share PDF + } label: { + Label("Share", systemImage: "square.and.arrow.up") + } + + Button { + // Download PDF + } label: { + Label("Download", systemImage: "arrow.down.circle") + } + + Button { + // Print PDF + } label: { + Label("Print", systemImage: "printer") + } + } label: { + Image(systemName: "ellipsis.circle") + } + } + } + } + .onAppear { + loadPDF() + } + } + + private func loadPDF() { + isLoading = true + error = nil + + // Simulate loading or implement actual PDF loading logic + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + isLoading = false + } + } +} + +// MARK: - PDFKit View + +struct PDFKitView: UIViewRepresentable { + let url: URL + @Binding var isLoading: Bool + @Binding var error: Error? + + func makeUIView(context: Context) -> PDFView { + let pdfView = PDFView() + pdfView.autoScales = true + pdfView.displayMode = .singlePageContinuous + pdfView.displayDirection = .vertical + #if os(iOS) + pdfView.backgroundColor = .systemBackground + #else + pdfView.backgroundColor = .black + #endif + + // Load PDF + loadPDF(into: pdfView) + + return pdfView + } + + func updateUIView(_ uiView: PDFView, context: Context) { + // Update if needed + } + + private func loadPDF(into pdfView: PDFView) { + Task { + do { + let (data, _) = try await URLSession.shared.data(from: url) + + await MainActor.run { + if let pdfDocument = PDFDocument(data: data) { + pdfView.document = pdfDocument + self.isLoading = false + } else { + self.error = URLError(.cannotDecodeContentData) + self.isLoading = false + } + } + } catch { + await MainActor.run { + self.error = error + self.isLoading = false + } + } + } + } +} + +// MARK: - Supporting View Components + +struct ServiceSectionView: View { + let title: String + let content: String + let icon: String + let color: Color + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + HStack(spacing: 8) { + Image(systemName: icon) + .font(.subheadline) + .foregroundStyle(color) + + Text(title) + .font(.subheadline) + .fontWeight(.semibold) + .foregroundStyle(color) + + Spacer() + } + + HymnTextView(text: content) + .padding(.leading, 4) + } + .padding(16) + .background(color.opacity(0.05), in: RoundedRectangle(cornerRadius: 12)) + } +} + +struct ScriptureReadingView: View { + let scripture: String + + private func parseScripture(_ text: String) -> [ScriptureVerse] { + // Use the Rust implementation for consistent scripture parsing + let jsonString = formatScriptureTextJson(scriptureText: text) + guard let data = jsonString.data(using: .utf8), + let scriptureSection = try? JSONDecoder().decode([ScriptureSection].self, from: data) else { + // Fallback to original numbered verse parsing if Rust parsing fails + return parseNumberedVerses(text) + } + + var verses: [ScriptureVerse] = [] + + for section in scriptureSection { + // If we have both verse and reference, add them separately + if !section.reference.isEmpty { + verses.append(ScriptureVerse(number: nil, text: section.verse)) + verses.append(ScriptureVerse(number: nil, text: section.reference)) + } else { + // Single verse or text without reference + verses.append(ScriptureVerse(number: nil, text: section.verse)) + } + } + + // If still no verses found, fallback + if verses.isEmpty { + verses.append(ScriptureVerse(number: nil, text: text.trimmingCharacters(in: .whitespacesAndNewlines))) + } + + return verses + } + + private func parseNumberedVerses(_ text: String) -> [ScriptureVerse] { + // Fallback: Check for numbered verses like "1. text" or "1 text" + let lines = text.components(separatedBy: .newlines) + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + + var verses: [ScriptureVerse] = [] + + for line in lines { + // Check if line starts with a number + if let firstChar = line.first, firstChar.isNumber { + let components = line.components(separatedBy: CharacterSet(charactersIn: ". ")) + if components.count > 1, + let number = Int(components[0]), + components.count > 1 { + let text = components.dropFirst().joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines) + if !text.isEmpty { + verses.append(ScriptureVerse(number: number, text: text)) + continue + } + } + } + + // Add as regular text + verses.append(ScriptureVerse(number: nil, text: line)) + } + + // If no verses found, treat as single verse + if verses.isEmpty { + verses.append(ScriptureVerse(number: nil, text: text.trimmingCharacters(in: .whitespacesAndNewlines))) + } + + return verses + } + + var body: some View { + let verses = parseScripture(scripture) + + VStack(alignment: .leading, spacing: 16) { + // Header with enhanced styling + HStack(spacing: 8) { + ZStack { + Circle() + .fill(.indigo.opacity(0.15)) + .frame(width: 28, height: 28) + + Image(systemName: "book.closed.fill") + .font(.caption) + .foregroundStyle(.indigo) + } + + Text("Scripture Reading") + .font(.subheadline) + .fontWeight(.semibold) + .foregroundStyle(.indigo) + + Spacer() + + // Verse count indicator for multiple verses + if verses.count > 1 { + Text("\(verses.count) verses") + .font(.caption2) + .padding(.horizontal, 8) + .padding(.vertical, 3) + .background(.indigo.opacity(0.1), in: Capsule()) + .foregroundStyle(.indigo) + } + } + + // Verses with enhanced styling + VStack(spacing: verses.count > 1 ? 12 : 8) { + ForEach(Array(verses.enumerated()), id: \.offset) { index, verse in + VStack(alignment: .leading, spacing: 6) { + if let number = verse.number { + HStack(spacing: 8) { + // Verse number badge + Text("\(number)") + .font(.caption) + .fontWeight(.bold) + .foregroundStyle(.white) + .frame(width: 20, height: 20) + .background(.indigo, in: Circle()) + + // Verse text with elegant styling + Text(verse.text) + .font(.body) + .fontWeight(.medium) + .foregroundStyle(.primary) + .multilineTextAlignment(.leading) + } + } else { + // Single verse or paragraph without numbering + HStack(alignment: .top, spacing: 8) { + // Decorative quote mark or bullet + if verses.count == 1 { + Image(systemName: "quote.opening") + .font(.caption) + .foregroundStyle(.indigo.opacity(0.6)) + .padding(.top, 2) + } else { + Circle() + .fill(.indigo.opacity(0.4)) + .frame(width: 4, height: 4) + .padding(.top, 8) + } + + Text(verse.text) + .font(.body) + .fontWeight(.medium) + .foregroundStyle(.primary) + .multilineTextAlignment(.leading) + } + } + } + .padding(.leading, 4) + + // Separator between verses (except last one) + if index < verses.count - 1 { + HStack { + Spacer() + Rectangle() + .fill(.indigo.opacity(0.2)) + .frame(width: 30, height: 1) + Spacer() + } + } + } + } + } + .padding(20) + .background(.indigo.opacity(0.03), in: RoundedRectangle(cornerRadius: 16)) + .overlay( + RoundedRectangle(cornerRadius: 16) + .strokeBorder(.indigo.opacity(0.15), lineWidth: 1.5) + ) + .shadow(color: .indigo.opacity(0.1), radius: 4, x: 0, y: 2) + } +} + +// MARK: - Scripture Verse Model +struct ScriptureVerse { + let number: Int? + let text: String +} + +struct SunsetTimeView: View { + let sunset: String + + var body: some View { + HStack(spacing: 12) { + Image(systemName: "sunset.fill") + .font(.title3) + .foregroundStyle(.orange) + + VStack(alignment: .leading, spacing: 2) { + Text("Sabbath Ends") + .font(.caption) + .foregroundStyle(.secondary) + + Text(sunset) + .font(.headline) + .fontWeight(.semibold) + } + + Spacer() + + Image(systemName: "clock.fill") + .font(.title3) + .foregroundStyle(.orange.opacity(0.6)) + } + .padding(16) + .background(.orange.opacity(0.05), in: RoundedRectangle(cornerRadius: 12)) + } +} + +struct QuickActionButton: View { + let title: String + let icon: String + let color: Color + let action: () -> Void + + var body: some View { + Button(action: action) { + VStack(spacing: 8) { + Image(systemName: icon) + .font(.title2) + .foregroundStyle(color) + + Text(title) + .font(.caption) + .fontWeight(.medium) + .multilineTextAlignment(.center) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 16) + .background(color.opacity(0.1), in: RoundedRectangle(cornerRadius: 12)) + } + .buttonStyle(.plain) + } +} + +// MARK: - Hymn Text View with Clickable Hymn Numbers + +struct HymnTextView: View { + let text: String + + private func parseTextWithHymns(_ text: String) -> [(text: String, isHymn: Bool, hymnNumber: Int?)] { + var result: [(text: String, isHymn: Bool, hymnNumber: Int?)] = [] + let hymnPattern = #"(?:Hymn(?:al)?\s+(?:#\s*)?|#\s*)(\d+)(?:\s+["""]([^"""]*)[""])?.*"# + + guard let regex = try? NSRegularExpression(pattern: hymnPattern, options: [.caseInsensitive]) else { + return [(text: text, isHymn: false, hymnNumber: nil)] + } + + let nsText = text as NSString + let matches = regex.matches(in: text, range: NSRange(location: 0, length: nsText.length)) + + if matches.isEmpty { + return [(text: text, isHymn: false, hymnNumber: nil)] + } + + var lastIndex = 0 + + for match in matches { + // Add text before the hymn match + if match.range.location > lastIndex { + let textBefore = nsText.substring(with: NSRange(location: lastIndex, length: match.range.location - lastIndex)) + if !textBefore.isEmpty { + result.append((text: textBefore, isHymn: false, hymnNumber: nil)) + } + } + + // Add hymn match + let hymnText = nsText.substring(with: match.range) + let hymnNumber = Int(nsText.substring(with: match.range(at: 1))) ?? 0 + result.append((text: hymnText, isHymn: true, hymnNumber: hymnNumber)) + + lastIndex = match.range.location + match.range.length + } + + // Add remaining text after the last match + if lastIndex < nsText.length { + let remainingText = nsText.substring(from: lastIndex) + if !remainingText.isEmpty { + result.append((text: remainingText, isHymn: false, hymnNumber: nil)) + } + } + + return result + } + + var body: some View { + let parsedText = parseTextWithHymns(text) + + if parsedText.count == 1 && !parsedText[0].isHymn { + // No hymns found, just show regular text + Text(text) + .font(.body) + } else { + // Mix of text and hymn numbers + VStack(alignment: .leading, spacing: 8) { + ForEach(Array(parsedText.enumerated()), id: \.offset) { _, segment in + if segment.isHymn, let hymnNumber = segment.hymnNumber { + Button(action: { + openHymnInAdventistHymnarium(hymnNumber) + }) { + HStack(spacing: 6) { + Image(systemName: "music.note") + .foregroundStyle(.blue) + .font(.caption) + + Text(segment.text) + .font(.body) + .foregroundStyle(.blue) + } + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background( + RoundedRectangle(cornerRadius: 6) + .fill(.blue.opacity(0.1)) + ) + } + .buttonStyle(.plain) + } else { + Text(segment.text) + .font(.body) + } + } + } + } + } + + private func openHymnInAdventistHymnarium(_ number: Int) { + // Try to open Adventist Hymnarium app first + let hymnAppURL = URL(string: "adventisthymnarium://hymn?number=\(number)") + let appStoreURL = URL(string: "https://apps.apple.com/us/app/adventist-hymnarium/id6738877733") + + if let hymnAppURL = hymnAppURL, UIApplication.shared.canOpenURL(hymnAppURL) { + // Open in Adventist Hymnarium app + UIApplication.shared.open(hymnAppURL) + } else if let appStoreURL = appStoreURL { + // Offer to download the app + let alert = UIAlertController( + title: "Open in Adventist Hymnarium", + message: "Download the Adventist Hymnarium app to view Hymn #\(number)?", + preferredStyle: .alert + ) + + alert.addAction(UIAlertAction(title: "Download App", style: .default) { _ in + UIApplication.shared.open(appStoreURL) + }) + + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first, + let rootViewController = window.rootViewController { + rootViewController.present(alert, animated: true) + } + } + } +} + +#Preview { + NavigationStack { + BulletinDetailView(bulletin: ChurchBulletin( + id: "1", + title: "January 11, 2025", + date: "Saturday, January 11, 2025", + sabbathSchool: "The Beatitudes - Part 3", + divineWorship: "Blessed Are Those Who Hunger", + scriptureReading: "Matthew 5:1-12", + sunset: "5:47 PM", + pdfPath: "https://example.com/bulletin.pdf", + coverImage: nil, + isActive: true + )) + } +} diff --git a/Views/Detail/EventDetailShared.swift b/Views/Detail/EventDetailShared.swift new file mode 100644 index 0000000..18b0774 --- /dev/null +++ b/Views/Detail/EventDetailShared.swift @@ -0,0 +1,307 @@ +import SwiftUI +#if canImport(EventKit) +import EventKit +#endif + +// MARK: - Shared Components + +struct EventActionButtons: View { + let event: ChurchEvent + @Binding var showingDirections: Bool + @Binding var showingShareSheet: Bool + @Binding var showingCalendarAlert: Bool + @Binding var calendarMessage: String + let style: ActionButtonStyle + + enum ActionButtonStyle { + case iphone + case ipad + } + + var body: some View { + VStack(spacing: 12) { + if !event.location.isEmpty { + Button(action: { EventDetailActions.handleDirections(for: event, showingDirections: $showingDirections) }) { + HStack { + Image(systemName: EventDetailActions.isOnlineEvent(event) ? "link" : "location.fill") + Text(EventDetailActions.isOnlineEvent(event) ? "Join Event" : "Get Directions") + } + .applyButtonStyle(style) + .foregroundColor(.white) + .if(style == .ipad) { view in + view.background(Color.blue, in: Capsule()) + } + .if(style == .iphone) { view in + view.background(Color.blue, in: RoundedRectangle(cornerRadius: 10)) + } + } + } + + #if canImport(EventKit) + Button(action: { + EventDetailActions.addToCalendar( + event: event, + showingCalendarAlert: $showingCalendarAlert, + calendarMessage: $calendarMessage + ) + }) { + HStack { + Image(systemName: "calendar.badge.plus") + Text("Add to Calendar") + } + .applyButtonStyle(style) + .foregroundColor(.white) + .if(style == .ipad) { view in + view.background(Color.orange, in: Capsule()) + } + .if(style == .iphone) { view in + view.background(Color.orange, in: RoundedRectangle(cornerRadius: 10)) + } + } + #endif + + #if os(iOS) + Button(action: { + DispatchQueue.main.async { + showingShareSheet = true + } + }) { + HStack { + Image(systemName: "square.and.arrow.up") + Text("Share Event") + } + .applyButtonStyle(style) + .foregroundColor(.primary) + .if(style == .ipad) { view in + view.background(.thickMaterial, in: Capsule()) + } + .if(style == .iphone) { view in + view.background(Color.gray.opacity(0.2), in: RoundedRectangle(cornerRadius: 10)) + } + } + #endif + } + } +} + +// MARK: - Shared Actions + +struct EventDetailActions { + static func isOnlineEvent(_ event: ChurchEvent) -> Bool { + let onlineKeywords = ["zoom", "whatsapp", "online", "virtual", "webinar", "meeting", "call"] + return onlineKeywords.contains { keyword in + event.location.lowercased().contains(keyword) + } + } + + static func handleDirections(for event: ChurchEvent, showingDirections: Binding) { + if isOnlineEvent(event) { + if let locationUrl = event.locationUrl, let url = URL(string: locationUrl) { + UIApplication.shared.open(url) + } else { + showingDirections.wrappedValue = true + } + } else { + let encodedLocation = event.location.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" + if let url = URL(string: "http://maps.apple.com/?q=\(encodedLocation)") { + UIApplication.shared.open(url) + } + } + } + + #if canImport(EventKit) + static func addToCalendar(event: ChurchEvent, showingCalendarAlert: Binding, calendarMessage: Binding) { + let eventStore = EKEventStore() + + if #available(iOS 17.0, *) { + eventStore.requestWriteOnlyAccessToEvents { granted, error in + handleCalendarAccess(granted: granted, error: error, eventStore: eventStore, event: event, showingCalendarAlert: showingCalendarAlert, calendarMessage: calendarMessage) + } + } else { + eventStore.requestAccess(to: .event) { granted, error in + handleCalendarAccess(granted: granted, error: error, eventStore: eventStore, event: event, showingCalendarAlert: showingCalendarAlert, calendarMessage: calendarMessage) + } + } + } + + private static func handleCalendarAccess(granted: Bool, error: Error?, eventStore: EKEventStore, event: ChurchEvent, showingCalendarAlert: Binding, calendarMessage: Binding) { + DispatchQueue.main.async { + if granted && error == nil { + // Use Rust function to parse event data (RTSDA Architecture Rules compliance) + guard let eventJson = try? JSONEncoder().encode(event), + let jsonString = String(data: eventJson, encoding: .utf8) else { + calendarMessage.wrappedValue = "Failed to process event data." + showingCalendarAlert.wrappedValue = true + return + } + + let calendarDataJson = createCalendarEventData(eventJson: jsonString) + let parsedDataJson = parseCalendarEventData(calendarJson: calendarDataJson) + + guard let data = parsedDataJson.data(using: .utf8), + let calendarData = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { + calendarMessage.wrappedValue = "Failed to parse calendar response" + showingCalendarAlert.wrappedValue = true + return + } + + guard let success = calendarData["success"] as? Bool, success else { + let errorMsg = calendarData["error"] as? String ?? "Failed to parse event dates" + calendarMessage.wrappedValue = errorMsg + showingCalendarAlert.wrappedValue = true + return + } + + let calendarEvent = EKEvent(eventStore: eventStore) + calendarEvent.title = calendarData["title"] as? String ?? event.title + calendarEvent.notes = calendarData["description"] as? String ?? event.description + calendarEvent.location = calendarData["location"] as? String ?? event.location + + // Use parsed timestamps from Rust + if let startTimestamp = calendarData["start_timestamp"] as? TimeInterval, + let endTimestamp = calendarData["end_timestamp"] as? TimeInterval { + calendarEvent.startDate = Date(timeIntervalSince1970: startTimestamp) + calendarEvent.endDate = Date(timeIntervalSince1970: endTimestamp) + } else { + calendarMessage.wrappedValue = "Failed to parse event timestamps." + showingCalendarAlert.wrappedValue = true + return + } + + // Add recurrence rule if event is recurring + if let hasRecurrence = calendarData["has_recurrence"] as? Bool, hasRecurrence, + let recurringType = calendarData["recurring_type"] as? String { + if let recurrenceRule = createRecurrenceRule(from: recurringType) { + calendarEvent.recurrenceRules = [recurrenceRule] + } + } + + calendarEvent.calendar = eventStore.defaultCalendarForNewEvents + + do { + // Use .futureEvents for recurring events, .thisEvent for single events + let span: EKSpan = (calendarData["has_recurrence"] as? Bool == true) ? .futureEvents : .thisEvent + try eventStore.save(calendarEvent, span: span) + + let message = (calendarData["has_recurrence"] as? Bool == true) ? + "Recurring event series successfully added to your calendar!" : + "Event successfully added to your calendar!" + calendarMessage.wrappedValue = message + showingCalendarAlert.wrappedValue = true + } catch { + calendarMessage.wrappedValue = "Failed to add event to calendar. Please try again." + showingCalendarAlert.wrappedValue = true + } + } else { + calendarMessage.wrappedValue = "Calendar access is required to add events. Please enable it in Settings." + showingCalendarAlert.wrappedValue = true + } + } + } + + private static func createRecurrenceRule(from recurringType: String) -> EKRecurrenceRule? { + switch recurringType.uppercased() { + case "DAILY": + // Daily Prayer Meeting: Sunday-Friday (exclude Saturday) + let weekdays = [ + EKRecurrenceDayOfWeek(.sunday), + EKRecurrenceDayOfWeek(.monday), + EKRecurrenceDayOfWeek(.tuesday), + EKRecurrenceDayOfWeek(.wednesday), + EKRecurrenceDayOfWeek(.thursday), + EKRecurrenceDayOfWeek(.friday) + ] + return EKRecurrenceRule( + recurrenceWith: .weekly, + interval: 1, + daysOfTheWeek: weekdays, + daysOfTheMonth: nil, + monthsOfTheYear: nil, + weeksOfTheYear: nil, + daysOfTheYear: nil, + setPositions: nil, + end: EKRecurrenceEnd(end: Calendar.current.date(byAdding: .year, value: 1, to: Date()) ?? Date()) + ) + case "WEEKLY": + return EKRecurrenceRule( + recurrenceWith: .weekly, + interval: 1, + end: EKRecurrenceEnd(end: Calendar.current.date(byAdding: .year, value: 1, to: Date()) ?? Date()) + ) + case "BIWEEKLY": + return EKRecurrenceRule( + recurrenceWith: .weekly, + interval: 2, + end: EKRecurrenceEnd(end: Calendar.current.date(byAdding: .year, value: 1, to: Date()) ?? Date()) + ) + case "MONTHLY": + return EKRecurrenceRule( + recurrenceWith: .monthly, + interval: 1, + end: EKRecurrenceEnd(end: Calendar.current.date(byAdding: .year, value: 1, to: Date()) ?? Date()) + ) + case "FIRST_TUESDAY": + let dayOfWeek = EKRecurrenceDayOfWeek(.tuesday) + return EKRecurrenceRule( + recurrenceWith: .monthly, + interval: 1, + daysOfTheWeek: [dayOfWeek], + daysOfTheMonth: nil, + monthsOfTheYear: nil, + weeksOfTheYear: nil, + daysOfTheYear: nil, + setPositions: [1], + end: EKRecurrenceEnd(end: Calendar.current.date(byAdding: .year, value: 1, to: Date()) ?? Date()) + ) + default: + return nil + } + } + #endif + + static func createShareText(for event: ChurchEvent) -> String { + return """ + \(event.title) + + \(event.description) + + 📅 \(event.isMultiDay ? event.formattedDateTime : event.formattedDateRange) + 🕐 \(event.formattedTime) + 📍 \(event.location) + + Join us at Rockville Tolland SDA Church! + """ + } +} + +// MARK: - Helper Extensions + +extension View { + func applyButtonStyle(_ style: EventActionButtons.ActionButtonStyle) -> AnyView { + switch style { + case .iphone: + return AnyView( + self + .frame(maxWidth: .infinity) + .padding() + ) + case .ipad: + return AnyView( + self + .font(.headline) + .padding(.horizontal, 24) + .padding(.vertical, 12) + ) + } + } + + @ViewBuilder + func `if`(_ condition: Bool, transform: (Self) -> Content) -> some View { + if condition { + transform(self) + } else { + self + } + } +} + diff --git a/Views/Detail/EventDetailView+iPad.swift b/Views/Detail/EventDetailView+iPad.swift new file mode 100644 index 0000000..c62f367 --- /dev/null +++ b/Views/Detail/EventDetailView+iPad.swift @@ -0,0 +1,156 @@ +import SwiftUI + +struct iPadEventDetailView: View { + let event: ChurchEvent + @State private var showingDirections = false + @State private var showingShareSheet = false + @State private var showingCalendarAlert = false + @State private var calendarMessage = "" + @State private var shareText = "" + + var body: some View { + VStack(spacing: 0) { + // Hero Image Section - Full Width + if let imageUrl = event.image { + AsyncImage(url: URL(string: imageUrl)) { image in + image + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxHeight: 400) + } placeholder: { + Rectangle() + .fill(LinearGradient( + colors: [Color.blue.opacity(0.6), Color.purple.opacity(0.8)], + startPoint: .topLeading, + endPoint: .bottomTrailing + )) + .frame(height: 400) + } + } else { + Rectangle() + .fill(LinearGradient( + colors: [Color.blue.opacity(0.6), Color.purple.opacity(0.8)], + startPoint: .topLeading, + endPoint: .bottomTrailing + )) + .frame(height: 400) + } + + // Title Section - Below Image + HStack { + VStack(alignment: .leading, spacing: 16) { + // Category Badge + if !event.category.isEmpty { + HStack(spacing: 8) { + Image(systemName: "tag.fill") + Text(event.category.uppercased()) + .fontWeight(.bold) + } + .font(.subheadline) + .foregroundColor(.white) + .padding(.horizontal, 20) + .padding(.vertical, 10) + .background(Color(hex: "fb8b23"), in: Capsule()) + } + + // Title + Text(event.title) + .font(.system(size: 42, weight: .bold, design: .serif)) + .foregroundColor(.primary) + .lineLimit(3) + } + + Spacer() + + // Action Buttons + EventActionButtons( + event: event, + showingDirections: $showingDirections, + showingShareSheet: $showingShareSheet, + showingCalendarAlert: $showingCalendarAlert, + calendarMessage: $calendarMessage, + style: .ipad + ) + } + .padding(.horizontal, 40) + .padding(.vertical, 32) + .background(Color(.systemBackground)) + + // Simple Centered Content + VStack(spacing: 32) { + // Event Details Card - Compact and Centered + VStack(alignment: .leading, spacing: 24) { + HStack { + Image(systemName: "calendar.circle.fill") + .font(.title) + .foregroundColor(.blue) + + VStack(alignment: .leading, spacing: 2) { + Text(event.formattedDate) + .font(.title2) + .fontWeight(.bold) + Text(event.detailedTimeDisplay) + .font(.subheadline) + .foregroundColor(.secondary) + } + + Spacer() + } + + if !event.location.isEmpty { + HStack { + Image(systemName: "location.circle.fill") + .font(.title) + .foregroundColor(.red) + + Text(event.location) + .font(.title3) + .fontWeight(.semibold) + + Spacer() + } + } + + if !event.description.isEmpty { + Text(event.description) + .font(.body) + .lineSpacing(4) + .padding(.top, 8) + } + + // Registration/contact info removed since fields no longer exist in API + } + .padding(40) + .frame(maxWidth: 600) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 20)) + } + .frame(maxWidth: .infinity) + .padding(.horizontal, 60) + .padding(.bottom, 60) + } + .onAppear { + shareText = EventDetailActions.createShareText(for: event) + } + .sheet(isPresented: $showingDirections) { + Text("Directions to \(event.location)") + } + #if os(iOS) + .sheet(isPresented: $showingShareSheet) { + ShareSheet(activityItems: [shareText]) + } + #endif + .alert("Calendar", isPresented: $showingCalendarAlert) { + Button("OK") { } + } message: { + Text(calendarMessage) + } + } +} + + + + + +#Preview { + iPadEventDetailView(event: ChurchEvent.sampleEvent()) +} diff --git a/Views/Detail/EventDetailView+iPhone.swift b/Views/Detail/EventDetailView+iPhone.swift new file mode 100644 index 0000000..d31c741 --- /dev/null +++ b/Views/Detail/EventDetailView+iPhone.swift @@ -0,0 +1,131 @@ +import SwiftUI + +struct EventDetailView: View { + let event: ChurchEvent + @State private var showingDirections = false + @State private var showingShareSheet = false + @State private var showingCalendarAlert = false + @State private var calendarMessage = "" + @State private var shareText = "" + + var body: some View { + ScrollView { + LazyVStack(alignment: .leading, spacing: 20) { + // Event Image + if let imageUrl = event.image { + AsyncImage(url: URL(string: imageUrl)) { image in + image + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxHeight: 200) + } placeholder: { + Rectangle() + .fill(LinearGradient( + colors: [Color.blue.opacity(0.6), Color.purple.opacity(0.8)], + startPoint: .topLeading, + endPoint: .bottomTrailing + )) + .frame(height: 200) + } + .cornerRadius(12) + } + + VStack(alignment: .leading, spacing: 16) { + // Title and Category + VStack(alignment: .leading, spacing: 8) { + if !event.category.isEmpty { + Text(event.category.uppercased()) + .font(.caption) + .fontWeight(.bold) + .foregroundColor(.blue) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(Color.blue.opacity(0.1)) + .cornerRadius(8) + } + + Text(event.title) + .font(.largeTitle) + .fontWeight(.bold) + .lineLimit(nil) + + Text(event.description) + .font(.body) + .foregroundColor(.secondary) + .lineLimit(nil) + } + + Divider() + + // Event Details + VStack(alignment: .leading, spacing: 12) { + HStack { + Image(systemName: "calendar") + .foregroundColor(.blue) + .frame(width: 20) + Text(event.formattedDate) + .font(.body) + } + + HStack { + Image(systemName: "clock") + .foregroundColor(.blue) + .frame(width: 20) + Text(event.detailedTimeDisplay) + .font(.body) + } + + if !event.location.isEmpty { + HStack { + Image(systemName: "location.fill") + .foregroundColor(.blue) + .frame(width: 20) + Text(event.location) + .font(.body) + .lineLimit(nil) + } + } + } + + Divider() + + // Action Buttons + EventActionButtons( + event: event, + showingDirections: $showingDirections, + showingShareSheet: $showingShareSheet, + showingCalendarAlert: $showingCalendarAlert, + calendarMessage: $calendarMessage, + style: .iphone + ) + } + .padding(16) + .padding(.bottom, 100) + } + } + .ignoresSafeArea(.keyboard) + .navigationBarTitleDisplayMode(.inline) + .onAppear { + shareText = EventDetailActions.createShareText(for: event) + } + .sheet(isPresented: $showingDirections) { + Text("Directions to \(event.location)") + } + #if os(iOS) + .sheet(isPresented: $showingShareSheet) { + ShareSheet(activityItems: [shareText]) + } + #endif + .alert("Calendar", isPresented: $showingCalendarAlert) { + Button("OK") { } + } message: { + Text(calendarMessage) + } + } +} + +#Preview { + NavigationStack { + EventDetailView(event: ChurchEvent.sampleEvent()) + } +} \ No newline at end of file diff --git a/Views/Detail/EventDetailViewWrapper.swift b/Views/Detail/EventDetailViewWrapper.swift new file mode 100644 index 0000000..e55fc95 --- /dev/null +++ b/Views/Detail/EventDetailViewWrapper.swift @@ -0,0 +1,20 @@ +import SwiftUI + +/// Smart wrapper that automatically selects the appropriate EventDetailView based on device +struct EventDetailViewWrapper: View { + let event: ChurchEvent + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + if horizontalSizeClass == .regular { + // iPad: Use space-optimized layout + iPadEventDetailView(event: event) + } else { + // iPhone: Use single-column layout + EventDetailView(event: event) + } + } +} + +// MARK: - Convenience typealias for backward compatibility +typealias EventDetailView_Old = EventDetailViewWrapper \ No newline at end of file diff --git a/Views/EventCard.swift b/Views/EventCard.swift deleted file mode 100644 index 07cc91a..0000000 --- a/Views/EventCard.swift +++ /dev/null @@ -1,74 +0,0 @@ -import SwiftUI - -struct EventCard: View { - let event: Event - let action: () -> Void - - var body: some View { - Button(action: action) { - VStack(alignment: .center, spacing: 12) { - if let imageURL = event.imageURL { - CachedAsyncImage(url: imageURL) { image in - image - .resizable() - .aspectRatio(contentMode: .fill) - } placeholder: { - Color.gray.opacity(0.2) - } - .frame(height: 160) - .clipShape(RoundedRectangle(cornerRadius: 12)) - } - - VStack(alignment: .center, spacing: 8) { - Text(event.title) - .font(.headline) - .multilineTextAlignment(.center) - .fixedSize(horizontal: false, vertical: true) - - HStack(spacing: 8) { - Text(event.category.rawValue) - .font(.caption) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Color.blue.opacity(0.1)) - .clipShape(Capsule()) - - if event.reoccuring != .none { - Text(event.reoccuring.rawValue.replacingOccurrences(of: "_", with: " ").capitalized) - .font(.caption) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Color.purple.opacity(0.1)) - .clipShape(Capsule()) - } - } - .fixedSize(horizontal: true, vertical: false) - - HStack { - Image(systemName: "calendar") - Text(event.formattedDateTime) - .multilineTextAlignment(.center) - .fixedSize(horizontal: false, vertical: true) - } - .font(.subheadline) - .foregroundStyle(.secondary) - } - .frame(maxWidth: .infinity) - } - .padding() - .background(Color(.systemBackground)) - .clipShape(RoundedRectangle(cornerRadius: 16)) - .contentShape(RoundedRectangle(cornerRadius: 16)) - .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 2) - } - .buttonStyle(PlainButtonStyle()) - } -} - -struct EventCardButtonStyle: ButtonStyle { - func makeBody(configuration: Configuration) -> some View { - configuration.label - .scaleEffect(configuration.isPressed ? 0.98 : 1.0) - .animation(.easeInOut(duration: 0.2), value: configuration.isPressed) - } -} \ No newline at end of file diff --git a/Views/EventDetailView.swift b/Views/EventDetailView.swift deleted file mode 100644 index 4d146ea..0000000 --- a/Views/EventDetailView.swift +++ /dev/null @@ -1,149 +0,0 @@ -import SwiftUI - -struct EventDetailView: View { - let event: Event - @Environment(\.dismiss) var dismiss - @State private var showingAlert = false - @State private var alertTitle = "" - @State private var alertMessage = "" - - var body: some View { - ScrollView { - VStack(alignment: .center, spacing: 20) { - // Header with dismiss button - HStack { - Button("Done") { - dismiss() - } - .padding() - - Spacer() - } - - // Image if available - if let imageURL = event.imageURL { - AsyncImage(url: imageURL) { image in - image - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxHeight: 200) - } placeholder: { - Color.gray.opacity(0.2) - .frame(height: 200) - } - } - - VStack(alignment: .center, spacing: 16) { - // Title and tags - Text(event.title) - .font(.title2.bold()) - .multilineTextAlignment(.center) - .fixedSize(horizontal: false, vertical: true) - - HStack(spacing: 8) { - Text(event.category.rawValue) - .font(.caption) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Color.blue.opacity(0.1)) - .clipShape(Capsule()) - - if event.reoccuring != .none { - Text(event.reoccuring.rawValue.replacingOccurrences(of: "_", with: " ").capitalized) - .font(.caption) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Color.purple.opacity(0.1)) - .clipShape(Capsule()) - } - } - - // Date and location - VStack(spacing: 12) { - HStack { - Image(systemName: "calendar") - Text(event.formattedDateTime) - } - .font(.subheadline) - .foregroundStyle(.secondary) - - if event.hasLocation { - Button { - Task { - await event.openInMaps() - } - } label: { - HStack { - Image(systemName: "mappin.and.ellipse") - Text(event.displayLocation) - .multilineTextAlignment(.center) - } - .font(.subheadline) - .foregroundStyle(.secondary) - } - } - } - - // Description - if !event.plainDescription.isEmpty { - let lines = event.plainDescription.components(separatedBy: .newlines) - VStack(alignment: .center, spacing: 4) { - ForEach(lines, id: \.self) { line in - if line.starts(with: "📞") { - Button { - Task { - event.callPhone() - } - } label: { - Text(line) - .font(.body) - .multilineTextAlignment(.center) - .foregroundStyle(.blue) - } - } else { - Text(line) - .font(.body) - .multilineTextAlignment(.center) - } - } - } - .fixedSize(horizontal: false, vertical: true) - .padding(.top, 8) - } - - // Add to Calendar button - Button { - Task { - await event.addToCalendar { success, error in - if success { - alertTitle = "Success" - alertMessage = "Event has been added to your calendar" - } else { - alertTitle = "Error" - alertMessage = error?.localizedDescription ?? "Failed to add event to calendar" - } - showingAlert = true - } - } - } label: { - Label("Add to Calendar", systemImage: "calendar.badge.plus") - .font(.headline) - .foregroundColor(.white) - .frame(maxWidth: .infinity) - .padding() - .background(Color.blue) - .clipShape(RoundedRectangle(cornerRadius: 12)) - } - .padding(.top, 16) - } - .padding(.horizontal) - } - } - .background(Color(.systemBackground)) - .alert(alertTitle, isPresented: $showingAlert) { - Button("OK", role: .cancel) { } - } message: { - Text(alertMessage) - } - } -} diff --git a/Views/EventsListView.swift b/Views/EventsListView.swift new file mode 100644 index 0000000..27324b8 --- /dev/null +++ b/Views/EventsListView.swift @@ -0,0 +1,292 @@ +import SwiftUI + +struct EventsListView: View { + @Environment(ChurchDataService.self) private var dataService + @State private var searchText = "" + @State private var selectedCategory = "All" + @State private var showingFilters = false + + private let categories = ["All", "Service", "Ministry", "Social", "Other"] + + private var grayBackgroundColor: Color { + #if os(iOS) + Color(.systemGray6) + #else + Color.gray.opacity(0.2) + #endif + } + + private var systemBackgroundColor: Color { + #if os(iOS) + Color(.systemBackground) + #else + Color.black + #endif + } + + var filteredEvents: [ChurchEvent] { + var events = dataService.events + + // Filter by search text + events = SearchUtils.searchEvents(events, searchText: searchText) + + // Filter by category + if selectedCategory != "All" { + events = events.filter { $0.category.lowercased() == selectedCategory.lowercased() } + } + + return events + } + + var body: some View { + VStack(spacing: 0) { + // Search and Filter Bar + VStack(spacing: 12) { + // Search Bar + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(.secondary) + + TextField("Search events...", text: $searchText) + .textFieldStyle(PlainTextFieldStyle()) + + if !searchText.isEmpty { + Button(action: { searchText = "" }) { + Image(systemName: "xmark.circle.fill") + .foregroundColor(.secondary) + } + } + } + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background(grayBackgroundColor) + .cornerRadius(10) + + // Category Filter + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 8) { + ForEach(categories, id: \.self) { category in + Button(action: { + selectedCategory = category + }) { + Text(category) + .font(.subheadline) + .fontWeight(.medium) + .padding(.horizontal, 16) + .padding(.vertical, 8) + .background(selectedCategory == category ? Color.blue : grayBackgroundColor) + .foregroundColor(selectedCategory == category ? .white : .primary) + .cornerRadius(20) + } + } + } + .padding(.horizontal, 20) + } + } + .padding(.horizontal, 20) + .padding(.top, 8) + .padding(.bottom, 16) + .background(systemBackgroundColor) + + // Events List + if dataService.isLoading { + Spacer() + ProgressView("Loading events...") + Spacer() + } else if filteredEvents.isEmpty { + Spacer() + VStack(spacing: 16) { + Image(systemName: "calendar.badge.exclamationmark") + .font(.system(size: 50)) + .foregroundColor(.secondary) + + Text("No events found") + .font(.headline) + .foregroundColor(.secondary) + + if !searchText.isEmpty || selectedCategory != "All" { + Text("Try adjusting your search or filters") + .font(.subheadline) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + } + } + .padding() + Spacer() + } else { + ScrollView { + LazyVStack(spacing: 16) { + ForEach(filteredEvents) { event in + NavigationLink(destination: EventDetailViewWrapper(event: event) + .environment(dataService)) { + EventListCard(event: event) + } + .buttonStyle(.plain) + } + } + .padding(.horizontal, 20) + .padding(.bottom, 100) + } + .refreshable { + await dataService.loadAllEvents() + } + } + } + .navigationTitle("Events") + #if os(iOS) + .navigationBarTitleDisplayMode(.large) + #endif + .task { + await dataService.loadAllEvents() + } + } +} + +// MARK: - Event List Card Component + +struct EventListCard: View { + let event: ChurchEvent + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + private var systemBackgroundColor: Color { + #if os(iOS) + Color(.systemBackground) + #else + Color.black + #endif + } + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + // Hero Image or Gradient + Group { + if let imageUrl = event.image, !imageUrl.isEmpty { + CachedAsyncImage(url: URL(string: imageUrl)) { image in + image + .resizable() + .aspectRatio(contentMode: .fill) + } placeholder: { + gradientBackground + } + } else { + gradientBackground + } + } + .frame(height: horizontalSizeClass == .regular ? 180 : 140) + .clipped() + .overlay( + // Category Badge + VStack { + HStack { + if !event.category.isEmpty { + Text(event.category.uppercased()) + .font(.caption2) + .fontWeight(.bold) + .foregroundColor(.white) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(.ultraThinMaterial) + .cornerRadius(6) + } + Spacer() + + if event.isFeatured { + Image(systemName: "star.fill") + .foregroundColor(.yellow) + .font(.caption) + } + } + Spacer() + } + .padding(12) + ) + + // Event Details + VStack(alignment: .leading, spacing: 8) { + Text(event.title) + .font(.headline) + .fontWeight(.semibold) + .lineLimit(2) + .multilineTextAlignment(.leading) + + // Date and Time + HStack(spacing: 4) { + Image(systemName: "calendar") + .foregroundColor(.blue) + .font(.caption) + + if event.isMultiDay { + // Multi-day: show formatted time which contains "Aug 30 - Aug 31 at 6 PM" + Text(event.formattedTime) + .font(.subheadline) + .foregroundColor(.secondary) + } else { + // Single day: show date and time separately + Text(event.formattedDateRange) + .font(.subheadline) + .foregroundColor(.secondary) + + Text("•") + .foregroundColor(.secondary) + .font(.caption) + + Text(event.formattedTime) + .font(.subheadline) + .foregroundColor(.secondary) + } + } + + // Location + if !event.location.isEmpty { + HStack(spacing: 4) { + Image(systemName: "location") + .foregroundColor(.blue) + .font(.caption) + + Text(event.location) + .font(.subheadline) + .foregroundColor(.secondary) + .lineLimit(1) + } + } + + // Recurring indicator + if let recurringType = event.recurringType, !recurringType.isEmpty { + HStack(spacing: 4) { + Image(systemName: "repeat") + .foregroundColor(.orange) + .font(.caption) + + Text(recurringType.capitalized) + .font(.caption) + .foregroundColor(.orange) + .fontWeight(.medium) + } + } + } + .padding(16) + } + .background(systemBackgroundColor) + .cornerRadius(12) + .shadow(color: .black.opacity(0.1), radius: 4, x: 0, y: 2) + .contentShape(RoundedRectangle(cornerRadius: 12)) + } + + private var gradientBackground: some View { + LinearGradient( + colors: [ + Color.blue.opacity(0.7), + Color.purple.opacity(0.6) + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + } +} + +#Preview { + NavigationStack { + EventsListView() + .environment(ChurchDataService.shared) + } +} diff --git a/Views/EventsView.swift b/Views/EventsView.swift deleted file mode 100644 index b95bbbc..0000000 --- a/Views/EventsView.swift +++ /dev/null @@ -1,60 +0,0 @@ -import SwiftUI - -struct EventsView: View { - @StateObject private var viewModel = EventsViewModel() - @State private var selectedEvent: Event? - - var body: some View { - NavigationStack { - Group { - if viewModel.isLoading { - ProgressView() - } else if let error = viewModel.error { - VStack(spacing: 16) { - Text("Unable to load events") - .font(.headline) - Text(error.localizedDescription) - .font(.subheadline) - .foregroundStyle(.secondary) - Button("Try Again") { - Task { - await viewModel.loadEvents() - } - } - .buttonStyle(.bordered) - } - } else if viewModel.events.isEmpty { - Text("No upcoming events") - .font(.headline) - .foregroundStyle(.secondary) - } else { - ScrollView { - LazyVStack(spacing: 24) { - ForEach(viewModel.events) { event in - EventCard(event: event) { - selectedEvent = event - } - .padding(.horizontal) - } - } - .padding(.vertical) - } - .refreshable { - await viewModel.loadEvents() - } - } - } - .navigationTitle("Events") - .sheet(item: $selectedEvent) { event in - EventDetailView(event: event) - } - .task { - await viewModel.loadEvents() - } - } - } -} - -#Preview { - EventsView() -} diff --git a/Views/FilterSheet.swift b/Views/FilterSheet.swift new file mode 100644 index 0000000..fb0aeb5 --- /dev/null +++ b/Views/FilterSheet.swift @@ -0,0 +1,236 @@ +import SwiftUI + +struct FilterSheet: View { + let selectedTab: WatchView.WatchTab + @Binding var selectedSpeaker: String + @Binding var selectedDateRange: String + @Binding var selectedDuration: String + @Environment(\.dismiss) private var dismiss + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @Environment(ChurchDataService.self) private var dataService + + @State private var selectedYear: String = "All Time" + @State private var selectedMonth: String = "All Months" + + private var availableSpeakers: [String] { + let currentContent = selectedTab == .sermons ? dataService.sermons : dataService.livestreamArchives + let cleanedSpeakers = currentContent.compactMap { sermon -> String? in + let speaker = sermon.speaker.trimmingCharacters(in: .whitespacesAndNewlines) + return speaker.isEmpty ? nil : speaker + } + let uniqueSpeakers = Set(cleanedSpeakers) + return ["All Speakers"] + Array(uniqueSpeakers).sorted() + } + + private var availableYears: [String] { + var years = ["All Time"] + let calendar = Calendar.current + + // Use proper date formatter for your date format: "August 02, 2025" + let formatter = DateFormatter() + formatter.dateFormat = "MMMM dd, yyyy" + formatter.locale = Locale(identifier: "en_US") + + let currentContent = selectedTab == .sermons ? dataService.sermons : dataService.livestreamArchives + + // Extract all unique years from content + var yearSet: Set = [] + + for item in currentContent { + guard let dateString = item.date, + !dateString.isEmpty, + let date = formatter.date(from: dateString) else { continue } + + let year = calendar.component(.year, from: date) + yearSet.insert(year) + } + + // Add years in descending order + let sortedYears = yearSet.sorted(by: >) + for year in sortedYears { + years.append(String(year)) + } + + return years + } + + private var availableMonths: [String] { + guard selectedYear != "All Time", let targetYear = Int(selectedYear) else { + return ["All Months"] + } + + var months = ["All Months"] + let calendar = Calendar.current + + // Use proper date formatter for your date format: "August 02, 2025" + let formatter = DateFormatter() + formatter.dateFormat = "MMMM dd, yyyy" + formatter.locale = Locale(identifier: "en_US") + + let currentContent = selectedTab == .sermons ? dataService.sermons : dataService.livestreamArchives + + // Extract months for the selected year + var monthSet: Set = [] + + for item in currentContent { + guard let dateString = item.date, + !dateString.isEmpty, + let date = formatter.date(from: dateString) else { continue } + + let year = calendar.component(.year, from: date) + let month = calendar.component(.month, from: date) + + if year == targetYear { + monthSet.insert(month) + } + } + + // Add months in descending order (most recent first) + let monthNames = ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"] + + let sortedMonths = monthSet.sorted(by: >) + for month in sortedMonths { + if month >= 1 && month <= 12 { + months.append(monthNames[month-1]) + } + } + + print("🔍 DEBUG: availableMonths for year \(targetYear): \(months)") + return months + } + private let durations = ["Any Duration", "Under 30 min", "30-60 min", "Over 60 min"] + + var body: some View { + NavigationStack { + Form { + Section { + Text("Filter \(selectedTab.rawValue)") + .font(.headline) + .foregroundColor(.primary) + } header: { + EmptyView() + } + + Section("Speaker") { + Picker("Speaker", selection: $selectedSpeaker) { + ForEach(availableSpeakers, id: \.self) { speaker in + Text(speaker).tag(speaker) + } + } + .pickerStyle(.menu) + } + + Section("Date Range") { + Picker("Year", selection: $selectedYear) { + ForEach(availableYears, id: \.self) { year in + Text(year).tag(year) + } + } + .pickerStyle(.menu) + .onChange(of: selectedYear) { _, newYear in + // Don't auto-reset month when year changes during initialization + // Only reset if it's a user-initiated change + if selectedMonth != "All Months" && !availableMonths.contains(selectedMonth) { + selectedMonth = "All Months" + } + } + + // Only show month picker if a specific year is selected + if selectedYear != "All Time" { + Picker("Month", selection: $selectedMonth) { + ForEach(availableMonths, id: \.self) { month in + Text(month).tag(month) + } + } + .pickerStyle(.menu) + } + } + + Section("Duration") { + Picker("Duration", selection: $selectedDuration) { + ForEach(durations, id: \.self) { duration in + Text(duration).tag(duration) + } + } + .pickerStyle(.menu) + } + + Section { + Button("Reset Filters") { + selectedSpeaker = "All Speakers" + selectedYear = "All Time" + selectedMonth = "All Months" + selectedDuration = "Any Duration" + } + .foregroundColor(.red) + } + } + .navigationTitle("Filters") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button("Cancel") { + dismiss() + } + } + + ToolbarItem(placement: .navigationBarTrailing) { + Button("Apply") { + // Convert year/month selection to selectedDateRange format + if selectedYear == "All Time" { + selectedDateRange = "All Time" + } else if selectedMonth == "All Months" { + selectedDateRange = selectedYear + } else { + selectedDateRange = "\(selectedMonth) \(selectedYear)" + } + dismiss() + } + .fontWeight(.semibold) + .foregroundColor(Color(hex: "fb8b23")) + } + } + } + .presentationDetents([.medium, .large]) + .presentationDragIndicator(.visible) + .onAppear { + // Initialize year/month from current selectedDateRange + print("🔍 DEBUG: Initializing filter sheet with selectedDateRange: '\(selectedDateRange)'") + + if selectedDateRange == "All Time" { + selectedYear = "All Time" + selectedMonth = "All Months" + } else { + // Check if it's a year-only filter (e.g., "2024") + if let _ = Int(selectedDateRange) { + selectedYear = selectedDateRange + selectedMonth = "All Months" + print("🔍 DEBUG: Set year-only filter - Year: \(selectedYear), Month: \(selectedMonth)") + } else { + // It's a month-year filter (e.g., "January 2024") + let components = selectedDateRange.components(separatedBy: " ") + if components.count == 2 { + selectedYear = components[1] + selectedMonth = components[0] + print("🔍 DEBUG: Set month-year filter - Year: \(selectedYear), Month: \(selectedMonth)") + + // Force a small delay to ensure availableMonths is computed before validation + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + if !availableMonths.contains(selectedMonth) { + print("🔍 DEBUG: Month '\(selectedMonth)' not found in available months, resetting") + selectedMonth = "All Months" + } else { + print("🔍 DEBUG: Month '\(selectedMonth)' validated successfully") + } + } + } else { + selectedYear = "All Time" + selectedMonth = "All Months" + print("🔍 DEBUG: Failed to parse, defaulting to All Time") + } + } + } + } + } +} \ No newline at end of file diff --git a/Views/HomeFeedView.swift b/Views/HomeFeedView.swift new file mode 100644 index 0000000..dc1d91a --- /dev/null +++ b/Views/HomeFeedView.swift @@ -0,0 +1,450 @@ +import SwiftUI + +struct HomeFeedView: View { + @Environment(ChurchDataService.self) private var dataService + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + if horizontalSizeClass == .regular { + // iPad: Enhanced entertainment-focused layout + iPadHomeFeedView() + } else { + // iPhone: Current compact layout + iPhoneHomeFeedView() + } + } +} + +struct iPadHomeFeedView: View { + @Environment(ChurchDataService.self) private var dataService + + var body: some View { + ScrollView { + LazyVStack(spacing: 32) { + // Hero Section - Latest Featured Sermon + if let latestSermon = dataService.sermons.first { + VStack(alignment: .leading, spacing: 0) { + HStack { + VStack(alignment: .leading, spacing: 8) { + Text("LATEST MESSAGE") + .font(.caption) + .fontWeight(.bold) + .foregroundColor(Color(hex: getBrandColor())) + .textCase(.uppercase) + .tracking(1) + + Text("Featured Sermon") + .font(.system(size: 32, weight: .bold)) + .foregroundColor(.primary) + } + + Spacer() + + NavigationLink("Browse All") { + WatchView() + } + .font(.system(size: 16, weight: .medium)) + .foregroundColor(Color(hex: getBrandColor())) + } + .padding(.horizontal, 24) + .padding(.top, 16) + + Button { + if latestSermon.videoUrl != nil, let url = URL(string: getOptimalStreamingUrl(mediaId: latestSermon.id)) { + SharedVideoManager.shared.playVideo(url: url, title: latestSermon.title, artworkURL: latestSermon.thumbnail) + } + } label: { + HeroSermonCard(sermon: latestSermon) + } + .buttonStyle(PlainButtonStyle()) + .padding(.top, 20) + } + } + + // Recent Sermons Grid + if dataService.sermons.count > 1 { + VStack(alignment: .leading, spacing: 20) { + HStack { + Text("Recent Sermons") + .font(.system(size: 28, weight: .bold)) + Spacer() + NavigationLink("View All") { + WatchView() + } + .font(.system(size: 16, weight: .medium)) + .foregroundColor(Color(hex: getBrandColor())) + } + .padding(.horizontal, 24) + + // 2x2 grid layout for iPads + LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 16), count: 2), spacing: 16) { + ForEach(Array(dataService.sermons.dropFirst().prefix(4)), id: \.id) { sermon in + Button { + if sermon.videoUrl != nil, let url = URL(string: getOptimalStreamingUrl(mediaId: sermon.id)) { + SharedVideoManager.shared.playVideo(url: url, title: sermon.title, artworkURL: sermon.thumbnail) + } + } label: { + SermonGridCard(sermon: sermon) + } + .buttonStyle(PlainButtonStyle()) + } + } + .padding(.horizontal, 24) + } + } + + // Upcoming Events Section + if !dataService.events.isEmpty { + VStack(alignment: .leading, spacing: 20) { + HStack { + VStack(alignment: .leading, spacing: 4) { + Text("WHAT'S HAPPENING") + .font(.caption) + .fontWeight(.bold) + .foregroundColor(Color(hex: getBrandColor())) + .textCase(.uppercase) + .tracking(1) + + Text("Upcoming Events") + .font(.system(size: 28, weight: .bold)) + } + + Spacer() + + NavigationLink("All Events") { + EventsListView() + } + .font(.system(size: 16, weight: .medium)) + .foregroundColor(Color(hex: getBrandColor())) + } + .padding(.horizontal, 24) + + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 16) { + ForEach(dataService.events.prefix(5), id: \.id) { event in + NavigationLink { + EventDetailViewWrapper(event: event) + } label: { + ChurchEventHighlightCard(event: event) + .frame(width: 300) + } + .buttonStyle(.plain) + } + } + .padding(.horizontal, 24) + } + } + } + + // Quick Actions removed - functionality available via main navigation tabs + } + .padding(.vertical, 20) + .padding(.bottom, 100) + } + .navigationTitle("Welcome to RTSDA") + .navigationBarTitleDisplayMode(.large) + .refreshable { + await dataService.loadHomeFeed() + } + } +} + +struct iPhoneHomeFeedView: View { + @Environment(ChurchDataService.self) private var dataService + + var body: some View { + ScrollView { + LazyVStack(spacing: 20) { + if !dataService.sermons.isEmpty { + VStack(alignment: .leading, spacing: 16) { + HStack { + Text("Featured Sermons") + .font(.system(size: 24, weight: .bold)) + Spacer() + NavigationLink("See All") { + WatchView() + } + .font(.subheadline) + .foregroundColor(.blue) + } + + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 16) { + ForEach(dataService.sermons.prefix(5), id: \.id) { sermon in + SermonCard(sermon: sermon, style: .feed) + .frame(width: 250) + } + } + .padding(.horizontal, 20) + } + } + } + + if !dataService.events.isEmpty { + VStack(alignment: .leading, spacing: 16) { + HStack { + Text("Upcoming Events") + .font(.system(size: 24, weight: .bold)) + Spacer() + NavigationLink("See All") { + EventsListView() + } + .font(.subheadline) + .foregroundColor(.blue) + } + + ForEach(dataService.events.prefix(3), id: \.id) { event in + FeedItemCard(item: FeedItem(type: .event(event), timestamp: Date())) + } + } + } + } + .padding(.horizontal, 20) + .padding(.bottom, 100) + } + .navigationTitle("Welcome to RTSDA") + .navigationBarTitleDisplayMode(.large) + .refreshable { + await dataService.loadHomeFeed() + } + } +} + +// MARK: - iPad-specific Cards + +struct HeroSermonCard: View { + let sermon: Sermon + + var body: some View { + ZStack { + // Background image with gradient overlay + AsyncImage(url: URL(string: sermon.thumbnail ?? "")) { image in + image + .resizable() + .aspectRatio(contentMode: .fill) + .offset(y: 20) // Shift DOWN to show heads instead of cutting them off + .scaleEffect(0.9) // Zoom out to ensure heads are visible + } placeholder: { + Rectangle() + .fill(LinearGradient( + colors: [Color(hex: getBrandColor()), Color(hex: getBrandColor()).opacity(0.7)], + startPoint: .topLeading, + endPoint: .bottomTrailing + )) + } + .frame(height: 450) + .clipped() + + // Gradient overlay + LinearGradient( + colors: [Color.clear, Color.black.opacity(0.8)], + startPoint: .top, + endPoint: .bottom + ) + + // Content overlay + VStack(alignment: .leading) { + Spacer() + + HStack { + VStack(alignment: .leading, spacing: 12) { + // Play button + Image(systemName: "play.circle.fill") + .font(.system(size: 60)) + .foregroundColor(.white) + .shadow(color: .black.opacity(0.3), radius: 4) + + VStack(alignment: .leading, spacing: 8) { + Text(sermon.title) + .font(.system(size: 28, weight: .bold)) + .foregroundColor(.white) + .lineLimit(2) + .shadow(color: .black.opacity(0.5), radius: 2) + + HStack(spacing: 16) { + if !sermon.speaker.isEmpty { + HStack(spacing: 4) { + Image(systemName: "person.fill") + .font(.caption) + Text(sermon.speaker) + .font(.subheadline) + .fontWeight(.medium) + } + } + + if let duration = sermon.durationFormatted { + HStack(spacing: 4) { + Image(systemName: "clock.fill") + .font(.caption) + Text(duration) + .font(.subheadline) + } + } + + Text(sermon.formattedDate) + .font(.subheadline) + } + .foregroundColor(.white.opacity(0.9)) + .shadow(color: .black.opacity(0.3), radius: 1) + } + } + + Spacer() + } + .padding(32) + } + } + .cornerRadius(16) + .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) + .padding(.horizontal, 24) + } +} + +struct SermonGridCard: View { + let sermon: Sermon + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + // Thumbnail + ZStack { + AsyncImage(url: URL(string: sermon.thumbnail ?? "")) { image in + image + .resizable() + .aspectRatio(contentMode: .fill) + .offset(y: 10) // Shift DOWN to show heads instead of cutting them off + .scaleEffect(0.95) // Zoom out to ensure heads are visible + } placeholder: { + Rectangle() + .fill(LinearGradient( + colors: [Color(hex: getBrandColor()), Color(hex: getBrandColor()).opacity(0.7)], + startPoint: .topLeading, + endPoint: .bottomTrailing + )) + } + .frame(height: 200) + .clipped() + + // Play button overlay + Circle() + .fill(.black.opacity(0.6)) + .frame(width: 50, height: 50) + .overlay { + Image(systemName: "play.fill") + .font(.title2) + .foregroundColor(.white) + .offset(x: 2) // Visual centering + } + } + .cornerRadius(12) + + // Content + VStack(alignment: .leading, spacing: 8) { + Text(sermon.title) + .font(.system(size: 16, weight: .semibold)) + .lineLimit(2) + .multilineTextAlignment(.leading) + + HStack { + if !sermon.speaker.isEmpty { + Text(sermon.speaker) + .font(.system(size: 14)) + .foregroundColor(.secondary) + .lineLimit(1) + } + + Spacer() + + if let duration = sermon.durationFormatted { + Text(duration) + .font(.system(size: 14)) + .foregroundColor(.secondary) + } + } + + Text(sermon.formattedDate) + .font(.system(size: 13)) + .foregroundColor(.secondary) + } + .padding(.horizontal, 4) + } + .frame(height: 300) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.05), radius: 8, x: 0, y: 2) + } +} + +struct ChurchEventHighlightCard: View { + let event: ChurchEvent + + var body: some View { + HStack(spacing: 16) { + // Date indicator + VStack(spacing: 4) { + Text(event.dayOfMonth) + .font(.system(size: 24, weight: .bold)) + .foregroundColor(Color(hex: getBrandColor())) + + Text(event.monthAbbreviation) + .font(.system(size: 12, weight: .semibold)) + .foregroundColor(.secondary) + .textCase(.uppercase) + } + .frame(width: 50) + .padding(.vertical, 12) + .background(Color(hex: getBrandColor()).opacity(0.1), in: RoundedRectangle(cornerRadius: 12)) + + VStack(alignment: .leading, spacing: 8) { + Text(event.title) + .font(.system(size: 18, weight: .semibold)) + .lineLimit(2) + + if !event.description.isEmpty { + Text(event.description.stripHtml()) + .font(.system(size: 14)) + .foregroundColor(.secondary) + .lineLimit(2) + } + + HStack(spacing: 8) { + Image(systemName: "clock.fill") + .font(.caption) + .foregroundColor(Color(hex: getBrandColor())) + + Text(event.timeString) + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.secondary) + + if !event.location.isEmpty { + Image(systemName: "location.fill") + .font(.caption) + .foregroundColor(Color(hex: getBrandColor())) + + Text(event.location) + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.secondary) + .lineLimit(1) + } + } + } + + Spacer() + } + .padding(20) + .frame(height: 140) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.05), radius: 6, x: 0, y: 2) + } +} + +// HomeQuickActionCard removed - no longer needed + +// MARK: - Extensions + +// All date/time formatting now handled by Rust church-core crate (RTSDA Architecture Rules compliance) +// ChurchEvent now includes dayOfMonth, monthAbbreviation, and timeString fields directly from Rust + +extension String { + func stripHtml() -> String { + return self.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil) + } +} diff --git a/Views/JellyfinPlayerView.swift b/Views/JellyfinPlayerView.swift deleted file mode 100644 index 6bd56d8..0000000 --- a/Views/JellyfinPlayerView.swift +++ /dev/null @@ -1,39 +0,0 @@ -import SwiftUI -import AVKit - -class PlayerViewController: AVPlayerViewController { - override func viewDidLoad() { - super.viewDidLoad() - allowsPictureInPicturePlayback = true - } -} - -struct JellyfinPlayerView: View { - let videoUrl: String - @Environment(\.dismiss) private var dismiss - - var body: some View { - if let url = URL(string: videoUrl) { - VideoViewControllerRepresentable(url: url, dismiss: dismiss) - .ignoresSafeArea() - .onAppear { - try? AVAudioSession.sharedInstance().setCategory(.playback) - try? AVAudioSession.sharedInstance().setActive(true) - } - } - } -} - -struct VideoViewControllerRepresentable: UIViewControllerRepresentable { - let url: URL - let dismiss: DismissAction - - func makeUIViewController(context: Context) -> PlayerViewController { - let controller = PlayerViewController() - controller.player = AVPlayer(url: url) - controller.player?.play() - return controller - } - - func updateUIViewController(_ uiViewController: PlayerViewController, context: Context) {} -} \ No newline at end of file diff --git a/Views/LivestreamCard.swift b/Views/LivestreamCard.swift deleted file mode 100644 index 78c4bf2..0000000 --- a/Views/LivestreamCard.swift +++ /dev/null @@ -1,57 +0,0 @@ -import SwiftUI - -struct LivestreamCard: View { - let livestream: Message - - var body: some View { - NavigationLink { - if let url = URL(string: livestream.videoUrl) { - VideoPlayerView(url: url) - } - } label: { - VStack(alignment: .leading, spacing: 8) { - // Thumbnail - AsyncImage(url: URL(string: livestream.thumbnailUrl ?? "")) { image in - image.resizable() - } placeholder: { - Color.gray.opacity(0.3) - } - .aspectRatio(16/9, contentMode: .fill) - .clipped() - - // Content - VStack(alignment: .leading, spacing: 6) { - Text(livestream.title) - .font(.custom("Montserrat-SemiBold", size: 18)) - .lineLimit(2) - - HStack { - Text(livestream.speaker) - .font(.custom("Montserrat-Regular", size: 14)) - .foregroundColor(.secondary) - - Spacer() - - Text(livestream.formattedDate) - .font(.custom("Montserrat-Regular", size: 14)) - .foregroundColor(.secondary) - } - - if livestream.isLiveStream { - Text("LIVE") - .font(.custom("Montserrat-Bold", size: 12)) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Color.red) - .foregroundColor(.white) - .cornerRadius(4) - } - } - .padding(.horizontal) - .padding(.bottom) - } - .background(Color(.systemBackground)) - .cornerRadius(8) - } - } -} \ No newline at end of file diff --git a/Views/MainAppView.swift b/Views/MainAppView.swift new file mode 100644 index 0000000..7ec393f --- /dev/null +++ b/Views/MainAppView.swift @@ -0,0 +1,274 @@ +import SwiftUI +import MapKit + +enum SidebarSelection: Hashable { + case home + case events + case watch + case connect + case bulletins +} + +struct MainAppView: View { + @State private var dataService = ChurchDataService.shared + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @State private var selectedView: SidebarSelection = .home + + var body: some View { + Group { + if horizontalSizeClass == .regular { + // iPad: Use standard NavigationSplitView + NavigationSplitView { + SidebarView(selectedView: $selectedView) + } detail: { + NavigationStack { + selectedDetailView + .environment(dataService) + } + } + } else { + // iPhone: Tab navigation + TabView { + NavigationStack { + HomeFeedView() + .environment(dataService) + } + .tabItem { + Label("Home", systemImage: "house.fill") + } + + NavigationStack { + EventsListView() + .environment(dataService) + } + .tabItem { + Label("Events", systemImage: "calendar") + } + + NavigationStack { + WatchView() + .environment(dataService) + } + .tabItem { + Label("Watch", systemImage: "play.rectangle.fill") + } + + NavigationStack { + ConnectView() + .environment(dataService) + } + .tabItem { + Label("Connect", systemImage: "person.2.fill") + } + + NavigationStack { + BulletinsView() + .environment(dataService) + } + .tabItem { + Label("Bulletins", systemImage: "newspaper.fill") + } + } + .tint(Color(hex: "fb8b23")) + } + } + .task { + await dataService.loadHomeFeed() + } + .overlay { + SharedVideoOverlay() + } + } + + + @ViewBuilder + private var selectedDetailView: some View { + switch selectedView { + case .home: + HomeFeedView() + case .events: + EventsListView() // Just use the regular list view - NavigationStack will handle details + case .watch: + WatchView() + case .connect: + ConnectView() + case .bulletins: + BulletinsView() + } + } +} + +struct SidebarView: View { + @Binding var selectedView: SidebarSelection + + var body: some View { + List { + Button(action: { selectedView = .home }) { + Label("Home", systemImage: "house.fill") + } + .foregroundColor(selectedView == .home ? .blue : .primary) + + Button(action: { selectedView = .events }) { + Label("Events", systemImage: "calendar") + } + .foregroundColor(selectedView == .events ? .blue : .primary) + + Button(action: { selectedView = .watch }) { + Label("Watch", systemImage: "play.rectangle.fill") + } + .foregroundColor(selectedView == .watch ? .blue : .primary) + + Button(action: { selectedView = .connect }) { + Label("Connect", systemImage: "person.2.fill") + } + .foregroundColor(selectedView == .connect ? .blue : .primary) + + Button(action: { selectedView = .bulletins }) { + Label("Bulletins", systemImage: "newspaper.fill") + } + .foregroundColor(selectedView == .bulletins ? .blue : .primary) + + // Quick Actions removed - functionality available via main tabs + } + .navigationTitle("RTSDA") + .navigationBarTitleDisplayMode(.large) + } +} + +// MARK: - Placeholder Views (to be implemented) + + + + + + + + + + + + + + + + + + + + + + + + + +// MARK: - New iPad Cards + + + + + + + + + + + + + + + +struct LiveStreamCard: View { + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + + var body: some View { + Button { + // Use shared video player like sermons do + if let livestreamUrl = getLivestreamUrl() { + SharedVideoManager.shared.playVideo(url: livestreamUrl) + } + } label: { + VStack(spacing: 0) { + ZStack { + // Background gradient + LinearGradient( + colors: [Color.red, Color.red.opacity(0.8)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + .frame(height: horizontalSizeClass == .regular ? 200 : 150) + + // Live indicator and play button + VStack(spacing: 16) { + HStack { + // LIVE indicator + HStack(spacing: 6) { + Circle() + .fill(.white) + .frame(width: 8, height: 8) + .opacity(0.9) + + Text("LIVE") + .font(.system(size: 14, weight: .bold)) + .foregroundColor(.white) + } + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(.black.opacity(0.6)) + .cornerRadius(20) + + Spacer() + } + + // Play button + Circle() + .fill(.white.opacity(0.9)) + .frame(width: horizontalSizeClass == .regular ? 70 : 60, + height: horizontalSizeClass == .regular ? 70 : 60) + .overlay { + Image(systemName: "play.fill") + .font(.system(size: horizontalSizeClass == .regular ? 28 : 24)) + .foregroundColor(.red) + .offset(x: 2) // Visual centering + } + .shadow(color: .black.opacity(0.3), radius: 4) + } + .padding(20) + } + + // Content section + VStack(alignment: .leading, spacing: 12) { + HStack { + VStack(alignment: .leading, spacing: 6) { + Text("Live Stream") + .font(.system(size: horizontalSizeClass == .regular ? 20 : 18, weight: .bold)) + .foregroundColor(.primary) + + Text("Join our live worship service") + .font(.system(size: horizontalSizeClass == .regular ? 16 : 14)) + .foregroundColor(.secondary) + } + + Spacer() + + Image(systemName: "chevron.right") + .font(.title2) + .foregroundColor(.secondary) + } + } + .padding(20) + } + } + .buttonStyle(PlainButtonStyle()) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 16)) + .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) + } + + private func getLivestreamUrl() -> URL? { + // Use Rust function for JSON parsing - NO business logic in Swift! + let statusJson = fetchStreamStatusJson() + let streamUrl = extractStreamUrlFromStatus(statusJson: statusJson) + + return streamUrl.isEmpty ? nil : URL(string: streamUrl) + } +} + diff --git a/Views/MessageCard.swift b/Views/MessageCard.swift deleted file mode 100644 index 22e1592..0000000 --- a/Views/MessageCard.swift +++ /dev/null @@ -1,60 +0,0 @@ -import SwiftUI -import AVKit - -struct MessageCard: View { - let message: Message - - var body: some View { - NavigationLink { - if let url = URL(string: message.videoUrl) { - VideoPlayerView(url: url) - } - } label: { - VStack(alignment: .leading, spacing: 8) { - // Thumbnail - AsyncImage(url: URL(string: message.thumbnailUrl ?? "")) { image in - image - .resizable() - .aspectRatio(16/9, contentMode: .fill) - .clipped() - } placeholder: { - Rectangle() - .fill(Color.gray.opacity(0.2)) - .aspectRatio(16/9, contentMode: .fill) - } - - VStack(alignment: .leading, spacing: 6) { - Text(message.title) - .font(.custom("Montserrat-SemiBold", size: 18)) - .lineLimit(2) - - HStack { - Text(message.speaker) - .font(.custom("Montserrat-Regular", size: 14)) - .foregroundColor(.secondary) - - Spacer() - - Text(message.formattedDate) - .font(.custom("Montserrat-Regular", size: 14)) - .foregroundColor(.secondary) - } - - if message.isLiveStream { - Text("LIVE") - .font(.custom("Montserrat-Bold", size: 12)) - .padding(.horizontal, 8) - .padding(.vertical, 4) - .background(Color.red) - .foregroundColor(.white) - .cornerRadius(4) - } - } - .padding() - } - .background(Color(.systemBackground)) - .cornerRadius(12) - .shadow(radius: 2) - } - } -} \ No newline at end of file diff --git a/Views/MessagesView.swift b/Views/MessagesView.swift deleted file mode 100644 index 6ad8986..0000000 --- a/Views/MessagesView.swift +++ /dev/null @@ -1,304 +0,0 @@ -import SwiftUI -import AVKit - -struct MessagesView: View { - @StateObject private var viewModel = MessagesViewModel() - @State private var selectedYear: String? - @State private var selectedMonth: String? - @State private var showingFilters = false - - var body: some View { - ScrollView { - VStack(spacing: 16) { - // Header with Filter Button and Active Filters - VStack(spacing: 8) { - HStack { - Button { - showingFilters = true - } label: { - HStack { - Image(systemName: "line.3.horizontal.decrease.circle.fill") - Text("Filter") - } - .font(.headline) - .foregroundStyle(.blue) - } - - Spacer() - - if selectedYear != nil || selectedMonth != nil { - Button(action: { - selectedYear = nil - selectedMonth = nil - viewModel.filterContent(year: nil, month: nil) - }) { - Text("Clear Filters") - .foregroundStyle(.red) - .font(.subheadline) - } - } - } - - // Active Filters Display - if selectedYear != nil || selectedMonth != nil || viewModel.currentMediaType == .livestreams { - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 8) { - // Media Type Pill - Text(viewModel.currentMediaType == .sermons ? "Sermons" : "Live Archives") - .font(.footnote.weight(.medium)) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background(Color.blue.opacity(0.1)) - .foregroundStyle(.blue) - .clipShape(Capsule()) - - // Year Pill (if selected) - if let year = selectedYear { - Text(year) - .font(.footnote.weight(.medium)) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background(Color.blue.opacity(0.1)) - .foregroundStyle(.blue) - .clipShape(Capsule()) - } - - // Month Pill (if selected) - if let month = selectedMonth { - Text(formatMonth(month)) - .font(.footnote.weight(.medium)) - .padding(.horizontal, 12) - .padding(.vertical, 6) - .background(Color.blue.opacity(0.1)) - .foregroundStyle(.blue) - .clipShape(Capsule()) - } - } - .padding(.horizontal) - } - } - } - .padding(.horizontal) - - // Content Section - if viewModel.isLoading { - ProgressView() - .padding() - } else if viewModel.error != nil { - VStack { - Text("Error loading content") - .foregroundColor(.red) - Button("Try Again") { - Task { - await viewModel.refreshContent() - } - } - } - .padding() - } else if viewModel.filteredMessages.isEmpty { - Text("No messages available") - .padding() - } else { - LazyVStack(spacing: 16) { - if let livestream = viewModel.livestream { - MessageCard(message: livestream) - .padding(.horizontal) - } - - ForEach(viewModel.filteredMessages) { message in - MessageCard(message: message) - .padding(.horizontal) - } - } - } - } - } - .navigationTitle(viewModel.currentMediaType == .sermons ? "Sermons" : "Live Archives") - .refreshable { - await viewModel.refreshContent() - } - .sheet(isPresented: $showingFilters) { - NavigationStack { - FilterView( - currentMediaType: $viewModel.currentMediaType, - selectedYear: $selectedYear, - selectedMonth: $selectedMonth, - availableYears: viewModel.availableYears, - availableMonths: viewModel.availableMonths, - onMediaTypeChange: { newType in - Task { - await viewModel.loadContent(mediaType: newType) - } - }, - onYearChange: { year in - if let year = year { - viewModel.updateMonthsForYear(year) - } - viewModel.filterContent(year: year, month: selectedMonth) - }, - onMonthChange: { month in - viewModel.filterContent(year: selectedYear, month: month) - } - ) - .navigationTitle("Filter Content") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .topBarLeading) { - Button("Done") { - showingFilters = false - } - } - - ToolbarItem(placement: .topBarTrailing) { - if selectedYear != nil || selectedMonth != nil { - Button("Reset") { - selectedYear = nil - selectedMonth = nil - viewModel.filterContent(year: nil, month: nil) - showingFilters = false - } - .foregroundStyle(.red) - } - } - } - } - .presentationDetents([.medium]) - } - } - - private func formatMonth(_ month: String) -> String { - let formatter = DateFormatter() - formatter.dateFormat = "MM" - - if let date = formatter.date(from: month) { - formatter.dateFormat = "MMM" - return formatter.string(from: date) - } - return month - } -} - -struct FilterView: View { - @Binding var currentMediaType: JellyfinService.MediaType - @Binding var selectedYear: String? - @Binding var selectedMonth: String? - let availableYears: [String] - let availableMonths: [String] - let onMediaTypeChange: (JellyfinService.MediaType) -> Void - let onYearChange: (String?) -> Void - let onMonthChange: (String?) -> Void - - var body: some View { - Form { - Section("Content Type") { - Picker("Type", selection: $currentMediaType) { - Text("Sermons").tag(JellyfinService.MediaType.sermons) - Text("Live Archives").tag(JellyfinService.MediaType.livestreams) - } - .pickerStyle(.segmented) - .onChange(of: currentMediaType) { oldValue, newValue in - selectedYear = nil - selectedMonth = nil - onMediaTypeChange(newValue) - } - } - - Section("Year") { - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 12) { - MessageFilterChip( - title: "All", - isSelected: selectedYear == nil, - action: { - selectedYear = nil - selectedMonth = nil - onYearChange(nil) - } - ) - - ForEach(availableYears, id: \.self) { year in - MessageFilterChip( - title: year, - isSelected: selectedYear == year, - action: { - selectedYear = year - selectedMonth = nil - onYearChange(year) - } - ) - } - } - .padding(.horizontal, 4) - } - } - - if selectedYear != nil && !availableMonths.isEmpty { - Section("Month") { - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 12) { - MessageFilterChip( - title: "All", - isSelected: selectedMonth == nil, - action: { - selectedMonth = nil - onMonthChange(nil) - } - ) - - ForEach(availableMonths, id: \.self) { month in - MessageFilterChip( - title: formatMonth(month), - isSelected: selectedMonth == month, - action: { - selectedMonth = month - onMonthChange(month) - } - ) - } - } - .padding(.horizontal, 4) - } - } - } - } - } - - private func formatMonth(_ month: String) -> String { - let formatter = DateFormatter() - formatter.dateFormat = "MM" - - if let date = formatter.date(from: month) { - formatter.dateFormat = "MMM" - return formatter.string(from: date) - } - return month - } -} - -struct MessageFilterChip: View { - let title: String - let isSelected: Bool - let action: () -> Void - - var body: some View { - Button(action: action) { - Text(title) - .font(.subheadline.weight(.medium)) - .padding(.horizontal, 16) - .padding(.vertical, 8) - .background(isSelected ? Color.blue : Color.gray.opacity(0.15)) - .foregroundStyle(isSelected ? .white : .primary) - .clipShape(Capsule()) - } - } -} - -// Helper extension for optional binding in Picker -extension Binding where Value == String? { - func toUnwrapped(defaultValue: String) -> Binding { - Binding( - get: { self.wrappedValue ?? defaultValue }, - set: { self.wrappedValue = $0 } - ) - } -} diff --git a/Views/OwncastView.swift b/Views/OwncastView.swift deleted file mode 100644 index d377878..0000000 --- a/Views/OwncastView.swift +++ /dev/null @@ -1,26 +0,0 @@ -import SwiftUI - -struct OwncastView: View { - @StateObject private var viewModel = OwncastViewModel() - - var body: some View { - NavigationStack { - Group { - if let streamUrl = viewModel.streamUrl { - VideoPlayerView(url: streamUrl) - } else { - ContentUnavailableView { - Label("Stream Offline", systemImage: "video.slash") - } description: { - Text("The live stream is currently offline") - } - } - } - .navigationTitle("Live Stream") - .navigationBarTitleDisplayMode(.inline) - } - .task { - await viewModel.checkStreamStatus() - } - } -} \ No newline at end of file diff --git a/Views/SharedVideoPlayerView.swift b/Views/SharedVideoPlayerView.swift new file mode 100644 index 0000000..45ac5f4 --- /dev/null +++ b/Views/SharedVideoPlayerView.swift @@ -0,0 +1,143 @@ +import SwiftUI +import AVKit +@preconcurrency import MediaPlayer + +// Global video player state +class SharedVideoManager: ObservableObject { + static let shared = SharedVideoManager() + + @Published var currentVideoURL: URL? + @Published var showFullScreenPlayer = false + @Published var isInPiPMode = false + + // Track the current player to force PiP stop + weak var currentPlayerController: AVPlayerViewController? + + // Store current media info for lock screen controls + var currentTitle: String? + var currentArtworkURL: String? + + private init() { + + } + + func playVideo(url: URL, title: String? = nil, artworkURL: String? = nil) { + // Store media info for lock screen controls + currentTitle = title + currentArtworkURL = artworkURL + + // If we already have a video playing, we need to switch to the new one + if currentVideoURL != nil { + if isInPiPMode { + // Force stop PiP if it's active + if let playerController = currentPlayerController { + // Force stop the player to close PiP + playerController.player?.pause() + playerController.player = nil + } + } + + // Clear the old video first to ensure proper cleanup + currentVideoURL = nil + showFullScreenPlayer = false + isInPiPMode = false + currentPlayerController = nil + + // Small delay to let old player fully cleanup before starting new one + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + self.currentVideoURL = url + self.showFullScreenPlayer = true + self.isInPiPMode = false + + } + } else { + // No existing video, start immediately + currentVideoURL = url + showFullScreenPlayer = true + isInPiPMode = false + + } + } + + func hideFullScreenPlayer() { + showFullScreenPlayer = false + } + + func stopVideo() { + // Stop the actual player first + if let playerController = currentPlayerController { + playerController.player?.pause() + playerController.player = nil + } + + // Clear all state + currentVideoURL = nil + showFullScreenPlayer = false + isInPiPMode = false + currentPlayerController = nil + currentTitle = nil + currentArtworkURL = nil + + } + + func setPiPMode(_ isPiP: Bool) { + isInPiPMode = isPiP + if isPiP { + // When PiP starts, hide full screen but DON'T touch currentVideoURL + showFullScreenPlayer = false + } else { + // When PiP ends, show full screen again + showFullScreenPlayer = true + } + } + +} + +// Shared video overlay that exists at app level - GLOBAL PERSISTENT PLAYER +struct SharedVideoOverlay: View { + @StateObject private var videoManager = SharedVideoManager.shared + @State private var currentPlayerURL: URL? + + var body: some View { + ZStack { + // ALWAYS render the persistent player when we have a URL + // Force recreate when URL changes to ensure old player is destroyed + if let url = videoManager.currentVideoURL { + PersistentVideoPlayer(url: url) + .id(url.absoluteString) // Force recreate when URL changes + .opacity(videoManager.showFullScreenPlayer ? 1 : 0) // Show/hide but never destroy + .allowsHitTesting(videoManager.showFullScreenPlayer) // Only interactive when visible + .background(videoManager.showFullScreenPlayer ? Color.black : Color.clear) + .ignoresSafeArea() + .onAppear { + + } + } + } + .onChange(of: videoManager.showFullScreenPlayer) { _, isVisible in + + } + .onChange(of: videoManager.currentVideoURL) { oldURL, newURL in + if let old = oldURL, let new = newURL, old != new { + + } else if newURL == nil { + + } + } + } +} + +// Persistent video player that stays alive +struct PersistentVideoPlayer: View { + let url: URL + + var body: some View { + VideoPlayerView(url: url) + .onAppear { + + } + .onDisappear { + + } + } +} \ No newline at end of file diff --git a/Views/SplashScreenView.swift b/Views/SplashScreenView.swift index 02d41c6..10ce283 100644 --- a/Views/SplashScreenView.swift +++ b/Views/SplashScreenView.swift @@ -1,7 +1,7 @@ import SwiftUI struct SplashScreenView: View { - @StateObject private var configService = ConfigService.shared + @State private var dataService = ChurchDataService.shared @State private var isActive = false @State private var size = 0.8 @State private var opacity = 0.5 @@ -23,86 +23,125 @@ struct SplashScreenView: View { var body: some View { if isActive { - ContentView() + MainAppView() } else { ZStack { - LinearGradient(gradient: Gradient(colors: [ - Color(hex: "3b0d11"), - Color(hex: "21070a") - ]), startPoint: .top, endPoint: .bottom) + // Modern background matching the app theme + LinearGradient( + colors: [.blue.opacity(0.1), .indigo.opacity(0.05)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) .ignoresSafeArea() + // Background pattern for subtle texture + GeometryReader { geometry in + ForEach(0..<20, id: \.self) { _ in + Circle() + .fill(.white.opacity(0.02)) + .frame(width: 20, height: 20) + .position( + x: CGFloat.random(in: 0...geometry.size.width), + y: CGFloat.random(in: 0...geometry.size.height) + ) + } + } + VStack(spacing: 20) { - Image("sdalogo") - .resizable() - .scaledToFit() - .frame(width: 80, height: 80) + Spacer() - Text("Rockville-Tolland SDA Church") - .font(.custom("Montserrat-SemiBold", size: 24)) - .foregroundColor(.white) - .multilineTextAlignment(.center) + // Logo and church info card + VStack(spacing: 24) { + // Logo with modern background + ZStack { + Circle() + .fill(.regularMaterial) + .frame(width: 120, height: 120) + .shadow(color: .black.opacity(0.1), radius: 8, x: 0, y: 4) + + Image("sdalogo") + .resizable() + .scaledToFit() + .frame(width: 80, height: 80) + } + + VStack(spacing: 12) { + Text("Rockville-Tolland") + .font(.custom("Montserrat-SemiBold", size: 28)) + .foregroundStyle(.primary) + + Text("SDA Church") + .font(.custom("Montserrat-Regular", size: 20)) + .foregroundStyle(.secondary) + + // Modern separator + Capsule() + .fill(Color(hex: "fb8b23")) + .frame(width: 80, height: 3) + } + } - Rectangle() - .fill(Color(hex: "fb8b23")) - .frame(width: 60, height: 2) + Spacer() + .frame(maxHeight: 40) - Text(bibleVerse) - .font(.custom("Lora-Italic", size: 18)) - .foregroundColor(.white.opacity(0.8)) - .multilineTextAlignment(.center) + // Bible verse card + if !bibleVerse.isEmpty { + VStack(spacing: 16) { + // Quote icon + Image(systemName: "quote.opening") + .font(.title2) + .foregroundStyle(Color(hex: "fb8b23").opacity(0.8)) + + VStack(spacing: 12) { + Text(bibleVerse) + .font(.custom("Lora-Italic", size: 18)) + .foregroundStyle(.primary) + .multilineTextAlignment(.center) + .lineSpacing(6) + + Text(bibleReference) + .font(.custom("Montserrat-Regular", size: 14)) + .foregroundStyle(Color(hex: "fb8b23")) + .fontWeight(.medium) + } + } + .padding(24) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 20)) + .shadow(color: .black.opacity(0.1), radius: 12, x: 0, y: 6) .padding(.horizontal, 20) - .lineSpacing(4) + } - Text(bibleReference) - .font(.custom("Montserrat-Regular", size: 14)) - .foregroundColor(Color(hex: "fb8b23")) + Spacer() } .padding() .scaleEffect(size) .opacity(opacity) .task { - // First load config - await configService.loadConfig() + // Load a random Bible verse from the Rust crate + await dataService.loadDailyVerse() - do { - let verse = try await BibleService.shared.getRandomVerse() - bibleVerse = verse.verse + if let verse = dataService.dailyVerse { + bibleVerse = verse.text bibleReference = verse.reference - - // Calculate display duration based on verse length - let displayDuration = calculateDisplayDuration(for: verse.verse) - - // Start fade in animation after verse is loaded - withAnimation(.easeIn(duration: fadeInDuration)) { - self.size = 0.9 - self.opacity = 1.0 - } - - // Wait for fade in + calculated display duration before transitioning - DispatchQueue.main.asyncAfter(deadline: .now() + fadeInDuration + displayDuration) { - withAnimation { - self.isActive = true - } - } - } catch { + } else { // Fallback to a default verse if API fails bibleVerse = "For God so loved the world that he gave his one and only Son, that whoever believes in him shall not perish but have eternal life." bibleReference = "John 3:16" - - // Calculate duration for fallback verse - let displayDuration = calculateDisplayDuration(for: bibleVerse) - - // Use same timing for fallback verse - withAnimation(.easeIn(duration: fadeInDuration)) { - self.size = 0.9 - self.opacity = 1.0 - } - - DispatchQueue.main.asyncAfter(deadline: .now() + fadeInDuration + displayDuration) { - withAnimation { - self.isActive = true - } + } + + // Calculate display duration based on verse length + let displayDuration = calculateDisplayDuration(for: bibleVerse) + + // Start fade in animation after verse is loaded + withAnimation(.easeIn(duration: fadeInDuration)) { + self.size = 0.9 + self.opacity = 1.0 + } + + // Wait for fade in + calculated display duration before transitioning + DispatchQueue.main.asyncAfter(deadline: .now() + fadeInDuration + displayDuration) { + withAnimation { + self.isActive = true } } } @@ -111,32 +150,7 @@ struct SplashScreenView: View { } } -extension Color { - init(hex: String) { - let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) - var int: UInt64 = 0 - Scanner(string: hex).scanHexInt64(&int) - let a, r, g, b: UInt64 - switch hex.count { - case 3: // RGB (12-bit) - (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) - case 6: // RGB (24-bit) - (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) - case 8: // ARGB (32-bit) - (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) - default: - (a, r, g, b) = (1, 1, 1, 0) - } - - self.init( - .sRGB, - red: Double(r) / 255, - green: Double(g) / 255, - blue: Double(b) / 255, - opacity: Double(a) / 255 - ) - } -} +// Color extension is already defined in FeedItemCard.swift #Preview { SplashScreenView() diff --git a/Views/VideoPlayerView.swift b/Views/VideoPlayerView.swift index 46ace44..90a8e57 100644 --- a/Views/VideoPlayerView.swift +++ b/Views/VideoPlayerView.swift @@ -1,15 +1,17 @@ import SwiftUI import AVKit +@preconcurrency import MediaPlayer struct VideoPlayerView: View { let url: URL @Environment(\.dismiss) private var dismiss @State private var isInPiPMode = false @State private var isLoading = true + @State private var isInNativeFullscreen = false var body: some View { ZStack { - VideoPlayerViewController(url: url, isInPiPMode: $isInPiPMode, isLoading: $isLoading) + VideoPlayerViewController(url: url, isInPiPMode: $isInPiPMode, isLoading: $isLoading, isInNativeFullscreen: $isInNativeFullscreen) .ignoresSafeArea() .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(isInPiPMode) @@ -20,15 +22,55 @@ struct VideoPlayerView: View { .scaleEffect(1.5) .tint(.white) } + + // Close button - only show when NOT in native fullscreen mode + #if os(iOS) + if !isInNativeFullscreen { + VStack { + HStack { + Spacer() + Button(action: { + SharedVideoManager.shared.stopVideo() + }) { + ZStack { + // Background circle for better tap target + Circle() + .fill(Color.black.opacity(0.6)) + .frame(width: 44, height: 44) + + Image(systemName: "xmark.circle.fill") + .font(.system(size: UIDevice.current.userInterfaceIdiom == .pad ? 28 : 20)) + .foregroundColor(.white) + } + } + .buttonStyle(.plain) + .frame(width: 44, height: 44) + .contentShape(Circle()) // Make the entire circle tappable + .padding(.top, UIDevice.current.userInterfaceIdiom == .pad ? 20 : 130) // Moved further below volume controls + .padding(.trailing, UIDevice.current.userInterfaceIdiom == .pad ? 20 : 16) // Better aligned with volume button on iPhone + } + Spacer() + } + } + #endif } .onAppear { + print("🎬 DEBUG: VideoPlayerView onAppear") setupAudio() } + .onDisappear { + print("🎬 DEBUG: VideoPlayerView onDisappear") + } } private func setupAudio() { - try? AVAudioSession.sharedInstance().setCategory(.playback) - try? AVAudioSession.sharedInstance().setActive(true) + do { + try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback) + try AVAudioSession.sharedInstance().setActive(true) + print("🎬 DEBUG: Audio session configured for playback with movie mode") + } catch { + print("🎬 DEBUG: Failed to configure audio session: \(error)") + } } } @@ -36,78 +78,221 @@ struct VideoPlayerViewController: UIViewControllerRepresentable { let url: URL @Binding var isInPiPMode: Bool @Binding var isLoading: Bool + @Binding var isInNativeFullscreen: Bool func makeUIViewController(context: Context) -> AVPlayerViewController { - let player = AVPlayer(url: url) + print("🎬 DEBUG: makeUIViewController called with URL: \(url)") + let playerItem = AVPlayerItem(url: url) + let player = AVPlayer(playerItem: playerItem) + + // Set metadata directly on the player item + setMetadataOnPlayerItem(playerItem) + let controller = AVPlayerViewController() controller.player = player controller.allowsPictureInPicturePlayback = true controller.delegate = context.coordinator - // Add observer for buffering state - player.addObserver(context.coordinator, forKeyPath: "timeControlStatus", options: [.old, .new], context: nil) + // Disable fullscreen functionality + controller.entersFullScreenWhenPlaybackBegins = false + controller.exitsFullScreenWhenPlaybackEnds = false + + print("🎬 DEBUG: Setting up player and controller") context.coordinator.setPlayerController(controller) + // Add observers for buffering state and duration + player.addObserver(context.coordinator, forKeyPath: "timeControlStatus", options: [.old, .new], context: nil) + player.currentItem?.addObserver(context.coordinator, forKeyPath: "duration", options: [.old, .new], context: nil) + context.coordinator.hasObserver = true + print("🎬 DEBUG: Observer added successfully") + + print("🎬 DEBUG: Starting playback") player.play() + + return controller } func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {} func makeCoordinator() -> Coordinator { - Coordinator(isInPiPMode: $isInPiPMode, isLoading: $isLoading) + Coordinator(isInPiPMode: $isInPiPMode, isLoading: $isLoading, isInNativeFullscreen: $isInNativeFullscreen) } + private func setMetadataOnPlayerItem(_ playerItem: AVPlayerItem) { + let videoManager = SharedVideoManager.shared + var metadata: [AVMetadataItem] = [] + + // Title + if let title = videoManager.currentTitle { + let titleItem = AVMutableMetadataItem() + titleItem.identifier = .commonIdentifierTitle + titleItem.value = title as NSString + titleItem.extendedLanguageTag = "und" + metadata.append(titleItem) + } + + // Artist + let artistItem = AVMutableMetadataItem() + artistItem.identifier = .commonIdentifierArtist + artistItem.value = "Rockville Tolland SDA Church" as NSString + artistItem.extendedLanguageTag = "und" + metadata.append(artistItem) + + // Artwork + if let artworkURLString = videoManager.currentArtworkURL, + let artworkURL = URL(string: artworkURLString) { + Task { + do { + let (data, _) = try await URLSession.shared.data(from: artworkURL) + if UIImage(data: data) != nil { + let artworkItem = AVMutableMetadataItem() + artworkItem.identifier = .commonIdentifierArtwork + artworkItem.value = data as NSData + artworkItem.dataType = kCMMetadataBaseDataType_JPEG as String + artworkItem.extendedLanguageTag = "und" + + await MainActor.run { + var updatedMetadata = playerItem.externalMetadata + updatedMetadata.append(artworkItem) + playerItem.externalMetadata = updatedMetadata + } + } + } catch { + print("Failed to load artwork for metadata: \(error)") + } + } + } + + playerItem.externalMetadata = metadata + } + + class Coordinator: NSObject, AVPlayerViewControllerDelegate { @Binding var isInPiPMode: Bool @Binding var isLoading: Bool + @Binding var isInNativeFullscreen: Bool internal var playerController: AVPlayerViewController? private var wasPlayingBeforeDismiss = false + internal var hasObserver = false - init(isInPiPMode: Binding, isLoading: Binding) { + init(isInPiPMode: Binding, isLoading: Binding, isInNativeFullscreen: Binding) { _isInPiPMode = isInPiPMode _isLoading = isLoading + _isInNativeFullscreen = isInNativeFullscreen + print("🎬 DEBUG: Coordinator init") super.init() } func setPlayerController(_ controller: AVPlayerViewController) { + print("🎬 DEBUG: setPlayerController called") playerController = controller + // Register with SharedVideoManager so it can control PiP + SharedVideoManager.shared.currentPlayerController = controller + print("🎬 DEBUG: Player registered with SharedVideoManager") } + override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + print("🎬 DEBUG: observeValue called for keyPath: \(keyPath ?? "nil")") if keyPath == "timeControlStatus", let player = object as? AVPlayer { + let status = player.timeControlStatus + print("🎬 DEBUG: timeControlStatus = \(status.rawValue)") DispatchQueue.main.async { self.isLoading = player.timeControlStatus == .waitingToPlayAtSpecifiedRate + print("🎬 DEBUG: isLoading set to \(self.isLoading)") + + } + } else if keyPath == "duration", + let playerItem = object as? AVPlayerItem { + let duration = playerItem.duration + if duration.isValid && !duration.isIndefinite { + let durationSeconds = CMTimeGetSeconds(duration) + print("🎬 DEBUG: Duration loaded: \(durationSeconds) seconds") + } } } func playerViewController(_ playerViewController: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { + print("🎬 DEBUG: willBeginFullScreenPresentation") + DispatchQueue.main.async { + self.isInNativeFullscreen = true + } if let player = playerController?.player { wasPlayingBeforeDismiss = (player.rate > 0) + print("🎬 DEBUG: wasPlayingBeforeDismiss = \(wasPlayingBeforeDismiss)") } } func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { + print("🎬 DEBUG: willEndFullScreenPresentation - exiting native fullscreen") + DispatchQueue.main.async { + self.isInNativeFullscreen = false + } if wasPlayingBeforeDismiss, let player = playerController?.player { - // Prevent the player from pausing during transition + print("🎬 DEBUG: Restoring playback rate to 1.0") player.rate = 1.0 } + + // Notify SharedVideoManager that we're exiting fullscreen + DispatchQueue.main.async { + SharedVideoManager.shared.showFullScreenPlayer = false + print("🎬 DEBUG: Set SharedVideoManager.showFullScreenPlayer = false") + } } func playerViewControllerWillStartPictureInPicture(_ playerViewController: AVPlayerViewController) { - isInPiPMode = true + print("🎬 DEBUG: PiP WILL START") + DispatchQueue.main.async { + self.isInPiPMode = true + SharedVideoManager.shared.setPiPMode(true) + print("🎬 DEBUG: isInPiPMode set to true") + } + } + + func playerViewControllerDidStartPictureInPicture(_ playerViewController: AVPlayerViewController) { + print("🎬 DEBUG: PiP DID START") + } + + func playerViewControllerWillStopPictureInPicture(_ playerViewController: AVPlayerViewController) { + print("🎬 DEBUG: PiP WILL STOP") } func playerViewControllerDidStopPictureInPicture(_ playerViewController: AVPlayerViewController) { - isInPiPMode = false + print("🎬 DEBUG: PiP DID STOP") + DispatchQueue.main.async { + self.isInPiPMode = false + SharedVideoManager.shared.setPiPMode(false) + print("🎬 DEBUG: isInPiPMode set to false") + } + } + + func playerViewController(_ playerViewController: AVPlayerViewController, failedToStartPictureInPictureWithError error: Error) { + print("🎬 DEBUG: PiP FAILED TO START: \(error)") + } + + func playerViewController(_ playerViewController: AVPlayerViewController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) { + print("🎬 DEBUG: RESTORE USER INTERFACE FOR PIP STOP - This is the 'Return to App' action") + print("🎬 DEBUG: Current isInPiPMode: \(isInPiPMode)") + print("🎬 DEBUG: playerController exists: \(playerController != nil)") + + // This is the critical method for "Return to App" + DispatchQueue.main.async { + print("🎬 DEBUG: Calling completionHandler(true)") + completionHandler(true) + } } deinit { - if let player = playerController?.player { + print("🎬 DEBUG: Coordinator deinit") + if let player = playerController?.player, hasObserver { + print("🎬 DEBUG: Removing observers in deinit") player.removeObserver(self, forKeyPath: "timeControlStatus") + player.currentItem?.removeObserver(self, forKeyPath: "duration") + hasObserver = false } } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/Views/WatchView.swift b/Views/WatchView.swift new file mode 100644 index 0000000..901458d --- /dev/null +++ b/Views/WatchView.swift @@ -0,0 +1,179 @@ +import SwiftUI + +struct WatchView: View { + @Environment(ChurchDataService.self) private var dataService + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @State private var searchText = "" + @State private var selectedTab: WatchTab = .sermons + + + private let grayBackgroundColor: Color = { + #if os(iOS) + Color(.systemGray6) + #else + Color.gray.opacity(0.2) + #endif + }() + + enum WatchTab: String, CaseIterable { + case sermons = "Sermons" + case liveArchives = "Live Archives" + + var icon: String { + switch self { + case .sermons: + return "play.rectangle.fill" + case .liveArchives: + return "dot.radiowaves.left.and.right" + } + } + } + + var currentContent: [Sermon] { + switch selectedTab { + case .sermons: + return dataService.sermons + case .liveArchives: + return dataService.livestreamArchives + } + } + + var filteredContent: [Sermon] { + let content = currentContent + + // Apply search text filter + return SearchUtils.searchSermons(content, searchText: searchText, contentType: selectedTab.rawValue.lowercased()) + } + + + + var body: some View { + VStack(spacing: 0) { + // Toggle buttons + HStack(spacing: 12) { + ForEach(WatchTab.allCases, id: \.self) { tab in + Button(action: { + selectedTab = tab + Task { + switch tab { + case .sermons: + await dataService.loadAllSermons() + case .liveArchives: + await dataService.loadAllLivestreamArchives() + } + } + }) { + HStack(spacing: 8) { + Image(systemName: tab.icon) + .font(horizontalSizeClass == .regular ? .body : .subheadline) + Text(tab.rawValue) + .font(.system(size: horizontalSizeClass == .regular ? 18 : 16, weight: .medium)) + } + .foregroundColor(selectedTab == tab ? .white : .secondary) + .padding(.horizontal, horizontalSizeClass == .regular ? 32 : 20) + .padding(.vertical, horizontalSizeClass == .regular ? 20 : 14) + .background( + selectedTab == tab ? + Color(hex: getBrandColor()) : + Color.clear, + in: RoundedRectangle(cornerRadius: horizontalSizeClass == .regular ? 12 : 10) + ) + .overlay( + RoundedRectangle(cornerRadius: horizontalSizeClass == .regular ? 12 : 10) + .strokeBorder(selectedTab == tab ? Color.clear : .secondary.opacity(0.3), lineWidth: 1) + ) + .contentShape(RoundedRectangle(cornerRadius: horizontalSizeClass == .regular ? 12 : 10)) + } + .buttonStyle(.plain) + } + Spacer() + } + .padding(.horizontal, 20) + .padding(.top, 16) + .padding(.bottom, 12) + + // Search bar + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(.secondary) + + TextField(selectedTab == .sermons ? "Search sermons..." : "Search live archives...", text: $searchText) + .textFieldStyle(PlainTextFieldStyle()) + + if !searchText.isEmpty { + Button(action: { searchText = "" }) { + Image(systemName: "xmark.circle.fill") + .foregroundColor(.secondary) + } + } + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .background(grayBackgroundColor) + .cornerRadius(12) + .padding(.horizontal, 20) + .padding(.bottom, 16) + + // Content + if dataService.isLoading { + Spacer() + ProgressView(selectedTab == .sermons ? "Loading sermons..." : "Loading live archives...") + Spacer() + } else if filteredContent.isEmpty { + Spacer() + VStack(spacing: 16) { + Image(systemName: selectedTab == .sermons ? "play.rectangle" : "dot.radiowaves.left.and.right") + .font(.system(size: 50)) + .foregroundColor(.secondary) + + Text(selectedTab == .sermons ? "No sermons found" : "No live archives available") + .font(.headline) + .foregroundColor(.secondary) + + if !searchText.isEmpty { + Text("Try adjusting your search terms") + .font(.subheadline) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + } + } + .padding() + Spacer() + } else { + ScrollView { + LazyVStack(spacing: 16) { + // Live stream card - only show when stream is live + if dataService.isStreamLive { + LiveStreamCard() + .padding(.horizontal, 20) + } + + ForEach(filteredContent, id: \.id) { sermon in + SermonCard(sermon: sermon, style: .watch) + } + } + .padding(.horizontal, 20) + .padding(.bottom, 100) + } + .refreshable { + switch selectedTab { + case .sermons: + await dataService.loadAllSermons() + case .liveArchives: + await dataService.loadAllLivestreamArchives() + } + // Always refresh stream status when pulling to refresh + await dataService.loadStreamStatus() + } + } + } + .navigationTitle("Watch") + .task { + // Load both sermons, live archives, and stream status on initial load + await dataService.loadAllSermons() + await dataService.loadAllLivestreamArchives() + await dataService.loadStreamStatus() + } + + } +} \ No newline at end of file diff --git a/church_core.swift b/church_core.swift new file mode 100644 index 0000000..49a7622 --- /dev/null +++ b/church_core.swift @@ -0,0 +1,1102 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +// swiftlint:disable all +import Foundation + +// Depending on the consumer's build setup, the low-level FFI code +// might be in a separate module, or it might be compiled inline into +// this module. This is a bit of light hackery to work with both. +#if canImport(church_coreFFI) +import church_coreFFI +#endif + +fileprivate extension RustBuffer { + // Allocate a new buffer, copying the contents of a `UInt8` array. + init(bytes: [UInt8]) { + let rbuf = bytes.withUnsafeBufferPointer { ptr in + RustBuffer.from(ptr) + } + self.init(capacity: rbuf.capacity, len: rbuf.len, data: rbuf.data) + } + + static func empty() -> RustBuffer { + RustBuffer(capacity: 0, len:0, data: nil) + } + + static func from(_ ptr: UnsafeBufferPointer) -> RustBuffer { + try! rustCall { ffi_church_core_rustbuffer_from_bytes(ForeignBytes(bufferPointer: ptr), $0) } + } + + // Frees the buffer in place. + // The buffer must not be used after this is called. + func deallocate() { + try! rustCall { ffi_church_core_rustbuffer_free(self, $0) } + } +} + +fileprivate extension ForeignBytes { + init(bufferPointer: UnsafeBufferPointer) { + self.init(len: Int32(bufferPointer.count), data: bufferPointer.baseAddress) + } +} + +// For every type used in the interface, we provide helper methods for conveniently +// lifting and lowering that type from C-compatible data, and for reading and writing +// values of that type in a buffer. + +// Helper classes/extensions that don't change. +// Someday, this will be in a library of its own. + +fileprivate extension Data { + init(rustBuffer: RustBuffer) { + // TODO: This copies the buffer. Can we read directly from a + // Rust buffer? + self.init(bytes: rustBuffer.data!, count: Int(rustBuffer.len)) + } +} + +// Define reader functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. +// +// With external types, one swift source file needs to be able to call the read +// method on another source file's FfiConverter, but then what visibility +// should Reader have? +// - If Reader is fileprivate, then this means the read() must also +// be fileprivate, which doesn't work with external types. +// - If Reader is internal/public, we'll get compile errors since both source +// files will try define the same type. +// +// Instead, the read() method and these helper functions input a tuple of data + +fileprivate func createReader(data: Data) -> (data: Data, offset: Data.Index) { + (data: data, offset: 0) +} + +// Reads an integer at the current offset, in big-endian order, and advances +// the offset on success. Throws if reading the integer would move the +// offset past the end of the buffer. +fileprivate func readInt(_ reader: inout (data: Data, offset: Data.Index)) throws -> T { + let range = reader.offset...size + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + if T.self == UInt8.self { + let value = reader.data[reader.offset] + reader.offset += 1 + return value as! T + } + var value: T = 0 + let _ = withUnsafeMutableBytes(of: &value, { reader.data.copyBytes(to: $0, from: range)}) + reader.offset = range.upperBound + return value.bigEndian +} + +// Reads an arbitrary number of bytes, to be used to read +// raw bytes, this is useful when lifting strings +fileprivate func readBytes(_ reader: inout (data: Data, offset: Data.Index), count: Int) throws -> Array { + let range = reader.offset..<(reader.offset+count) + guard reader.data.count >= range.upperBound else { + throw UniffiInternalError.bufferOverflow + } + var value = [UInt8](repeating: 0, count: count) + value.withUnsafeMutableBufferPointer({ buffer in + reader.data.copyBytes(to: buffer, from: range) + }) + reader.offset = range.upperBound + return value +} + +// Reads a float at the current offset. +fileprivate func readFloat(_ reader: inout (data: Data, offset: Data.Index)) throws -> Float { + return Float(bitPattern: try readInt(&reader)) +} + +// Reads a float at the current offset. +fileprivate func readDouble(_ reader: inout (data: Data, offset: Data.Index)) throws -> Double { + return Double(bitPattern: try readInt(&reader)) +} + +// Indicates if the offset has reached the end of the buffer. +fileprivate func hasRemaining(_ reader: (data: Data, offset: Data.Index)) -> Bool { + return reader.offset < reader.data.count +} + +// Define writer functionality. Normally this would be defined in a class or +// struct, but we use standalone functions instead in order to make external +// types work. See the above discussion on Readers for details. + +fileprivate func createWriter() -> [UInt8] { + return [] +} + +fileprivate func writeBytes(_ writer: inout [UInt8], _ byteArr: S) where S: Sequence, S.Element == UInt8 { + writer.append(contentsOf: byteArr) +} + +// Writes an integer in big-endian order. +// +// Warning: make sure what you are trying to write +// is in the correct type! +fileprivate func writeInt(_ writer: inout [UInt8], _ value: T) { + var value = value.bigEndian + withUnsafeBytes(of: &value) { writer.append(contentsOf: $0) } +} + +fileprivate func writeFloat(_ writer: inout [UInt8], _ value: Float) { + writeInt(&writer, value.bitPattern) +} + +fileprivate func writeDouble(_ writer: inout [UInt8], _ value: Double) { + writeInt(&writer, value.bitPattern) +} + +// Protocol for types that transfer other types across the FFI. This is +// analogous to the Rust trait of the same name. +fileprivate protocol FfiConverter { + associatedtype FfiType + associatedtype SwiftType + + static func lift(_ value: FfiType) throws -> SwiftType + static func lower(_ value: SwiftType) -> FfiType + static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType + static func write(_ value: SwiftType, into buf: inout [UInt8]) +} + +// Types conforming to `Primitive` pass themselves directly over the FFI. +fileprivate protocol FfiConverterPrimitive: FfiConverter where FfiType == SwiftType { } + +extension FfiConverterPrimitive { + public static func lift(_ value: FfiType) throws -> SwiftType { + return value + } + + public static func lower(_ value: SwiftType) -> FfiType { + return value + } +} + +// Types conforming to `FfiConverterRustBuffer` lift and lower into a `RustBuffer`. +// Used for complex types where it's hard to write a custom lift/lower. +fileprivate protocol FfiConverterRustBuffer: FfiConverter where FfiType == RustBuffer {} + +extension FfiConverterRustBuffer { + public static func lift(_ buf: RustBuffer) throws -> SwiftType { + var reader = createReader(data: Data(rustBuffer: buf)) + let value = try read(from: &reader) + if hasRemaining(reader) { + throw UniffiInternalError.incompleteData + } + buf.deallocate() + return value + } + + public static func lower(_ value: SwiftType) -> RustBuffer { + var writer = createWriter() + write(value, into: &writer) + return RustBuffer(bytes: writer) + } +} +// An error type for FFI errors. These errors occur at the UniFFI level, not +// the library level. +fileprivate enum UniffiInternalError: LocalizedError { + case bufferOverflow + case incompleteData + case unexpectedOptionalTag + case unexpectedEnumCase + case unexpectedNullPointer + case unexpectedRustCallStatusCode + case unexpectedRustCallError + case unexpectedStaleHandle + case rustPanic(_ message: String) + + public var errorDescription: String? { + switch self { + case .bufferOverflow: return "Reading the requested value would read past the end of the buffer" + case .incompleteData: return "The buffer still has data after lifting its containing value" + case .unexpectedOptionalTag: return "Unexpected optional tag; should be 0 or 1" + case .unexpectedEnumCase: return "Raw enum value doesn't match any cases" + case .unexpectedNullPointer: return "Raw pointer value was null" + case .unexpectedRustCallStatusCode: return "Unexpected RustCallStatus code" + case .unexpectedRustCallError: return "CALL_ERROR but no errorClass specified" + case .unexpectedStaleHandle: return "The object in the handle map has been dropped already" + case let .rustPanic(message): return message + } + } +} + +fileprivate extension NSLock { + func withLock(f: () throws -> T) rethrows -> T { + self.lock() + defer { self.unlock() } + return try f() + } +} + +fileprivate let CALL_SUCCESS: Int8 = 0 +fileprivate let CALL_ERROR: Int8 = 1 +fileprivate let CALL_UNEXPECTED_ERROR: Int8 = 2 +fileprivate let CALL_CANCELLED: Int8 = 3 + +fileprivate extension RustCallStatus { + init() { + self.init( + code: CALL_SUCCESS, + errorBuf: RustBuffer.init( + capacity: 0, + len: 0, + data: nil + ) + ) + } +} + +private func rustCall(_ callback: (UnsafeMutablePointer) -> T) throws -> T { + let neverThrow: ((RustBuffer) throws -> Never)? = nil + return try makeRustCall(callback, errorHandler: neverThrow) +} + +private func rustCallWithError( + _ errorHandler: @escaping (RustBuffer) throws -> E, + _ callback: (UnsafeMutablePointer) -> T) throws -> T { + try makeRustCall(callback, errorHandler: errorHandler) +} + +private func makeRustCall( + _ callback: (UnsafeMutablePointer) -> T, + errorHandler: ((RustBuffer) throws -> E)? +) throws -> T { + uniffiEnsureInitialized() + var callStatus = RustCallStatus.init() + let returnedVal = callback(&callStatus) + try uniffiCheckCallStatus(callStatus: callStatus, errorHandler: errorHandler) + return returnedVal +} + +private func uniffiCheckCallStatus( + callStatus: RustCallStatus, + errorHandler: ((RustBuffer) throws -> E)? +) throws { + switch callStatus.code { + case CALL_SUCCESS: + return + + case CALL_ERROR: + if let errorHandler = errorHandler { + throw try errorHandler(callStatus.errorBuf) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.unexpectedRustCallError + } + + case CALL_UNEXPECTED_ERROR: + // When the rust code sees a panic, it tries to construct a RustBuffer + // with the message. But if that code panics, then it just sends back + // an empty buffer. + if callStatus.errorBuf.len > 0 { + throw UniffiInternalError.rustPanic(try FfiConverterString.lift(callStatus.errorBuf)) + } else { + callStatus.errorBuf.deallocate() + throw UniffiInternalError.rustPanic("Rust panic") + } + + case CALL_CANCELLED: + fatalError("Cancellation not supported yet") + + default: + throw UniffiInternalError.unexpectedRustCallStatusCode + } +} + +private func uniffiTraitInterfaceCall( + callStatus: UnsafeMutablePointer, + makeCall: () throws -> T, + writeReturn: (T) -> () +) { + do { + try writeReturn(makeCall()) + } catch let error { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error)) + } +} + +private func uniffiTraitInterfaceCallWithError( + callStatus: UnsafeMutablePointer, + makeCall: () throws -> T, + writeReturn: (T) -> (), + lowerError: (E) -> RustBuffer +) { + do { + try writeReturn(makeCall()) + } catch let error as E { + callStatus.pointee.code = CALL_ERROR + callStatus.pointee.errorBuf = lowerError(error) + } catch { + callStatus.pointee.code = CALL_UNEXPECTED_ERROR + callStatus.pointee.errorBuf = FfiConverterString.lower(String(describing: error)) + } +} +fileprivate class UniffiHandleMap { + private var map: [UInt64: T] = [:] + private let lock = NSLock() + private var currentHandle: UInt64 = 1 + + func insert(obj: T) -> UInt64 { + lock.withLock { + let handle = currentHandle + currentHandle += 1 + map[handle] = obj + return handle + } + } + + func get(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map[handle] else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + @discardableResult + func remove(handle: UInt64) throws -> T { + try lock.withLock { + guard let obj = map.removeValue(forKey: handle) else { + throw UniffiInternalError.unexpectedStaleHandle + } + return obj + } + } + + var count: Int { + get { + map.count + } + } +} + + +// Public interface members begin here. + + +fileprivate struct FfiConverterDouble: FfiConverterPrimitive { + typealias FfiType = Double + typealias SwiftType = Double + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Double { + return try lift(readDouble(&buf)) + } + + public static func write(_ value: Double, into buf: inout [UInt8]) { + writeDouble(&buf, lower(value)) + } +} + +fileprivate struct FfiConverterBool : FfiConverter { + typealias FfiType = Int8 + typealias SwiftType = Bool + + public static func lift(_ value: Int8) throws -> Bool { + return value != 0 + } + + public static func lower(_ value: Bool) -> Int8 { + return value ? 1 : 0 + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Bool { + return try lift(readInt(&buf)) + } + + public static func write(_ value: Bool, into buf: inout [UInt8]) { + writeInt(&buf, lower(value)) + } +} + +fileprivate struct FfiConverterString: FfiConverter { + typealias SwiftType = String + typealias FfiType = RustBuffer + + public static func lift(_ value: RustBuffer) throws -> String { + defer { + value.deallocate() + } + if value.data == nil { + return String() + } + let bytes = UnsafeBufferPointer(start: value.data!, count: Int(value.len)) + return String(bytes: bytes, encoding: String.Encoding.utf8)! + } + + public static func lower(_ value: String) -> RustBuffer { + return value.utf8CString.withUnsafeBufferPointer { ptr in + // The swift string gives us int8_t, we want uint8_t. + ptr.withMemoryRebound(to: UInt8.self) { ptr in + // The swift string gives us a trailing null byte, we don't want it. + let buf = UnsafeBufferPointer(rebasing: ptr.prefix(upTo: ptr.count - 1)) + return RustBuffer.from(buf) + } + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> String { + let len: Int32 = try readInt(&buf) + return String(bytes: try readBytes(&buf, count: Int(len)), encoding: String.Encoding.utf8)! + } + + public static func write(_ value: String, into buf: inout [UInt8]) { + let len = Int32(value.utf8.count) + writeInt(&buf, len) + writeBytes(&buf, value.utf8) + } +} + +fileprivate struct FfiConverterOptionString: FfiConverterRustBuffer { + typealias SwiftType = String? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterString.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterString.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + +fileprivate struct FfiConverterSequenceDouble: FfiConverterRustBuffer { + typealias SwiftType = [Double] + + public static func write(_ value: [Double], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterDouble.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [Double] { + let len: Int32 = try readInt(&buf) + var seq = [Double]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterDouble.read(from: &buf)) + } + return seq + } +} +public func createCalendarEventData(eventJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_create_calendar_event_data( + FfiConverterString.lower(eventJson),$0 + ) +}) +} +public func createSermonShareItemsJson(title: String, speaker: String, videoUrl: String?, audioUrl: String?) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_create_sermon_share_items_json( + FfiConverterString.lower(title), + FfiConverterString.lower(speaker), + FfiConverterOptionString.lower(videoUrl), + FfiConverterOptionString.lower(audioUrl),$0 + ) +}) +} +public func deviceSupportsAv1() -> Bool { + return try! FfiConverterBool.lift(try! rustCall() { + uniffi_church_core_fn_func_device_supports_av1($0 + ) +}) +} +public func extractFullVerseText(versesJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_extract_full_verse_text( + FfiConverterString.lower(versesJson),$0 + ) +}) +} +public func extractScriptureReferencesString(scriptureText: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_extract_scripture_references_string( + FfiConverterString.lower(scriptureText),$0 + ) +}) +} +public func extractStreamUrlFromStatus(statusJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_extract_stream_url_from_status( + FfiConverterString.lower(statusJson),$0 + ) +}) +} +public func fetchBibleVerseJson(query: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_bible_verse_json( + FfiConverterString.lower(query),$0 + ) +}) +} +public func fetchBulletinsJson() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_bulletins_json($0 + ) +}) +} +public func fetchCachedImageBase64(url: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_cached_image_base64( + FfiConverterString.lower(url),$0 + ) +}) +} +public func fetchConfigJson() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_config_json($0 + ) +}) +} +public func fetchCurrentBulletinJson() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_current_bulletin_json($0 + ) +}) +} +public func fetchEventsJson() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_events_json($0 + ) +}) +} +public func fetchFeaturedEventsJson() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_featured_events_json($0 + ) +}) +} +public func fetchLiveStreamJson() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_live_stream_json($0 + ) +}) +} +public func fetchLivestreamArchiveJson() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_livestream_archive_json($0 + ) +}) +} +public func fetchRandomBibleVerseJson() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_random_bible_verse_json($0 + ) +}) +} +public func fetchScriptureVersesForSermonJson(sermonId: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_scripture_verses_for_sermon_json( + FfiConverterString.lower(sermonId),$0 + ) +}) +} +public func fetchSermonsJson() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_sermons_json($0 + ) +}) +} +public func fetchStreamStatusJson() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_fetch_stream_status_json($0 + ) +}) +} +public func filterSermonsByMediaType(sermonsJson: String, mediaTypeStr: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_filter_sermons_by_media_type( + FfiConverterString.lower(sermonsJson), + FfiConverterString.lower(mediaTypeStr),$0 + ) +}) +} +public func formatEventForDisplayJson(eventJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_format_event_for_display_json( + FfiConverterString.lower(eventJson),$0 + ) +}) +} +public func formatScriptureTextJson(scriptureText: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_format_scripture_text_json( + FfiConverterString.lower(scriptureText),$0 + ) +}) +} +public func formatTimeRangeString(startTime: String, endTime: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_format_time_range_string( + FfiConverterString.lower(startTime), + FfiConverterString.lower(endTime),$0 + ) +}) +} +public func generateHomeFeedJson(eventsJson: String, sermonsJson: String, bulletinsJson: String, verseJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_generate_home_feed_json( + FfiConverterString.lower(eventsJson), + FfiConverterString.lower(sermonsJson), + FfiConverterString.lower(bulletinsJson), + FfiConverterString.lower(verseJson),$0 + ) +}) +} +public func generateVerseDescription(versesJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_generate_verse_description( + FfiConverterString.lower(versesJson),$0 + ) +}) +} +public func getAboutText() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_about_text($0 + ) +}) +} +public func getAv1StreamingUrl(mediaId: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_av1_streaming_url( + FfiConverterString.lower(mediaId),$0 + ) +}) +} +public func getBrandColor() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_brand_color($0 + ) +}) +} +public func getChurchAddress() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_church_address($0 + ) +}) +} +public func getChurchName() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_church_name($0 + ) +}) +} +public func getContactEmail() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_contact_email($0 + ) +}) +} +public func getContactPhone() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_contact_phone($0 + ) +}) +} +public func getCoordinates() -> [Double] { + return try! FfiConverterSequenceDouble.lift(try! rustCall() { + uniffi_church_core_fn_func_get_coordinates($0 + ) +}) +} +public func getDonationUrl() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_donation_url($0 + ) +}) +} +public func getFacebookUrl() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_facebook_url($0 + ) +}) +} +public func getHlsStreamingUrl(mediaId: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_hls_streaming_url( + FfiConverterString.lower(mediaId),$0 + ) +}) +} +public func getInstagramUrl() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_instagram_url($0 + ) +}) +} +public func getLivestreamUrl() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_livestream_url($0 + ) +}) +} +public func getMediaTypeDisplayName(mediaTypeStr: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_media_type_display_name( + FfiConverterString.lower(mediaTypeStr),$0 + ) +}) +} +public func getMediaTypeIcon(mediaTypeStr: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_media_type_icon( + FfiConverterString.lower(mediaTypeStr),$0 + ) +}) +} +public func getMissionStatement() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_mission_statement($0 + ) +}) +} +public func getOptimalStreamingUrl(mediaId: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_optimal_streaming_url( + FfiConverterString.lower(mediaId),$0 + ) +}) +} +public func getStreamLiveStatus() -> Bool { + return try! FfiConverterBool.lift(try! rustCall() { + uniffi_church_core_fn_func_get_stream_live_status($0 + ) +}) +} +public func getWebsiteUrl() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_website_url($0 + ) +}) +} +public func getYoutubeUrl() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_get_youtube_url($0 + ) +}) +} +public func isMultiDayEventCheck(date: String) -> Bool { + return try! FfiConverterBool.lift(try! rustCall() { + uniffi_church_core_fn_func_is_multi_day_event_check( + FfiConverterString.lower(date),$0 + ) +}) +} +public func parseBibleVerseFromJson(verseJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_parse_bible_verse_from_json( + FfiConverterString.lower(verseJson),$0 + ) +}) +} +public func parseBulletinsFromJson(bulletinsJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_parse_bulletins_from_json( + FfiConverterString.lower(bulletinsJson),$0 + ) +}) +} +public func parseCalendarEventData(calendarJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_parse_calendar_event_data( + FfiConverterString.lower(calendarJson),$0 + ) +}) +} +public func parseContactResultFromJson(resultJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_parse_contact_result_from_json( + FfiConverterString.lower(resultJson),$0 + ) +}) +} +public func parseEventsFromJson(eventsJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_parse_events_from_json( + FfiConverterString.lower(eventsJson),$0 + ) +}) +} +public func parseSermonsFromJson(sermonsJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_parse_sermons_from_json( + FfiConverterString.lower(sermonsJson),$0 + ) +}) +} +public func submitContactJson(name: String, email: String, message: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_submit_contact_json( + FfiConverterString.lower(name), + FfiConverterString.lower(email), + FfiConverterString.lower(message),$0 + ) +}) +} +public func submitContactV2Json(name: String, email: String, subject: String, message: String, phone: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_submit_contact_v2_json( + FfiConverterString.lower(name), + FfiConverterString.lower(email), + FfiConverterString.lower(subject), + FfiConverterString.lower(message), + FfiConverterString.lower(phone),$0 + ) +}) +} +public func submitContactV2JsonLegacy(firstName: String, lastName: String, email: String, subject: String, message: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_submit_contact_v2_json_legacy( + FfiConverterString.lower(firstName), + FfiConverterString.lower(lastName), + FfiConverterString.lower(email), + FfiConverterString.lower(subject), + FfiConverterString.lower(message),$0 + ) +}) +} +public func validateContactFormJson(formJson: String) -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_church_core_fn_func_validate_contact_form_json( + FfiConverterString.lower(formJson),$0 + ) +}) +} +public func validateEmailAddress(email: String) -> Bool { + return try! FfiConverterBool.lift(try! rustCall() { + uniffi_church_core_fn_func_validate_email_address( + FfiConverterString.lower(email),$0 + ) +}) +} +public func validatePhoneNumber(phone: String) -> Bool { + return try! FfiConverterBool.lift(try! rustCall() { + uniffi_church_core_fn_func_validate_phone_number( + FfiConverterString.lower(phone),$0 + ) +}) +} + +private enum InitializationResult { + case ok + case contractVersionMismatch + case apiChecksumMismatch +} +// Use a global variable to perform the versioning checks. Swift ensures that +// the code inside is only computed once. +private var initializationResult: InitializationResult = { + // Get the bindings contract version from our ComponentInterface + let bindings_contract_version = 26 + // Get the scaffolding contract version by calling the into the dylib + let scaffolding_contract_version = ffi_church_core_uniffi_contract_version() + if bindings_contract_version != scaffolding_contract_version { + return InitializationResult.contractVersionMismatch + } + if (uniffi_church_core_checksum_func_create_calendar_event_data() != 18038) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_create_sermon_share_items_json() != 7165) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_device_supports_av1() != 2798) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_extract_full_verse_text() != 33299) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_extract_scripture_references_string() != 54242) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_extract_stream_url_from_status() != 7333) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_bible_verse_json() != 62434) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_bulletins_json() != 51697) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_cached_image_base64() != 56508) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_config_json() != 22720) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_current_bulletin_json() != 15976) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_events_json() != 55699) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_featured_events_json() != 40496) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_live_stream_json() != 8362) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_livestream_archive_json() != 39665) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_random_bible_verse_json() != 24962) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_scripture_verses_for_sermon_json() != 54526) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_sermons_json() != 35127) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_fetch_stream_status_json() != 11864) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_filter_sermons_by_media_type() != 55463) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_format_event_for_display_json() != 9802) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_format_scripture_text_json() != 33940) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_format_time_range_string() != 30520) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_generate_home_feed_json() != 33935) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_generate_verse_description() != 57052) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_about_text() != 63404) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_av1_streaming_url() != 15580) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_brand_color() != 38100) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_church_address() != 9838) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_church_name() != 51038) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_contact_email() != 3208) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_contact_phone() != 48541) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_coordinates() != 64388) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_donation_url() != 24711) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_facebook_url() != 16208) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_hls_streaming_url() != 7230) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_instagram_url() != 24193) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_livestream_url() != 60946) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_media_type_display_name() != 34144) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_media_type_icon() != 5231) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_mission_statement() != 37182) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_optimal_streaming_url() != 37505) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_stream_live_status() != 5029) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_website_url() != 56118) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_get_youtube_url() != 37371) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_is_multi_day_event_check() != 59258) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_parse_bible_verse_from_json() != 56853) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_parse_bulletins_from_json() != 49597) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_parse_calendar_event_data() != 53928) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_parse_contact_result_from_json() != 10921) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_parse_events_from_json() != 6684) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_parse_sermons_from_json() != 46352) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_submit_contact_json() != 14960) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_submit_contact_v2_json() != 24485) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_submit_contact_v2_json_legacy() != 19210) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_validate_contact_form_json() != 44651) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_validate_email_address() != 14406) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_church_core_checksum_func_validate_phone_number() != 58095) { + return InitializationResult.apiChecksumMismatch + } + + return InitializationResult.ok +}() + +private func uniffiEnsureInitialized() { + switch initializationResult { + case .ok: + break + case .contractVersionMismatch: + fatalError("UniFFI contract version mismatch: try cleaning and rebuilding your project") + case .apiChecksumMismatch: + fatalError("UniFFI API checksum mismatch: try cleaning and rebuilding your project") + } +} + +// swiftlint:enable all \ No newline at end of file diff --git a/church_coreFFI.h b/church_coreFFI.h new file mode 100644 index 0000000..7ef029c --- /dev/null +++ b/church_coreFFI.h @@ -0,0 +1,1204 @@ +// This file was autogenerated by some hot garbage in the `uniffi` crate. +// Trust me, you don't want to mess with it! + +#pragma once + +#include +#include +#include + +// The following structs are used to implement the lowest level +// of the FFI, and thus useful to multiple uniffied crates. +// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H. +#ifdef UNIFFI_SHARED_H + // We also try to prevent mixing versions of shared uniffi header structs. + // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V4 + #ifndef UNIFFI_SHARED_HEADER_V4 + #error Combining helper code from multiple versions of uniffi is not supported + #endif // ndef UNIFFI_SHARED_HEADER_V4 +#else +#define UNIFFI_SHARED_H +#define UNIFFI_SHARED_HEADER_V4 +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ + +typedef struct RustBuffer +{ + uint64_t capacity; + uint64_t len; + uint8_t *_Nullable data; +} RustBuffer; + +typedef struct ForeignBytes +{ + int32_t len; + const uint8_t *_Nullable data; +} ForeignBytes; + +// Error definitions +typedef struct RustCallStatus { + int8_t code; + RustBuffer errorBuf; +} RustCallStatus; + +// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️ +// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V4 in this file. ⚠️ +#endif // def UNIFFI_SHARED_H +#ifndef UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +#define UNIFFI_FFIDEF_RUST_FUTURE_CONTINUATION_CALLBACK +typedef void (*UniffiRustFutureContinuationCallback)(uint64_t, int8_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_FREE +typedef void (*UniffiForeignFutureFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +#define UNIFFI_FFIDEF_CALLBACK_INTERFACE_FREE +typedef void (*UniffiCallbackInterfaceFree)(uint64_t + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE +#define UNIFFI_FFIDEF_FOREIGN_FUTURE +typedef struct UniffiForeignFuture { + uint64_t handle; + UniffiForeignFutureFree _Nonnull free; +} UniffiForeignFuture; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U8 +typedef struct UniffiForeignFutureStructU8 { + uint8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U8 +typedef void (*UniffiForeignFutureCompleteU8)(uint64_t, UniffiForeignFutureStructU8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I8 +typedef struct UniffiForeignFutureStructI8 { + int8_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI8; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I8 +typedef void (*UniffiForeignFutureCompleteI8)(uint64_t, UniffiForeignFutureStructI8 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U16 +typedef struct UniffiForeignFutureStructU16 { + uint16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U16 +typedef void (*UniffiForeignFutureCompleteU16)(uint64_t, UniffiForeignFutureStructU16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I16 +typedef struct UniffiForeignFutureStructI16 { + int16_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI16; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I16 +typedef void (*UniffiForeignFutureCompleteI16)(uint64_t, UniffiForeignFutureStructI16 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U32 +typedef struct UniffiForeignFutureStructU32 { + uint32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U32 +typedef void (*UniffiForeignFutureCompleteU32)(uint64_t, UniffiForeignFutureStructU32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I32 +typedef struct UniffiForeignFutureStructI32 { + int32_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I32 +typedef void (*UniffiForeignFutureCompleteI32)(uint64_t, UniffiForeignFutureStructI32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_U64 +typedef struct UniffiForeignFutureStructU64 { + uint64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructU64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_U64 +typedef void (*UniffiForeignFutureCompleteU64)(uint64_t, UniffiForeignFutureStructU64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_I64 +typedef struct UniffiForeignFutureStructI64 { + int64_t returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructI64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_I64 +typedef void (*UniffiForeignFutureCompleteI64)(uint64_t, UniffiForeignFutureStructI64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F32 +typedef struct UniffiForeignFutureStructF32 { + float returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF32; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F32 +typedef void (*UniffiForeignFutureCompleteF32)(uint64_t, UniffiForeignFutureStructF32 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_F64 +typedef struct UniffiForeignFutureStructF64 { + double returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructF64; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_F64 +typedef void (*UniffiForeignFutureCompleteF64)(uint64_t, UniffiForeignFutureStructF64 + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_POINTER +typedef struct UniffiForeignFutureStructPointer { + void*_Nonnull returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructPointer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_POINTER +typedef void (*UniffiForeignFutureCompletePointer)(uint64_t, UniffiForeignFutureStructPointer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_RUST_BUFFER +typedef struct UniffiForeignFutureStructRustBuffer { + RustBuffer returnValue; + RustCallStatus callStatus; +} UniffiForeignFutureStructRustBuffer; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_RUST_BUFFER +typedef void (*UniffiForeignFutureCompleteRustBuffer)(uint64_t, UniffiForeignFutureStructRustBuffer + ); + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_STRUCT_VOID +typedef struct UniffiForeignFutureStructVoid { + RustCallStatus callStatus; +} UniffiForeignFutureStructVoid; + +#endif +#ifndef UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FOREIGN_FUTURE_COMPLETE_VOID +typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid + ); + +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_CALENDAR_EVENT_DATA +RustBuffer uniffi_church_core_fn_func_create_calendar_event_data(RustBuffer event_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +RustBuffer uniffi_church_core_fn_func_create_sermon_share_items_json(RustBuffer title, RustBuffer speaker, RustBuffer video_url, RustBuffer audio_url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_DEVICE_SUPPORTS_AV1 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_DEVICE_SUPPORTS_AV1 +int8_t uniffi_church_core_fn_func_device_supports_av1(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_FULL_VERSE_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_FULL_VERSE_TEXT +RustBuffer uniffi_church_core_fn_func_extract_full_verse_text(RustBuffer verses_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +RustBuffer uniffi_church_core_fn_func_extract_scripture_references_string(RustBuffer scripture_text, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +RustBuffer uniffi_church_core_fn_func_extract_stream_url_from_status(RustBuffer status_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BIBLE_VERSE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_bible_verse_json(RustBuffer query, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BULLETINS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_BULLETINS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_bulletins_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CACHED_IMAGE_BASE64 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CACHED_IMAGE_BASE64 +RustBuffer uniffi_church_core_fn_func_fetch_cached_image_base64(RustBuffer url, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CONFIG_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CONFIG_JSON +RustBuffer uniffi_church_core_fn_func_fetch_config_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CURRENT_BULLETIN_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_CURRENT_BULLETIN_JSON +RustBuffer uniffi_church_core_fn_func_fetch_current_bulletin_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_EVENTS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_events_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_FEATURED_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_FEATURED_EVENTS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_featured_events_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVE_STREAM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVE_STREAM_JSON +RustBuffer uniffi_church_core_fn_func_fetch_live_stream_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_livestream_archive_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +RustBuffer uniffi_church_core_fn_func_fetch_random_bible_verse_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +RustBuffer uniffi_church_core_fn_func_fetch_scripture_verses_for_sermon_json(RustBuffer sermon_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SERMONS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_SERMONS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_sermons_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_STREAM_STATUS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FETCH_STREAM_STATUS_JSON +RustBuffer uniffi_church_core_fn_func_fetch_stream_status_json(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +RustBuffer uniffi_church_core_fn_func_filter_sermons_by_media_type(RustBuffer sermons_json, RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +RustBuffer uniffi_church_core_fn_func_format_event_for_display_json(RustBuffer event_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +RustBuffer uniffi_church_core_fn_func_format_scripture_text_json(RustBuffer scripture_text, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_TIME_RANGE_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_FORMAT_TIME_RANGE_STRING +RustBuffer uniffi_church_core_fn_func_format_time_range_string(RustBuffer start_time, RustBuffer end_time, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_HOME_FEED_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_HOME_FEED_JSON +RustBuffer uniffi_church_core_fn_func_generate_home_feed_json(RustBuffer events_json, RustBuffer sermons_json, RustBuffer bulletins_json, RustBuffer verse_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_VERSE_DESCRIPTION +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GENERATE_VERSE_DESCRIPTION +RustBuffer uniffi_church_core_fn_func_generate_verse_description(RustBuffer verses_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_ABOUT_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_ABOUT_TEXT +RustBuffer uniffi_church_core_fn_func_get_about_text(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_AV1_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_AV1_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_av1_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_BRAND_COLOR +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_BRAND_COLOR +RustBuffer uniffi_church_core_fn_func_get_brand_color(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_ADDRESS +RustBuffer uniffi_church_core_fn_func_get_church_address(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CHURCH_NAME +RustBuffer uniffi_church_core_fn_func_get_church_name(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_EMAIL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_EMAIL +RustBuffer uniffi_church_core_fn_func_get_contact_email(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_PHONE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_CONTACT_PHONE +RustBuffer uniffi_church_core_fn_func_get_contact_phone(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_COORDINATES +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_COORDINATES +RustBuffer uniffi_church_core_fn_func_get_coordinates(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_DONATION_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_DONATION_URL +RustBuffer uniffi_church_core_fn_func_get_donation_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_FACEBOOK_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_FACEBOOK_URL +RustBuffer uniffi_church_core_fn_func_get_facebook_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_HLS_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_HLS_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_hls_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_INSTAGRAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_INSTAGRAM_URL +RustBuffer uniffi_church_core_fn_func_get_instagram_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_LIVESTREAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_LIVESTREAM_URL +RustBuffer uniffi_church_core_fn_func_get_livestream_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +RustBuffer uniffi_church_core_fn_func_get_media_type_display_name(RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_ICON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MEDIA_TYPE_ICON +RustBuffer uniffi_church_core_fn_func_get_media_type_icon(RustBuffer media_type_str, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MISSION_STATEMENT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_MISSION_STATEMENT +RustBuffer uniffi_church_core_fn_func_get_mission_statement(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_OPTIMAL_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_OPTIMAL_STREAMING_URL +RustBuffer uniffi_church_core_fn_func_get_optimal_streaming_url(RustBuffer media_id, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_STREAM_LIVE_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_STREAM_LIVE_STATUS +int8_t uniffi_church_core_fn_func_get_stream_live_status(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_WEBSITE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_WEBSITE_URL +RustBuffer uniffi_church_core_fn_func_get_website_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_YOUTUBE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_GET_YOUTUBE_URL +RustBuffer uniffi_church_core_fn_func_get_youtube_url(RustCallStatus *_Nonnull out_status + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_IS_MULTI_DAY_EVENT_CHECK +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_IS_MULTI_DAY_EVENT_CHECK +int8_t uniffi_church_core_fn_func_is_multi_day_event_check(RustBuffer date, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_bible_verse_from_json(RustBuffer verse_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BULLETINS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_BULLETINS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_bulletins_from_json(RustBuffer bulletins_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CALENDAR_EVENT_DATA +RustBuffer uniffi_church_core_fn_func_parse_calendar_event_data(RustBuffer calendar_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_contact_result_from_json(RustBuffer result_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_EVENTS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_EVENTS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_events_from_json(RustBuffer events_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_SERMONS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_PARSE_SERMONS_FROM_JSON +RustBuffer uniffi_church_core_fn_func_parse_sermons_from_json(RustBuffer sermons_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_JSON +RustBuffer uniffi_church_core_fn_func_submit_contact_json(RustBuffer name, RustBuffer email, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON +RustBuffer uniffi_church_core_fn_func_submit_contact_v2_json(RustBuffer name, RustBuffer email, RustBuffer subject, RustBuffer message, RustBuffer phone, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +RustBuffer uniffi_church_core_fn_func_submit_contact_v2_json_legacy(RustBuffer first_name, RustBuffer last_name, RustBuffer email, RustBuffer subject, RustBuffer message, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_CONTACT_FORM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_CONTACT_FORM_JSON +RustBuffer uniffi_church_core_fn_func_validate_contact_form_json(RustBuffer form_json, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_EMAIL_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_EMAIL_ADDRESS +int8_t uniffi_church_core_fn_func_validate_email_address(RustBuffer email, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_PHONE_NUMBER +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_FN_FUNC_VALIDATE_PHONE_NUMBER +int8_t uniffi_church_core_fn_func_validate_phone_number(RustBuffer phone, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_ALLOC +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_ALLOC +RustBuffer ffi_church_core_rustbuffer_alloc(uint64_t size, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FROM_BYTES +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FROM_BYTES +RustBuffer ffi_church_core_rustbuffer_from_bytes(ForeignBytes bytes, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FREE +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_FREE +void ffi_church_core_rustbuffer_free(RustBuffer buf, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_RESERVE +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUSTBUFFER_RESERVE +RustBuffer ffi_church_core_rustbuffer_reserve(RustBuffer buf, uint64_t additional, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U8 +void ffi_church_core_rust_future_poll_u8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U8 +void ffi_church_core_rust_future_cancel_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U8 +void ffi_church_core_rust_future_free_u8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U8 +uint8_t ffi_church_core_rust_future_complete_u8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I8 +void ffi_church_core_rust_future_poll_i8(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I8 +void ffi_church_core_rust_future_cancel_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I8 +void ffi_church_core_rust_future_free_i8(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I8 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I8 +int8_t ffi_church_core_rust_future_complete_i8(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U16 +void ffi_church_core_rust_future_poll_u16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U16 +void ffi_church_core_rust_future_cancel_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U16 +void ffi_church_core_rust_future_free_u16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U16 +uint16_t ffi_church_core_rust_future_complete_u16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I16 +void ffi_church_core_rust_future_poll_i16(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I16 +void ffi_church_core_rust_future_cancel_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I16 +void ffi_church_core_rust_future_free_i16(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I16 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I16 +int16_t ffi_church_core_rust_future_complete_i16(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U32 +void ffi_church_core_rust_future_poll_u32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U32 +void ffi_church_core_rust_future_cancel_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U32 +void ffi_church_core_rust_future_free_u32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U32 +uint32_t ffi_church_core_rust_future_complete_u32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I32 +void ffi_church_core_rust_future_poll_i32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I32 +void ffi_church_core_rust_future_cancel_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I32 +void ffi_church_core_rust_future_free_i32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I32 +int32_t ffi_church_core_rust_future_complete_i32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_U64 +void ffi_church_core_rust_future_poll_u64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_U64 +void ffi_church_core_rust_future_cancel_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_U64 +void ffi_church_core_rust_future_free_u64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_U64 +uint64_t ffi_church_core_rust_future_complete_u64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_I64 +void ffi_church_core_rust_future_poll_i64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_I64 +void ffi_church_core_rust_future_cancel_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_I64 +void ffi_church_core_rust_future_free_i64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_I64 +int64_t ffi_church_core_rust_future_complete_i64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F32 +void ffi_church_core_rust_future_poll_f32(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F32 +void ffi_church_core_rust_future_cancel_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F32 +void ffi_church_core_rust_future_free_f32(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F32 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F32 +float ffi_church_core_rust_future_complete_f32(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_F64 +void ffi_church_core_rust_future_poll_f64(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_F64 +void ffi_church_core_rust_future_cancel_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_F64 +void ffi_church_core_rust_future_free_f64(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F64 +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_F64 +double ffi_church_core_rust_future_complete_f64(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_POINTER +void ffi_church_core_rust_future_poll_pointer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_POINTER +void ffi_church_core_rust_future_cancel_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_POINTER +void ffi_church_core_rust_future_free_pointer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_POINTER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_POINTER +void*_Nonnull ffi_church_core_rust_future_complete_pointer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_RUST_BUFFER +void ffi_church_core_rust_future_poll_rust_buffer(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_RUST_BUFFER +void ffi_church_core_rust_future_cancel_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_RUST_BUFFER +void ffi_church_core_rust_future_free_rust_buffer(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_RUST_BUFFER +RustBuffer ffi_church_core_rust_future_complete_rust_buffer(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_POLL_VOID +void ffi_church_core_rust_future_poll_void(uint64_t handle, UniffiRustFutureContinuationCallback _Nonnull callback, uint64_t callback_data +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_CANCEL_VOID +void ffi_church_core_rust_future_cancel_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_FREE_VOID +void ffi_church_core_rust_future_free_void(uint64_t handle +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_VOID +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_RUST_FUTURE_COMPLETE_VOID +void ffi_church_core_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_CALENDAR_EVENT_DATA +uint16_t uniffi_church_core_checksum_func_create_calendar_event_data(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_CREATE_SERMON_SHARE_ITEMS_JSON +uint16_t uniffi_church_core_checksum_func_create_sermon_share_items_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_DEVICE_SUPPORTS_AV1 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_DEVICE_SUPPORTS_AV1 +uint16_t uniffi_church_core_checksum_func_device_supports_av1(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_FULL_VERSE_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_FULL_VERSE_TEXT +uint16_t uniffi_church_core_checksum_func_extract_full_verse_text(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_SCRIPTURE_REFERENCES_STRING +uint16_t uniffi_church_core_checksum_func_extract_scripture_references_string(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_EXTRACT_STREAM_URL_FROM_STATUS +uint16_t uniffi_church_core_checksum_func_extract_stream_url_from_status(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BIBLE_VERSE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_bible_verse_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BULLETINS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_BULLETINS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_bulletins_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CACHED_IMAGE_BASE64 +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CACHED_IMAGE_BASE64 +uint16_t uniffi_church_core_checksum_func_fetch_cached_image_base64(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CONFIG_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CONFIG_JSON +uint16_t uniffi_church_core_checksum_func_fetch_config_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CURRENT_BULLETIN_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_CURRENT_BULLETIN_JSON +uint16_t uniffi_church_core_checksum_func_fetch_current_bulletin_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_EVENTS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_events_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_FEATURED_EVENTS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_FEATURED_EVENTS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_featured_events_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVE_STREAM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVE_STREAM_JSON +uint16_t uniffi_church_core_checksum_func_fetch_live_stream_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_LIVESTREAM_ARCHIVE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_livestream_archive_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_RANDOM_BIBLE_VERSE_JSON +uint16_t uniffi_church_core_checksum_func_fetch_random_bible_verse_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SCRIPTURE_VERSES_FOR_SERMON_JSON +uint16_t uniffi_church_core_checksum_func_fetch_scripture_verses_for_sermon_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SERMONS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_SERMONS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_sermons_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_STREAM_STATUS_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FETCH_STREAM_STATUS_JSON +uint16_t uniffi_church_core_checksum_func_fetch_stream_status_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FILTER_SERMONS_BY_MEDIA_TYPE +uint16_t uniffi_church_core_checksum_func_filter_sermons_by_media_type(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_EVENT_FOR_DISPLAY_JSON +uint16_t uniffi_church_core_checksum_func_format_event_for_display_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_SCRIPTURE_TEXT_JSON +uint16_t uniffi_church_core_checksum_func_format_scripture_text_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_TIME_RANGE_STRING +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_FORMAT_TIME_RANGE_STRING +uint16_t uniffi_church_core_checksum_func_format_time_range_string(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_HOME_FEED_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_HOME_FEED_JSON +uint16_t uniffi_church_core_checksum_func_generate_home_feed_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_VERSE_DESCRIPTION +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GENERATE_VERSE_DESCRIPTION +uint16_t uniffi_church_core_checksum_func_generate_verse_description(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_ABOUT_TEXT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_ABOUT_TEXT +uint16_t uniffi_church_core_checksum_func_get_about_text(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_AV1_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_AV1_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_av1_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_BRAND_COLOR +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_BRAND_COLOR +uint16_t uniffi_church_core_checksum_func_get_brand_color(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_ADDRESS +uint16_t uniffi_church_core_checksum_func_get_church_address(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CHURCH_NAME +uint16_t uniffi_church_core_checksum_func_get_church_name(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_EMAIL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_EMAIL +uint16_t uniffi_church_core_checksum_func_get_contact_email(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_PHONE +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_CONTACT_PHONE +uint16_t uniffi_church_core_checksum_func_get_contact_phone(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_COORDINATES +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_COORDINATES +uint16_t uniffi_church_core_checksum_func_get_coordinates(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_DONATION_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_DONATION_URL +uint16_t uniffi_church_core_checksum_func_get_donation_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_FACEBOOK_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_FACEBOOK_URL +uint16_t uniffi_church_core_checksum_func_get_facebook_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_HLS_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_HLS_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_hls_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_INSTAGRAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_INSTAGRAM_URL +uint16_t uniffi_church_core_checksum_func_get_instagram_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_LIVESTREAM_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_LIVESTREAM_URL +uint16_t uniffi_church_core_checksum_func_get_livestream_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_DISPLAY_NAME +uint16_t uniffi_church_core_checksum_func_get_media_type_display_name(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_ICON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MEDIA_TYPE_ICON +uint16_t uniffi_church_core_checksum_func_get_media_type_icon(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MISSION_STATEMENT +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_MISSION_STATEMENT +uint16_t uniffi_church_core_checksum_func_get_mission_statement(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_OPTIMAL_STREAMING_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_OPTIMAL_STREAMING_URL +uint16_t uniffi_church_core_checksum_func_get_optimal_streaming_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_STREAM_LIVE_STATUS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_STREAM_LIVE_STATUS +uint16_t uniffi_church_core_checksum_func_get_stream_live_status(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_WEBSITE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_WEBSITE_URL +uint16_t uniffi_church_core_checksum_func_get_website_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_YOUTUBE_URL +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_GET_YOUTUBE_URL +uint16_t uniffi_church_core_checksum_func_get_youtube_url(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_IS_MULTI_DAY_EVENT_CHECK +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_IS_MULTI_DAY_EVENT_CHECK +uint16_t uniffi_church_core_checksum_func_is_multi_day_event_check(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BIBLE_VERSE_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_bible_verse_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BULLETINS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_BULLETINS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_bulletins_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CALENDAR_EVENT_DATA +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CALENDAR_EVENT_DATA +uint16_t uniffi_church_core_checksum_func_parse_calendar_event_data(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_CONTACT_RESULT_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_contact_result_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_EVENTS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_EVENTS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_events_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_SERMONS_FROM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_PARSE_SERMONS_FROM_JSON +uint16_t uniffi_church_core_checksum_func_parse_sermons_from_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_JSON +uint16_t uniffi_church_core_checksum_func_submit_contact_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON +uint16_t uniffi_church_core_checksum_func_submit_contact_v2_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_SUBMIT_CONTACT_V2_JSON_LEGACY +uint16_t uniffi_church_core_checksum_func_submit_contact_v2_json_legacy(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_CONTACT_FORM_JSON +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_CONTACT_FORM_JSON +uint16_t uniffi_church_core_checksum_func_validate_contact_form_json(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_EMAIL_ADDRESS +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_EMAIL_ADDRESS +uint16_t uniffi_church_core_checksum_func_validate_email_address(void + +); +#endif +#ifndef UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_PHONE_NUMBER +#define UNIFFI_FFIDEF_UNIFFI_CHURCH_CORE_CHECKSUM_FUNC_VALIDATE_PHONE_NUMBER +uint16_t uniffi_church_core_checksum_func_validate_phone_number(void + +); +#endif +#ifndef UNIFFI_FFIDEF_FFI_CHURCH_CORE_UNIFFI_CONTRACT_VERSION +#define UNIFFI_FFIDEF_FFI_CHURCH_CORE_UNIFFI_CONTRACT_VERSION +uint32_t ffi_church_core_uniffi_contract_version(void + +); +#endif + diff --git a/church_coreFFI.modulemap b/church_coreFFI.modulemap new file mode 100644 index 0000000..7e2fb4e --- /dev/null +++ b/church_coreFFI.modulemap @@ -0,0 +1,4 @@ +module church_coreFFI { + header "church_coreFFI.h" + export * +} diff --git a/libchurch_core.a b/libchurch_core.a new file mode 100644 index 0000000..5a66963 Binary files /dev/null and b/libchurch_core.a differ diff --git a/libchurch_core_ios.a b/libchurch_core_ios.a new file mode 100644 index 0000000..dc209c8 Binary files /dev/null and b/libchurch_core_ios.a differ diff --git a/libchurch_core_tvos.a b/libchurch_core_tvos.a new file mode 100644 index 0000000..c7332a7 Binary files /dev/null and b/libchurch_core_tvos.a differ diff --git a/test_verses.swift b/test_verses.swift deleted file mode 100644 index edb7e71..0000000 --- a/test_verses.swift +++ /dev/null @@ -1,107 +0,0 @@ -import Foundation - -class PocketBaseService { - static let shared = PocketBaseService() - let baseURL = "https://pocketbase.rockvilletollandsda.church/api/collections" - private init() {} -} - -@MainActor -class BibleService { - static let shared = BibleService() - private let pocketBaseService = PocketBaseService.shared - - private init() {} - - struct Verse: Identifiable, Codable { - let id: String - let reference: String - let text: String - let isActive: Bool - - enum CodingKeys: String, CodingKey { - case id - case reference - case text - case isActive = "is_active" - } - } - - struct VersesRecord: Codable { - let collectionId: String - let collectionName: String - let created: String - let id: String - let updated: String - let verses: VersesData - - struct VersesData: Codable { - let id: String - let verses: [Verse] - } - } - - private var cachedVerses: [Verse]? - - private func getVerses() async throws -> [Verse] { - print("Fetching verses from PocketBase") - let endpoint = "\(PocketBaseService.shared.baseURL)/bible_verses/records/nkf01o1q3456flr" - - guard let url = URL(string: endpoint) else { - print("Invalid URL: \(endpoint)") - throw URLError(.badURL) - } - - var request = URLRequest(url: url) - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - - print("Making request to: \(endpoint)") - let (data, response) = try await URLSession.shared.data(for: request) - - guard let httpResponse = response as? HTTPURLResponse else { - print("Invalid response type") - throw URLError(.badServerResponse) - } - - print("Response status code: \(httpResponse.statusCode)") - - guard httpResponse.statusCode == 200 else { - if let errorString = String(data: data, encoding: .utf8) { - print("Error response from server: \(errorString)") - } - throw URLError(.badServerResponse) - } - - let decoder = JSONDecoder() - let versesRecord = try decoder.decode(VersesRecord.self, from: data) - return versesRecord.verses.verses - } - - func testAllVerses() async throws { - print("\n=== Testing All Verses ===\n") - let verses = try await getVerses() - for verse in verses { - print("Reference: \(verse.reference)") - print("Text: \(verse.text)") - print("-------------------\n") - } - print("=== Test Complete ===\n") - } -} - -print("Starting Bible Verses Test...") - -// Create a semaphore to signal when the task is complete -let semaphore = DispatchSemaphore(value: 0) - -Task { - do { - try await BibleService.shared.testAllVerses() - } catch { - print("Error testing verses: \(error)") - } - semaphore.signal() -} - -// Wait for the task to complete -semaphore.wait() \ No newline at end of file