diff --git a/WireCalling/Package.swift b/WireCalling/Package.swift
index 866b45aacb6..ad5b1c47835 100644
--- a/WireCalling/Package.swift
+++ b/WireCalling/Package.swift
@@ -11,8 +11,9 @@ let package = Package(
.library(name: "WireCallingUI", targets: ["WireCallingUI"])
],
dependencies: [
- .package(path: "../WireFoundation"),
- .package(path: "../WireUI")
+ .package(name: "WireFoundation", path: "../WireFoundation"),
+ .package(path: "../WirePlugins"),
+ .package(name: "WireUI", path: "../WireUI")
],
targets: [
.target(
@@ -20,8 +21,10 @@ let package = Package(
dependencies: [
.product(name: "WireDesign", package: "WireUI"),
.product(name: "WireReusableUIComponents", package: "WireUI"),
+ .product(name: "WireAccountImageUI", package: "WireUI"),
"WireFoundation"
],
+ plugins: [.plugin(name: "SwiftGenPlugin", package: "WirePlugins")]
),
.testTarget(
name: "WireCallingTests",
diff --git a/WireCalling/Sources/WireCallingUI/.swiftgen.yml b/WireCalling/Sources/WireCallingUI/.swiftgen.yml
new file mode 100644
index 00000000000..e5f2e8becda
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/.swiftgen.yml
@@ -0,0 +1,15 @@
+# Every input/output paths in the rest of the config will then be expressed relative to these.
+
+input_dir: ./
+output_dir: ${GENERATED}/
+
+# Generate constants for your localized strings.
+
+strings:
+ inputs:
+ - Resources/Localization/en.lproj/Localizable.strings
+ - Resources/Localization/en.lproj/Accessibility.strings
+ filter:
+ outputs:
+ - templateName: structured-swift5
+ output: Strings+Generated.swift
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/Contents.json b/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/Contents.json
new file mode 100644
index 00000000000..73c00596a7f
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/VideoCall.imageset/Contents.json b/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/VideoCall.imageset/Contents.json
new file mode 100644
index 00000000000..af3b035e2fe
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/VideoCall.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "VideoCallFilled.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/VideoCall.imageset/VideoCallFilled.svg b/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/VideoCall.imageset/VideoCallFilled.svg
new file mode 100644
index 00000000000..ce8f6a70196
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/VideoCall.imageset/VideoCallFilled.svg
@@ -0,0 +1,4 @@
+
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/meetingsEmptyArrow.imageset/Contents.json b/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/meetingsEmptyArrow.imageset/Contents.json
new file mode 100644
index 00000000000..9e5732f0d13
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/meetingsEmptyArrow.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "arrow1.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/meetingsEmptyArrow.imageset/arrow1.svg b/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/meetingsEmptyArrow.imageset/arrow1.svg
new file mode 100644
index 00000000000..c28f79ebe5f
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Images.xcassets/meetingsEmptyArrow.imageset/arrow1.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/WireCalling/Sources/WireCallingUI/File.swift b/WireCalling/Sources/WireCallingUI/Resources/Localization/ar.lproj/Accessibility.strings
similarity index 97%
rename from WireCalling/Sources/WireCallingUI/File.swift
rename to WireCalling/Sources/WireCallingUI/Resources/Localization/ar.lproj/Accessibility.strings
index 45cad4d0330..89c90917712 100644
--- a/WireCalling/Sources/WireCallingUI/File.swift
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/ar.lproj/Accessibility.strings
@@ -15,5 +15,3 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//
-
-import Foundation
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/ar.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/ar.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/ar.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/da.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/da.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/da.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/da.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/da.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/da.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/de.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/de.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/de.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/de.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/de.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/de.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/en.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/en.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/en.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/en.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/en.lproj/Localizable.strings
new file mode 100644
index 00000000000..a998d27343e
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/en.lproj/Localizable.strings
@@ -0,0 +1,27 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+
+// MARK: - Meetings List
+
+"wireMeetings.list.title" = "Meetings";
+"wireMeetings.list.tabs.past" = "Past";
+"wireMeetings.list.tabs.upcoming" = "Upcoming";
+"wireMeetings.list.actions.meetNow" = "Meet Now";
+"wireMeetings.list.actions.scheduleMeeting" = "Schedule a Meeting";
+"wireMeetings.list.emptyState.title" = "No meetings yet";
+"wireMeetings.list.emptyState.subtitle" = "Start a meeting with team members, guests, or external parties.";
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/es.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/es.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/es.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/es.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/es.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/es.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/et.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/et.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/et.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/et.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/et.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/et.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/fi.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/fi.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/fi.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/fi.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/fi.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/fi.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/fr.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/fr.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/fr.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/fr.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/fr.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/fr.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/it.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/it.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/it.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/it.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/it.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/it.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/ja.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/ja.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/ja.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/ja.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/ja.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/ja.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/lt.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/lt.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/lt.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/lt.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/lt.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/lt.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/nl.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/nl.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/nl.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/nl.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/nl.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/nl.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/pl.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/pl.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/pl.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/pl.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/pl.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/pl.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/pt-BR.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/pt-BR.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/pt-BR.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/pt-BR.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/pt-BR.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/pt-BR.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/ru.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/ru.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/ru.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/ru.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/ru.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/ru.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/sl.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/sl.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/sl.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/sl.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/sl.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/sl.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/tr.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/tr.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/tr.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/tr.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/tr.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/tr.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/uk.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/uk.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/uk.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/uk.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/uk.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/uk.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hans.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hans.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hans.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hans.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hans.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hans.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hant.lproj/Accessibility.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hant.lproj/Accessibility.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hant.lproj/Accessibility.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hant.lproj/Localizable.strings b/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hant.lproj/Localizable.strings
new file mode 100644
index 00000000000..89c90917712
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/Localization/zh-Hant.lproj/Localizable.strings
@@ -0,0 +1,17 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
diff --git a/WireCalling/Sources/WireCallingUI/Resources/UImage+ImageResource.swift b/WireCalling/Sources/WireCallingUI/Resources/UImage+ImageResource.swift
new file mode 100644
index 00000000000..87b5a169ccc
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/Resources/UImage+ImageResource.swift
@@ -0,0 +1,23 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+
+import UIKit
+
+extension UIImage {
+ static let videoCall = UIImage(resource: .videoCall)
+}
diff --git a/WireCalling/Sources/WireCallingUI/WireMeetings/AccountView/AccountUI.swift b/WireCalling/Sources/WireCallingUI/WireMeetings/AccountView/AccountUI.swift
new file mode 100644
index 00000000000..66c952598ea
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/WireMeetings/AccountView/AccountUI.swift
@@ -0,0 +1,49 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+
+import SwiftUI
+import WireAccountImageUI
+import WireDesign
+
+struct AccountUI: View {
+ private let viewModel: AccountUIViewModel
+
+ init(viewModel: AccountUIViewModel) {
+ self.viewModel = viewModel
+ }
+
+ var body: some View {
+ let accountImageViewDesign = AccountImageViewDesign()
+ let availabilityIndicatorDesign = accountImageViewDesign.availabilityIndicator
+ HStack(spacing: 4) {
+ AccountImageViewRepresentable(
+ source: viewModel.avatarSource,
+ availability: viewModel.availability,
+ showNotificationsBadge: false
+ )
+ .accountImageBorderWidth(accountImageViewDesign.borderWidth)
+ .accountImageViewBorderColor(accountImageViewDesign.borderColor)
+ .availabilityIndicatorAvailableColor(availabilityIndicatorDesign.availableColor)
+ .availabilityIndicatorAwayColor(availabilityIndicatorDesign.awayColor)
+ .availabilityIndicatorBusyColor(availabilityIndicatorDesign.busyColor)
+ .availabilityIndicatorBackgroundViewColor(availabilityIndicatorDesign.backgroundViewColor)
+ .frame(width: 28, height: 28)
+ }
+ .fixedSize()
+ }
+}
diff --git a/WireCalling/Sources/WireCallingUI/WireMeetings/AccountView/AccountUIViewModel.swift b/WireCalling/Sources/WireCallingUI/WireMeetings/AccountView/AccountUIViewModel.swift
new file mode 100644
index 00000000000..49c13ac0640
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/WireMeetings/AccountView/AccountUIViewModel.swift
@@ -0,0 +1,38 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+
+public import Foundation
+public import WireAccountImageUI
+
+public struct AccountUIViewModel: Identifiable {
+ public let id = UUID()
+
+ let avatarSource: AccountImageSource
+ let availability: Availability?
+ public let action: () -> Void
+
+ public init(
+ avatarSource: AccountImageSource,
+ availability: Availability?,
+ action: @escaping () -> Void
+ ) {
+ self.avatarSource = avatarSource
+ self.availability = availability
+ self.action = action
+ }
+}
diff --git a/WireCalling/Sources/WireCallingUI/WireMeetings/AccountView/AccountUIWrapperView.swift b/WireCalling/Sources/WireCallingUI/WireMeetings/AccountView/AccountUIWrapperView.swift
new file mode 100644
index 00000000000..4a06bb2d1a8
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/WireMeetings/AccountView/AccountUIWrapperView.swift
@@ -0,0 +1,51 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+
+import SwiftUI
+import Foundation
+import WireAccountImageUI
+import WireDesign
+
+final class AccountUIWrapperView: UIView {
+ private let hostingController: UIHostingController
+
+ init(viewModel: AccountUIViewModel) {
+ hostingController = UIHostingController(rootView: AccountUI(viewModel: viewModel))
+ super.init(frame: .zero)
+
+ backgroundColor = .clear
+ hostingController.view.backgroundColor = .clear
+
+ addSubview(hostingController.view)
+ hostingController.view.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ hostingController.view.leadingAnchor.constraint(equalTo: leadingAnchor),
+ hostingController.view.trailingAnchor.constraint(equalTo: trailingAnchor),
+ hostingController.view.topAnchor.constraint(equalTo: topAnchor),
+ hostingController.view.bottomAnchor.constraint(equalTo: bottomAnchor),
+ ])
+ }
+
+ func apply(account: AccountUIViewModel) {
+ hostingController.rootView = AccountUI(viewModel: account)
+ hostingController.view.setNeedsLayout()
+ }
+
+ @available(*, unavailable)
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+}
diff --git a/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsEmptyStateView.swift b/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsEmptyStateView.swift
new file mode 100644
index 00000000000..fd62a331219
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsEmptyStateView.swift
@@ -0,0 +1,68 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+
+import SwiftUI
+import WireDesign
+
+package struct MeetingsEmptyStateView: View {
+ private typealias Strings = L10n.Localizable.WireMeetings.List.EmptyState
+
+ var body: some View {
+ ZStack {
+ VStack(spacing: 28) {
+ Spacer(minLength: 0)
+ Text(Strings.title)
+ .font(.textStyle(.h2))
+ .foregroundColor(ColorTheme.Backgrounds.onSurfaceVariant.color)
+ .multilineTextAlignment(.center)
+ .padding(.horizontal, 40)
+ .accessibilityIdentifier("meetingsEmptyStateTitle")
+
+ Text(Strings.subtitle)
+ .font(.textStyle(.body1))
+ .foregroundColor(ColorTheme.Backgrounds.onSurfaceVariant.color)
+ .multilineTextAlignment(.center)
+ .padding(.horizontal, 40)
+ .accessibilityIdentifier("meetingsEmptyStateSubtitle")
+ Spacer(minLength: 0)
+ }
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .background(ColorTheme.Backgrounds.surfaceVariant.color)
+ .overlay(
+ arrowView
+ .frame(width: 120, height: 200)
+ .padding(.trailing, 40)
+ .padding(.top, 40)
+ .allowsHitTesting(false)
+ .accessibilityHidden(true),
+ alignment: .topTrailing
+ )
+ .contentShape(Rectangle())
+ }
+
+ @ViewBuilder private var arrowView: some View {
+ Image(.meetingsEmptyArrow)
+ .resizable()
+ .renderingMode(.original)
+ }
+}
+
+#Preview {
+ MeetingsEmptyStateView()
+}
diff --git a/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsListView.swift b/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsListView.swift
new file mode 100644
index 00000000000..4c1489c1fb0
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsListView.swift
@@ -0,0 +1,72 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+
+public import SwiftUI
+import WireDesign
+
+public struct MeetingsListView: View {
+
+ @ObservedObject private var viewModel: MeetingsListViewModel
+
+ public init(viewModel: MeetingsListViewModel) {
+ self.viewModel = viewModel
+ }
+
+ public var body: some View {
+ Group {
+ if viewModel.hasMeetingsForSelectedTab {
+ VStack(spacing: 12) {
+ Picker("", selection: Binding(
+ get: { viewModel.selectedTab.rawValue },
+ set: { viewModel.selectedTab = MeetingsListViewModel.Tab(rawValue: $0) ?? .upcoming }
+ )) {
+ ForEach(MeetingsListViewModel.Tab.allCases, id: \.rawValue) { tab in
+ Text(tab.title).tag(tab.rawValue)
+ }
+ }
+ .pickerStyle(.segmented)
+ .padding(.horizontal, 16)
+ .padding(.top, 12)
+ .padding(.bottom, 10)
+ .accessibilityIdentifier("meetingsListPicker")
+
+ content
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
+ }
+ } else {
+ MeetingsEmptyStateView()
+ }
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
+ .background(ColorTheme.Backgrounds.background.color)
+ }
+
+ @ViewBuilder private var content: some View {
+ switch viewModel.selectedTab {
+ case .upcoming:
+ Spacer(minLength: 0)
+ case .past:
+ Spacer(minLength: 0)
+ }
+ }
+
+}
+
+#Preview {
+ MeetingsListView(viewModel: MeetingsListViewModel(account: AccountUIViewModel(avatarSource: .text("AN"), availability: nil, action: {})))
+}
diff --git a/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsListViewController.swift b/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsListViewController.swift
new file mode 100644
index 00000000000..51e98168a2f
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsListViewController.swift
@@ -0,0 +1,138 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+
+public import SwiftUI
+import Foundation
+import WireAccountImageUI
+import WireDesign
+import Combine
+
+public final class MeetingsListViewController: UIViewController {
+
+ private typealias Strings = L10n.Localizable.WireMeetings.List.Actions
+
+ private let viewModel: MeetingsListViewModel
+ private let hostingController: UIHostingController
+ private var cancellables = Set()
+ private var accountWrapperView: AccountUIWrapperView?
+
+ public init(viewModel: MeetingsListViewModel) {
+ self.viewModel = viewModel
+ self.hostingController = UIHostingController(rootView: MeetingsListView(viewModel: viewModel))
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ @available(*, unavailable)
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) is not supported")
+ }
+
+ public override func viewDidLoad() {
+ super.viewDidLoad()
+ view.accessibilityViewIsModal = true
+ addChild(hostingController)
+ view.addSubview(hostingController.view)
+ hostingController.didMove(toParent: self)
+
+ hostingController.view.translatesAutoresizingMaskIntoConstraints = false
+ NSLayoutConstraint.activate([
+ hostingController.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
+ hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+ ])
+
+ setupNavigationBar()
+ configureNavigationBarAppearance()
+ viewModel.$account
+ .receive(on: RunLoop.main)
+ .sink { [weak self] newAccount in
+ self?.accountWrapperView?.apply(account: newAccount)
+ }
+ .store(in: &cancellables)
+ }
+
+ public override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+ configureNavigationBarAppearance()
+ }
+
+ // MARK: - Navigation Bar
+
+ private func setupNavigationBar() {
+ navigationItem.title = L10n.Localizable.WireMeetings.List.title
+ setupLeftNavigationBarButtonItems()
+ setupRightNavigationBarButtonItems()
+ }
+
+ func configureNavigationBarAppearance() {
+ let appearance = UINavigationBarAppearance()
+ appearance.configureWithDefaultBackground()
+ appearance.backgroundColor = ColorTheme.Backgrounds.surface
+
+ navigationController?.navigationBar.standardAppearance = appearance
+ navigationController?.navigationBar.scrollEdgeAppearance = appearance
+ navigationController?.navigationBar.compactAppearance = appearance
+ }
+
+ private func setupRightNavigationBarButtonItems() {
+ let configuration = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 17))
+ let chevron = UIImage(systemName: "chevron.forward", withConfiguration: configuration)
+
+ let meetNow = UIAction(title: Strings.meetNow, image: chevron) { [weak self] _ in
+ self?.viewModel.meetNowTapped()
+ }
+ let schedule = UIAction(title: Strings.scheduleMeeting, image: chevron) { [weak self] _ in
+ self?.viewModel.scheduleMeetingTapped()
+ }
+
+ let menu = UIMenu(children: [meetNow, schedule])
+ let button = UIButton(type: .system)
+ button.setImage(UIImage(resource: .videoCall), for: .normal)
+ button.showsMenuAsPrimaryAction = true
+ button.menu = menu
+ button.accessibilityIdentifier = "scheduleMeetingBarButton"
+
+ let item = UIBarButtonItem(customView: button)
+ navigationItem.rightBarButtonItems = [item]
+ }
+
+ private func setupLeftNavigationBarButtonItems() {
+ let stackView = UIStackView()
+ stackView.spacing = 6
+ stackView.axis = .horizontal
+ stackView.alignment = .center
+
+ let avatar = makeAccountImageView()
+ stackView.addArrangedSubview(avatar)
+ accountWrapperView = avatar
+
+ let container = UIBarButtonItem(customView: stackView)
+ container.accessibilityIdentifier = "accountImageBarButton"
+ navigationItem.leftBarButtonItems = [container]
+ }
+
+ private func makeAccountImageView() -> AccountUIWrapperView {
+ let accountUI = AccountUIWrapperView(viewModel: viewModel.account)
+ accountUI.isAccessibilityElement = true
+ accountUI.translatesAutoresizingMaskIntoConstraints = false
+ accountUI.widthAnchor.constraint(equalToConstant: 28).isActive = true
+ accountUI.heightAnchor.constraint(equalToConstant: 28).isActive = true
+ return accountUI
+ }
+}
diff --git a/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsListViewModel.swift b/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsListViewModel.swift
new file mode 100644
index 00000000000..4958ff9695b
--- /dev/null
+++ b/WireCalling/Sources/WireCallingUI/WireMeetings/MeetingsListViewModel.swift
@@ -0,0 +1,69 @@
+//
+// Wire
+// Copyright (C) 2025 Wire Swiss GmbH
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+//
+
+public import Foundation
+
+public final class MeetingsListViewModel: ObservableObject {
+
+ private typealias Strings = L10n.Localizable.WireMeetings.List.Tabs
+
+ enum Tab: Int, CaseIterable {
+ case upcoming
+ case past
+
+ var title: String {
+ switch self {
+ case .upcoming: Strings.upcoming
+ case .past: Strings.past
+ }
+ }
+ }
+
+ @Published var selectedTab: Tab = .upcoming
+ @Published var upcomingMeetings: [Meeting] = []
+ @Published var pastMeetings: [Meeting] = []
+
+ var currentMeetings: [Meeting] {
+ switch selectedTab {
+ case .upcoming: upcomingMeetings
+ case .past: pastMeetings
+ }
+ }
+
+ var hasMeetingsForSelectedTab: Bool { !currentMeetings.isEmpty }
+ @Published var account: AccountUIViewModel
+
+ public init(account: AccountUIViewModel) {
+ self.account = account
+ }
+
+ public func updateAccount(_ account: AccountUIViewModel) {
+ self.account = account
+ }
+
+ func meetNowTapped() {}
+ func scheduleMeetingTapped() {}
+
+}
+
+package struct Meeting: Identifiable, Hashable {
+ public let id = UUID()
+ public let title: String
+ public let start: Date
+ public let end: Date
+}
diff --git a/WireUI/Sources/WireMainNavigationUI/Containers/MainSplitViewController+typealiases.swift b/WireUI/Sources/WireMainNavigationUI/Containers/MainSplitViewController+typealiases.swift
index b4023d8efc8..5ea8089bf4b 100644
--- a/WireUI/Sources/WireMainNavigationUI/Containers/MainSplitViewController+typealiases.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Containers/MainSplitViewController+typealiases.swift
@@ -23,6 +23,7 @@ public extension MainSplitViewController {
typealias ConversationListUI = TabController.ConversationListUI
typealias ArchiveUI = TabController.ArchiveUI
typealias SettingsUI = TabController.SettingsUI
+ typealias MeetingsUI = TabController.MeetingsUI
typealias ConversationUI = TabController.ConversationUI
typealias SettingsContentUI = UIViewController
diff --git a/WireUI/Sources/WireMainNavigationUI/Containers/MainSplitViewController.swift b/WireUI/Sources/WireMainNavigationUI/Containers/MainSplitViewController.swift
index 792f5b283a2..250ec2efab8 100644
--- a/WireUI/Sources/WireMainNavigationUI/Containers/MainSplitViewController.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Containers/MainSplitViewController.swift
@@ -46,6 +46,11 @@ public final class MainSplitViewController: UISplitViewC
set { setArchiveUI(newValue, animated: false) }
}
+ public var meetingsUI: MeetingsUI? {
+ get { _meetingsUI }
+ set { setMeetingsUI(newValue, animated: false) }
+ }
+
public var settingsUI: SettingsUI? {
get { _settingsUI }
set { setSettingsUI(newValue, animated: false) }
@@ -79,6 +84,7 @@ public final class MainSplitViewController: UISplitViewC
private weak var _conversationListUI: ConversationListUI?
private weak var _archiveUI: ArchiveUI?
+ private weak var _meetingsUI: MeetingsUI?
private weak var _settingsUI: SettingsUI?
private weak var _conversationUI: ConversationUI?
@@ -170,6 +176,14 @@ public final class MainSplitViewController: UISplitViewC
splitLayoutContainer.primaryNavigationController.view.layoutIfNeeded()
}
+ private func setMeetingsUI(_ meetingsUI: MeetingsUI?, animated: Bool) {
+ _meetingsUI = meetingsUI
+
+ let viewControllers = [meetingsUI].compactMap(\.self)
+ splitLayoutContainer.primaryNavigationController.setViewControllers(viewControllers, animated: animated)
+ splitLayoutContainer.primaryNavigationController.view.layoutIfNeeded()
+ }
+
private func setSettingsUI(_ settingsUI: SettingsUI?, animated: Bool) {
_settingsUI = settingsUI
diff --git a/WireUI/Sources/WireMainNavigationUI/Containers/MainTabBarController.swift b/WireUI/Sources/WireMainNavigationUI/Containers/MainTabBarController.swift
index 56bbda4e4d2..59de6f0d53d 100644
--- a/WireUI/Sources/WireMainNavigationUI/Containers/MainTabBarController.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Containers/MainTabBarController.swift
@@ -31,6 +31,7 @@ public final class MainTabBarController<
public typealias ArchiveUI = UIViewController
public typealias SettingsUI = UIViewController
+ public typealias MeetingsUI = UIViewController
// MARK: - Public Properties
@@ -44,6 +45,11 @@ public final class MainTabBarController<
set { setArchiveUI(newValue, animated: false) }
}
+ public var meetingsUI: MeetingsUI? {
+ get { _meetingsUI }
+ set { setMeetingsUI(newValue, animated: false) }
+ }
+
public var settingsUI: SettingsUI? {
get { _settingsUI }
set { setSettingsUI(newValue, animated: false) }
@@ -68,17 +74,23 @@ public final class MainTabBarController<
private weak var conversationListNavigationController: UINavigationController!
private weak var archiveNavigationController: UINavigationController!
+ private weak var meetingsNavigationController: UINavigationController!
private weak var settingsNavigationController: UINavigationController!
private weak var _conversationListUI: ConversationListUI?
private weak var _archiveUI: ArchiveUI?
+ private weak var _meetingsUI: MeetingsUI?
private weak var _settingsUI: SettingsUI?
private weak var _conversationUI: ConversationUI?
private weak var _settingsContentUI: UIViewController?
+ /// We should use DeveloperFlag 'wireMeetings' after moving it to WireFoundation:
+ /// https://wearezeta.atlassian.net/browse/WPB-19065
+ private var showMeetings: Bool
// MARK: - Life Cycle
- public required init() {
+ public init(showMeetings: Bool) {
+ self.showMeetings = showMeetings
super.init(nibName: nil, bundle: nil)
setupTabs()
setupAppearance()
@@ -98,16 +110,25 @@ public final class MainTabBarController<
archiveNavigationController.navigationBar.isTranslucent = false
self.archiveNavigationController = archiveNavigationController
+ let meetingsNavigationController = UINavigationController()
+ meetingsNavigationController.navigationBar.isTranslucent = false
+ self.meetingsNavigationController = meetingsNavigationController
+
let settingsNavigationController = UINavigationController()
settingsNavigationController.navigationBar.isTranslucent = false
self.settingsNavigationController = settingsNavigationController
- viewControllers = [
+ var tabs: [UIViewController] = [
conversationListNavigationController,
archiveNavigationController,
settingsNavigationController
]
+ if showMeetings {
+ tabs.insert(meetingsNavigationController, at: 2)
+ }
+ setViewControllers(tabs, animated: false)
+
for content in MainTabBarControllerContent.allCases {
switch content {
case .conversations:
@@ -148,6 +169,25 @@ public final class MainTabBarController<
)
archiveNavigationController.tabBarItem = tabBarItem
+ case .meetings:
+ let tabBarItem = UITabBarItem(
+ title: String(localized: "tabBar.meetings.title", bundle: .module),
+ image: .init(resource: .videoCall),
+ selectedImage: .init(resource: .videoCallFilled)
+ )
+ tabBarItem.accessibilityIdentifier = "bottomBarMeetingsButton"
+ tabBarItem.accessibilityLabel = String(
+ localized: "tabBar.meetings.description",
+ table: "Accessibility",
+ bundle: .module
+ )
+ tabBarItem.accessibilityHint = String(
+ localized: "tabBar.meetings.hint",
+ table: "Accessibility",
+ bundle: .module
+ )
+ meetingsNavigationController.tabBarItem = tabBarItem
+
case .settings:
let tabBarItem = UITabBarItem(
title: String(localized: "tabBar.settings.title", bundle: .module),
@@ -209,6 +249,14 @@ public final class MainTabBarController<
archiveNavigationController.view.layoutIfNeeded()
}
+ private func setMeetingsUI(_ meetingsUI: MeetingsUI?, animated: Bool) {
+ _meetingsUI = meetingsUI
+
+ let viewControllers = [meetingsUI].compactMap(\.self)
+ meetingsNavigationController.setViewControllers(viewControllers, animated: animated)
+ meetingsNavigationController.view.layoutIfNeeded()
+ }
+
private func setSettingsUI(_ settingsUI: SettingsUI?, animated: Bool) {
_settingsUI = settingsUI
diff --git a/WireUI/Sources/WireMainNavigationUI/Coordinator/AnyMainCoordinator.swift b/WireUI/Sources/WireMainNavigationUI/Coordinator/AnyMainCoordinator.swift
index d877e93b2bd..23a6c3b06cd 100644
--- a/WireUI/Sources/WireMainNavigationUI/Coordinator/AnyMainCoordinator.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Coordinator/AnyMainCoordinator.swift
@@ -26,12 +26,14 @@ public final class AnyMainCoordinator Void
private let _applyConversationFilter: @MainActor (_ conversationFilter: ConversationFilter?) -> Void
private let _showArchive: @MainActor () async -> Void
+ private let _showMeetings: @MainActor () async -> Void
private let _showSettings: @MainActor () async -> Void
private let _showConversation: @MainActor (
_ conversation: ConversationModel,
_ message: ConversationMessageModel?
) async -> Void
private let _hideConversation: @MainActor () -> Void
+ private let _hideMeetings: @MainActor () -> Void
private let _showSettingsContent: @MainActor (_ topLevelMenuItem: SettingsTopLevelMenuItem) -> Void
private let _hideSettingsContent: @MainActor () -> Void
private let _presentViewController: @MainActor (_ viewController: UIViewController) async -> Void
@@ -54,12 +56,18 @@ public final class AnyMainCoordinator: NSObject, MainCoordinatorProto
}
}
+ private var meetingsUI: TabBarController.MeetingsUI! {
+ switch mainSplitViewState {
+ case .collapsed: tabBarController.meetingsUI
+ case .expanded: splitViewController.meetingsUI
+ }
+ }
+
private var settingsUI: TabBarController.SettingsUI! {
switch mainSplitViewState {
case .collapsed: tabBarController.settingsUI
@@ -190,6 +197,10 @@ public final class MainCoordinator: NSObject, MainCoordinatorProto
showSettingsContent(.init(.account)) // TODO: [WPB-11347] make the selection visible
}
+ public func showMeetings() async {
+ // TODO: [WPB-20272]: add list of meetings
+ }
+
public func showConversation(
conversation: ConversationModel,
message: ConversationMessageModel?
@@ -252,6 +263,8 @@ public final class MainCoordinator: NSObject, MainCoordinatorProto
splitViewController.settingsContentUI = nil
}
+ public func hideMeetings() {}
+
public func presentViewController(_ viewController: UIViewController) async {
if mainSplitViewState == .expanded, splitViewController.splitBehavior == .overlay {
splitViewController.hideSidebar()
@@ -409,6 +422,9 @@ public final class MainCoordinator: NSObject, MainCoordinatorProto
case .archive:
sidebar.selectedMenuItem = .init(.archive)
+ case .meetings:
+ sidebar.selectedMenuItem = .init(.meetings)
+
case .settings:
sidebar.selectedMenuItem = .init(.settings)
}
diff --git a/WireUI/Sources/WireMainNavigationUI/Models/MainSidebarMenuItem.swift b/WireUI/Sources/WireMainNavigationUI/Models/MainSidebarMenuItem.swift
index 389e6f668ad..65f86382eb1 100644
--- a/WireUI/Sources/WireMainNavigationUI/Models/MainSidebarMenuItem.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Models/MainSidebarMenuItem.swift
@@ -31,6 +31,7 @@ public enum MainSidebarMenuItem: Sendable, Equatable {
case drafts
case archive
case folders
+ case meetings
// bottom
case settings
}
diff --git a/WireUI/Sources/WireMainNavigationUI/Models/MainTabBarControllerContent.swift b/WireUI/Sources/WireMainNavigationUI/Models/MainTabBarControllerContent.swift
index 565e1bdcb2e..1e8cc2e2550 100644
--- a/WireUI/Sources/WireMainNavigationUI/Models/MainTabBarControllerContent.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Models/MainTabBarControllerContent.swift
@@ -22,4 +22,5 @@ public enum MainTabBarControllerContent: Int, CaseIterable {
case conversations
case archive
case settings
+ case meetings
}
diff --git a/WireUI/Sources/WireMainNavigationUI/Preview/MainSplitViewControllerPreview.swift b/WireUI/Sources/WireMainNavigationUI/Preview/MainSplitViewControllerPreview.swift
index 77111e2f316..32ca5e72280 100644
--- a/WireUI/Sources/WireMainNavigationUI/Preview/MainSplitViewControllerPreview.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Preview/MainSplitViewControllerPreview.swift
@@ -23,7 +23,7 @@ func MainSplitViewControllerPreview() -> UISplitViewController {
let splitViewController = MainSplitViewController(
sidebar: PreviewSidebarViewController("sidebar"),
noConversationPlaceholder: UIHostingController(rootView: Text(verbatim: "no conversation placeholder")),
- tabController: .init()
+ tabController: .init(showMeetings: false)
)
splitViewController.conversationListUI = PreviewConversationListViewController("conversation list")
return splitViewController
diff --git a/WireUI/Sources/WireMainNavigationUI/Preview/MainTabBarControllerPreview.swift b/WireUI/Sources/WireMainNavigationUI/Preview/MainTabBarControllerPreview.swift
index d23534f2dd2..941a66c46b0 100644
--- a/WireUI/Sources/WireMainNavigationUI/Preview/MainTabBarControllerPreview.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Preview/MainTabBarControllerPreview.swift
@@ -20,7 +20,7 @@ import SwiftUI
@MainActor
func MainTabBarControllerPreview() -> some MainTabBarControllerProtocol {
- let tabBarController = PreviewTabBarController()
+ let tabBarController = PreviewTabBarController(showMeetings: false)
tabBarController.conversationListUI = .init("conversationList")
tabBarController.archiveUI = PlaceholderViewController()
tabBarController.settingsUI = .init()
diff --git a/WireUI/Sources/WireMainNavigationUI/Protocols/Containers/MainContainerViewController.swift b/WireUI/Sources/WireMainNavigationUI/Protocols/Containers/MainContainerViewController.swift
index 36a8e2816bc..646f646f719 100644
--- a/WireUI/Sources/WireMainNavigationUI/Protocols/Containers/MainContainerViewController.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Protocols/Containers/MainContainerViewController.swift
@@ -25,12 +25,14 @@ public protocol MainContainerViewControllerProtocol: UIViewController {
associatedtype ConversationListUI: MainConversationListUIProtocol
associatedtype ArchiveUI: UIViewController
associatedtype SettingsUI: UIViewController
+ associatedtype MeetingsUI: UIViewController
associatedtype ConversationUI: MainConversationUIProtocol
// These three properties represent the tabs of the main tab bar controller.
var conversationListUI: ConversationListUI? { get set }
var archiveUI: ArchiveUI? { get set }
+ var meetingsUI: MeetingsUI? { get set }
var settingsUI: SettingsUI? { get set }
// These two represent the content, which will be pushed on the three main screens.
diff --git a/WireUI/Sources/WireMainNavigationUI/Protocols/Containers/MainSplitViewControllerProtocol.swift b/WireUI/Sources/WireMainNavigationUI/Protocols/Containers/MainSplitViewControllerProtocol.swift
index 95f37d63df5..ec37a0176cf 100644
--- a/WireUI/Sources/WireMainNavigationUI/Protocols/Containers/MainSplitViewControllerProtocol.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Protocols/Containers/MainSplitViewControllerProtocol.swift
@@ -26,6 +26,7 @@ public protocol MainSplitViewControllerProtocol: UISplitViewController, MainCont
TabBarController.ConversationListUI == ConversationListUI,
TabBarController.ConversationUI == ConversationUI,
TabBarController.ArchiveUI == ArchiveUI,
+ TabBarController.MeetingsUI == MeetingsUI,
TabBarController.SettingsUI == SettingsUI
/// Contains the reference to the view controller shown in the primary column.
diff --git a/WireUI/Sources/WireMainNavigationUI/Protocols/Coordinator/MainCoordinatorProtocol.swift b/WireUI/Sources/WireMainNavigationUI/Protocols/Coordinator/MainCoordinatorProtocol.swift
index b7e33b496dd..3e14a36da0b 100644
--- a/WireUI/Sources/WireMainNavigationUI/Protocols/Coordinator/MainCoordinatorProtocol.swift
+++ b/WireUI/Sources/WireMainNavigationUI/Protocols/Coordinator/MainCoordinatorProtocol.swift
@@ -35,12 +35,16 @@ public protocol MainCoordinatorProtocol: AnyObject {
func showArchive() async
@MainActor
func showSettings() async
+ @MainActor
+ func showMeetings() async
@MainActor
func showConversation(conversation: ConversationModel, message: ConversationMessageModel?) async
/// This method will be called by the custom back button in the conversation content screen.
@MainActor
func hideConversation()
+ @MainActor
+ func hideMeetings()
@MainActor
func showSettingsContent(_ topLevelMenuItem: SettingsTopLevelMenuItem)
diff --git a/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/Contents.json b/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/Contents.json
new file mode 100644
index 00000000000..73c00596a7f
--- /dev/null
+++ b/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCall.imageset/Contents.json b/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCall.imageset/Contents.json
new file mode 100644
index 00000000000..e388c79f4e0
--- /dev/null
+++ b/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCall.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "VideoCall.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCall.imageset/VideoCall.svg b/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCall.imageset/VideoCall.svg
new file mode 100644
index 00000000000..08d70c6877f
--- /dev/null
+++ b/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCall.imageset/VideoCall.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCallFilled.imageset/Contents.json b/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCallFilled.imageset/Contents.json
new file mode 100644
index 00000000000..af3b035e2fe
--- /dev/null
+++ b/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCallFilled.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "VideoCallFilled.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCallFilled.imageset/VideoCallFilled.svg b/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCallFilled.imageset/VideoCallFilled.svg
new file mode 100644
index 00000000000..ce8f6a70196
--- /dev/null
+++ b/WireUI/Sources/WireMainNavigationUI/Resources/Images.xcassets/VideoCallFilled.imageset/VideoCallFilled.svg
@@ -0,0 +1,4 @@
+
diff --git a/WireUI/Sources/WireMainNavigationUI/Resources/Localization/Accessibility.xcstrings b/WireUI/Sources/WireMainNavigationUI/Resources/Localization/Accessibility.xcstrings
index f2b02e5e6f9..ec659dae2e1 100644
--- a/WireUI/Sources/WireMainNavigationUI/Resources/Localization/Accessibility.xcstrings
+++ b/WireUI/Sources/WireMainNavigationUI/Resources/Localization/Accessibility.xcstrings
@@ -497,6 +497,28 @@
}
}
},
+ "tabBar.meetings.description" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Meetings"
+ }
+ }
+ }
+ },
+ "tabBar.meetings.hint" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "List of meetings"
+ }
+ }
+ }
+ },
"tabBar.settings.description" : {
"localizations" : {
"ar" : {
@@ -747,4 +769,4 @@
}
},
"version" : "1.0"
-}
+}
\ No newline at end of file
diff --git a/WireUI/Sources/WireMainNavigationUI/Resources/Localization/Localizable.xcstrings b/WireUI/Sources/WireMainNavigationUI/Resources/Localization/Localizable.xcstrings
index b01fa011906..cd8aaa4685f 100644
--- a/WireUI/Sources/WireMainNavigationUI/Resources/Localization/Localizable.xcstrings
+++ b/WireUI/Sources/WireMainNavigationUI/Resources/Localization/Localizable.xcstrings
@@ -249,6 +249,17 @@
}
}
},
+ "tabBar.meetings.title" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Meetings"
+ }
+ }
+ }
+ },
"tabBar.settings.title" : {
"localizations" : {
"ar" : {
@@ -375,4 +386,4 @@
}
},
"version" : "1.0"
-}
+}
\ No newline at end of file
diff --git a/WireUI/Sources/WireSidebarUI/Models/SidebarModel.swift b/WireUI/Sources/WireSidebarUI/Models/SidebarModel.swift
index a6c02be1059..dd6b38a673a 100644
--- a/WireUI/Sources/WireSidebarUI/Models/SidebarModel.swift
+++ b/WireUI/Sources/WireSidebarUI/Models/SidebarModel.swift
@@ -41,6 +41,7 @@ final class SidebarModel: ObservableObject {
}
@Published var showUnreadFilters: Bool = false
+ @Published var showMeetings: Bool = false
let accountImageAction: () -> Void
let menuItemAction: (_ selectedMenuItem: SidebarSelectableMenuItem) -> Void
diff --git a/WireUI/Sources/WireSidebarUI/Models/SidebarSelectableMenuItem.swift b/WireUI/Sources/WireSidebarUI/Models/SidebarSelectableMenuItem.swift
index 2884e9bf1c4..e43f7d7003d 100644
--- a/WireUI/Sources/WireSidebarUI/Models/SidebarSelectableMenuItem.swift
+++ b/WireUI/Sources/WireSidebarUI/Models/SidebarSelectableMenuItem.swift
@@ -30,6 +30,7 @@ public enum SidebarSelectableMenuItem: Comparable, Sendable, Hashable {
case replies
case drafts
case folders
+ case meetings
// archived conversations
case archive
// bottom
diff --git a/WireUI/Sources/WireSidebarUI/Resources/Localization/en.lproj/Localizable.strings b/WireUI/Sources/WireSidebarUI/Resources/Localization/en.lproj/Localizable.strings
index 21cbacf06a9..9f83e2797b9 100644
--- a/WireUI/Sources/WireSidebarUI/Resources/Localization/en.lproj/Localizable.strings
+++ b/WireUI/Sources/WireSidebarUI/Resources/Localization/en.lproj/Localizable.strings
@@ -31,6 +31,9 @@
"sidebar.conversation_filter.folders.title" = "Folders";
"sidebar.conversation_filter.archived.title" = "Archive";
+"sidebar.meetings.title" = "Meetings";
+"sidebar.meetings.all_meetings.title" = "All Meetings";
+
"sidebar.contacts.title" = "Contacts";
"sidebar.contacts.connect.title" = "Connect";
diff --git a/WireUI/Sources/WireSidebarUI/Views/Preview/SidebarPreview.swift b/WireUI/Sources/WireSidebarUI/Views/Preview/SidebarPreview.swift
index d7994130ace..ca874723cd4 100644
--- a/WireUI/Sources/WireSidebarUI/Views/Preview/SidebarPreview.swift
+++ b/WireUI/Sources/WireSidebarUI/Views/Preview/SidebarPreview.swift
@@ -49,6 +49,7 @@ struct SidebarPreview: View {
accountInfo: accountInfo,
selectedMenuItem: $selectedMenuItem,
showUnreadFilters: false,
+ showMeetings: false,
accountImageAction: {},
foldersAction: { _ in },
supportAction: {},
diff --git a/WireUI/Sources/WireSidebarUI/Views/SidebarView.swift b/WireUI/Sources/WireSidebarUI/Views/SidebarView.swift
index bb1b48b1c93..0c5ba53e34f 100644
--- a/WireUI/Sources/WireSidebarUI/Views/SidebarView.swift
+++ b/WireUI/Sources/WireSidebarUI/Views/SidebarView.swift
@@ -27,6 +27,7 @@ public struct SidebarView:
public var accountInfo: SidebarAccountInfo?
@Binding public var selectedMenuItem: SidebarSelectableMenuItem
public var showUnreadFilters: Bool
+ public var showMeetings: Bool
private(set) var accountImageAction: () -> Void
private(set) var foldersAction: (CGRect) -> Void
@@ -44,6 +45,7 @@ public struct SidebarView:
accountInfo: SidebarAccountInfo,
selectedMenuItem: Binding,
showUnreadFilters: Bool,
+ showMeetings: Bool,
accountImageAction: @escaping () -> Void,
foldersAction: @escaping (_ buttonFrame: CGRect) -> Void,
supportAction: @escaping () -> Void,
@@ -53,6 +55,7 @@ public struct SidebarView:
self.accountInfo = accountInfo
_selectedMenuItem = selectedMenuItem
self.showUnreadFilters = showUnreadFilters
+ self.showMeetings = showMeetings
self.accountImageAction = accountImageAction
self.foldersAction = foldersAction
self.supportAction = supportAction
@@ -137,6 +140,12 @@ public struct SidebarView:
// Additional filters
selectableMenuItem(.folders)
selectableMenuItem(.archive)
+
+ // Meetings
+ if showMeetings {
+ menuItemHeader(Strings.Meetings.title, addTopPadding: false)
+ selectableMenuItem(.meetings)
+ }
}
.padding(.horizontal, 16)
}
@@ -262,6 +271,11 @@ public struct SidebarView:
icon = "archivebox"
accessibilityLabel = Text(Strings.ConversationFilter.Archived.title)
+ case .meetings:
+ text = Text(Strings.Meetings.AllMeetings.title)
+ icon = "video"
+ accessibilityLabel = Text(Strings.Meetings.AllMeetings.title)
+
case .settings:
text = Text(Strings.Settings.title)
icon = "gearshape"
diff --git a/WireUI/Sources/WireSidebarUI/Views/SidebarViewController.swift b/WireUI/Sources/WireSidebarUI/Views/SidebarViewController.swift
index a007a543590..0b3abe8afc6 100644
--- a/WireUI/Sources/WireSidebarUI/Views/SidebarViewController.swift
+++ b/WireUI/Sources/WireSidebarUI/Views/SidebarViewController.swift
@@ -94,6 +94,11 @@ public final class SidebarViewController: UIViewController {
set { model.showUnreadFilters = newValue }
}
+ public var showMeetings: Bool {
+ get { model.showMeetings }
+ set { model.showMeetings = newValue }
+ }
+
// MARK: - Private Properties
private var model: SidebarModel!
@@ -176,6 +181,7 @@ private struct SidebarAdapter