diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index 508b3f8..958ed34 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -747,16 +747,6 @@ } } }, - "%@ %@" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "%1$@ %2$@" - } - } - } - }, "%@ Models" : { "localizations" : { "fr" : { diff --git a/DynamicIsland/components/Calendar/DynamicIslandCalendar.swift b/DynamicIsland/components/Calendar/DynamicIslandCalendar.swift index 9fd68d1..5dac248 100644 --- a/DynamicIsland/components/Calendar/DynamicIslandCalendar.swift +++ b/DynamicIsland/components/Calendar/DynamicIslandCalendar.swift @@ -489,66 +489,96 @@ struct ReminderToggle: View { } } +// MARK: - Conference Provider + +enum ConferenceProvider: CaseIterable { + case zoom, teams, meet, webex, facetime, gotomeeting, bluejeans, whereby, jitsi, discord, generic + + var name: String { + switch self { + case .zoom: return "Zoom" + case .teams: return "Teams" + case .meet: return "Meet" + case .webex: return "Webex" + case .facetime: return "FaceTime" + case .gotomeeting: return "GoToMeeting" + case .bluejeans: return "BlueJeans" + case .whereby: return "Whereby" + case .jitsi: return "Jitsi" + case .discord: return "Discord" + case .generic: return "" + } + } + + var color: Color { + switch self { + case .zoom: return Color(red: 0.16, green: 0.52, blue: 0.95) + case .teams: return Color(red: 0.36, green: 0.42, blue: 0.89) + case .meet: return Color(red: 0.0, green: 0.65, blue: 0.42) + case .webex: return Color(red: 0.0, green: 0.71, blue: 0.84) + case .facetime: return Color(red: 0.2, green: 0.78, blue: 0.35) + case .gotomeeting: return Color(red: 0.95, green: 0.5, blue: 0.13) + case .bluejeans: return Color(red: 0.0, green: 0.48, blue: 0.87) + case .whereby: return Color(red: 0.27, green: 0.51, blue: 0.96) + case .jitsi: return Color(red: 0.16, green: 0.68, blue: 0.95) + case .discord: return Color(red: 0.35, green: 0.39, blue: 0.98) + case .generic: return Color.accentColor + } + } + + private var hostIdentifiers: [String] { + switch self { + case .zoom: return ["zoom.us"] + case .teams: return ["teams.microsoft.com"] + case .meet: return ["meet.google.com"] + case .webex: return ["webex.com"] + case .facetime: return ["facetime.apple.com"] + case .gotomeeting: return ["gotomeeting.com"] + case .bluejeans: return ["bluejeans.com"] + case .whereby: return ["whereby.com"] + case .jitsi: return ["meet.jit.si", "jitsi"] + case .discord: return ["discord.gg", "discord.com"] + case .generic: return [] + } + } + + static func detect(from url: URL) -> ConferenceProvider { + let host = url.host?.lowercased() ?? "" + return allCases.first { provider in + provider.hostIdentifiers.contains { host.contains($0) } + } ?? .generic + } +} + +// MARK: - Conference Join Button + struct ConferenceJoinButton: View { let url: URL let event: EventModel @Environment(\.openURL) private var openURL + private var provider: ConferenceProvider { .detect(from: url) } private var isJoinable: Bool { - // Allow joining 15 minutes before the event starts - let joinableTime = event.start.addingTimeInterval(-15 * 60) - return Date() >= joinableTime + event.start.addingTimeInterval(-15 * 60) <= Date() } private var buttonText: String { - if event.eventStatus == .inProgress { - return "Rejoin" - } else { - return "Join" - } - } - - private var conferenceService: String? { - let host = url.host?.lowercased() ?? "" - if host.contains("zoom.us") { - return "Zoom" - } else if host.contains("teams.microsoft.com") { - return "Teams" - } else if host.contains("meet.google.com") { - return "Meet" - } else if host.contains("webex.com") { - return "Webex" - } else if host.contains("facetime.apple.com") { - return "FaceTime" - } - return nil + event.eventStatus == .inProgress ? "Rejoin" : "Join" } var body: some View { - Button(action: { - openURL(url) - }) { + Button(action: { openURL(url) }) { HStack(spacing: 4) { Image(systemName: "video.fill") .font(.system(size: 9)) - if let service = conferenceService { - Text("\(buttonText) \(service)") - .font(.caption2) - .fontWeight(.medium) - } else { - Text(buttonText) - .font(.caption2) - .fontWeight(.medium) - } + Text(provider.name.isEmpty ? buttonText : "\(buttonText) \(provider.name)") + .font(.caption2) + .fontWeight(.medium) } .foregroundColor(isJoinable ? .white : Color(white: 0.5)) .padding(.horizontal, 8) .padding(.vertical, 4) - .background( - isJoinable - ? Color.accentColor.opacity(0.8) - : Color.gray.opacity(0.3) - ) + .background(isJoinable ? provider.color.opacity(0.85) : Color.gray.opacity(0.3)) .cornerRadius(6) } .buttonStyle(PlainButtonStyle())