
- Comprehensive README update documenting v2.0 architectural changes - Updated git remote to ssh://rockvilleav@git.rockvilletollandsda.church:10443/RTSDA/RTSDA-iOS.git - Documented unified ChurchService and 60% code reduction - Added new features: Home Feed, responsive reading, enhanced UI - Corrected license information (GPL v3 with church content copyright) - Updated build instructions and technical stack details
275 lines
8.5 KiB
Swift
275 lines
8.5 KiB
Swift
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)
|
|
}
|
|
}
|
|
|