diff --git a/.gitignore b/.gitignore index db1ee361..ad40381a 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,4 @@ iOSInjectionProject/ .Trashes ehthumbs.db Thumbs.db +Shared/Generated/Strings.swift diff --git a/JellyfinPlayer tvOS/Components/MediaPlayButtonRowView.swift b/JellyfinPlayer tvOS/Components/MediaPlayButtonRowView.swift index 810f7662..4e6ef971 100644 --- a/JellyfinPlayer tvOS/Components/MediaPlayButtonRowView.swift +++ b/JellyfinPlayer tvOS/Components/MediaPlayButtonRowView.swift @@ -20,7 +20,7 @@ struct MediaPlayButtonRowView: View { NavigationLink(destination: VideoPlayerView(item: viewModel.item).ignoresSafeArea()) { MediaViewActionButton(icon: "play.fill", scrollView: $wrappedScrollView) } - Text(viewModel.item.getItemProgressString() != "" ? "\(viewModel.item.getItemProgressString()) left" : "Play") + Text(viewModel.item.getItemProgressString() != "" ? "\(viewModel.item.getItemProgressString()) left" : L10n.play) .font(.caption) } VStack { diff --git a/JellyfinPlayer tvOS/Components/PortraitItemElement.swift b/JellyfinPlayer tvOS/Components/PortraitItemElement.swift index e2290ee3..1a7ec3c6 100644 --- a/JellyfinPlayer tvOS/Components/PortraitItemElement.swift +++ b/JellyfinPlayer tvOS/Components/PortraitItemElement.swift @@ -68,7 +68,7 @@ struct PortraitItemElement: View { .font(.caption) .fontWeight(.medium) } else { - Text("S\(String(item.parentIndexNumber ?? 0)):E\(String(item.indexNumber ?? 0))") + Text(L10n.seasonAndEpisode(String(item.parentIndexNumber ?? 0), String(item.indexNumber ?? 0))) .foregroundColor(.secondary) .font(.caption) .fontWeight(.medium) diff --git a/JellyfinPlayer tvOS/Views/BasicAppSettingsView.swift b/JellyfinPlayer tvOS/Views/BasicAppSettingsView.swift index 2ff8fe9b..241557f9 100644 --- a/JellyfinPlayer tvOS/Views/BasicAppSettingsView.swift +++ b/JellyfinPlayer tvOS/Views/BasicAppSettingsView.swift @@ -12,17 +12,17 @@ import Stinsen import SwiftUI struct BasicAppSettingsView: View { - + @EnvironmentObject var basicAppSettingsRouter: BasicAppSettingsCoordinator.Router @ObservedObject var viewModel: BasicAppSettingsViewModel @State var resetTapped: Bool = false - + @Default(.appAppearance) var appAppearance - + var body: some View { Form { Section { - Picker(NSLocalizedString("Appearance", comment: ""), selection: $appAppearance) { + Picker(L10n.appearance, selection: $appAppearance) { ForEach(self.viewModel.appearances, id: \.self) { appearance in Text(appearance.localizedName).tag(appearance.rawValue) } @@ -30,21 +30,21 @@ struct BasicAppSettingsView: View { UIApplication.shared.windows.first?.overrideUserInterfaceStyle = appAppearance.style }) } header: { - Text("Accessibility") + L10n.accessibility.text } - + Button { resetTapped = true } label: { - Text("Reset") + L10n.reset.text } } - .alert("Reset", isPresented: $resetTapped, actions: { + .alert(L10n.reset, isPresented: $resetTapped, actions: { Button(role: .destructive) { viewModel.reset() basicAppSettingsRouter.dismissCoordinator() } label: { - Text("Reset") + L10n.reset.text } }) .navigationTitle("Settings") diff --git a/JellyfinPlayer tvOS/Views/ConnectToServerView.swift b/JellyfinPlayer tvOS/Views/ConnectToServerView.swift index ce981f88..f47ff0b5 100644 --- a/JellyfinPlayer tvOS/Views/ConnectToServerView.swift +++ b/JellyfinPlayer tvOS/Views/ConnectToServerView.swift @@ -10,14 +10,14 @@ import SwiftUI import Stinsen struct ConnectToServerView: View { - + @StateObject var viewModel = ConnectToServerViewModel() @State var uri = "" - + var body: some View { List { Section { - TextField(NSLocalizedString("Server URL", comment: ""), text: $uri) + TextField(L10n.serverURL, text: $uri) .disableAutocorrection(true) .autocapitalization(.none) .keyboardType(.URL) @@ -25,7 +25,7 @@ struct ConnectToServerView: View { viewModel.connectToServer(uri: uri) } label: { HStack { - Text("Connect") + L10n.connect.text Spacer() if viewModel.isLoading { ProgressView() @@ -36,8 +36,8 @@ struct ConnectToServerView: View { } header: { Text("Connect to a Jellyfin server") } - - Section(header: Text("Local Servers")) { + + Section(header: L10n.localServers.text) { if viewModel.searching { ProgressView() } @@ -68,6 +68,6 @@ struct ConnectToServerView: View { message: Text(viewModel.errorMessage?.displayMessage ?? "Unknown Error"), dismissButton: .cancel()) } - .navigationTitle("Connect") + .navigationTitle(L10n.connect) } } diff --git a/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift b/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift index 0ca800bd..02a85f82 100644 --- a/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift +++ b/JellyfinPlayer tvOS/Views/ContinueWatchingView.swift @@ -16,11 +16,11 @@ struct ContinueWatchingView: View { @Namespace private var namespace var homeRouter: HomeCoordinator.Router? = RouterStore.shared.retrieve() - + var body: some View { VStack(alignment: .leading) { if items.count > 0 { - Text("Continue Watching") + L10n.continueWatching.text .font(.headline) .fontWeight(.semibold) .padding(.leading, 90) diff --git a/JellyfinPlayer tvOS/Views/HomeView.swift b/JellyfinPlayer tvOS/Views/HomeView.swift index 6ab1d03f..46956a2e 100644 --- a/JellyfinPlayer tvOS/Views/HomeView.swift +++ b/JellyfinPlayer tvOS/Views/HomeView.swift @@ -38,7 +38,7 @@ struct HomeView: View { self.homeRouter.route(to: \.modalLibrary, (.init(parentID: libraryID, filters: viewModel.recentFilterSet), title: library?.name ?? "")) } label: { HStack { - Text("Latest \(library?.name ?? "")") + Text(L10n.latestWithString(library?.name ?? "")) .font(.headline) .fontWeight(.semibold) Image(systemName: "chevron.forward.circle.fill") diff --git a/JellyfinPlayer tvOS/Views/ItemView/EpisodeItemView.swift b/JellyfinPlayer tvOS/Views/ItemView/EpisodeItemView.swift index 7343fdc9..d0632bf8 100644 --- a/JellyfinPlayer tvOS/Views/ItemView/EpisodeItemView.swift +++ b/JellyfinPlayer tvOS/Views/ItemView/EpisodeItemView.swift @@ -76,7 +76,7 @@ struct EpisodeItemView: View { HStack(alignment: .top) { VStack(alignment: .trailing) { if studio != nil { - Text("STUDIO") + L10n.studio.text .font(.body) .fontWeight(.semibold) .foregroundColor(.primary) @@ -88,7 +88,7 @@ struct EpisodeItemView: View { } if director != nil { - Text("DIRECTOR") + L10n.director.text .font(.body) .fontWeight(.semibold) .foregroundColor(.primary) @@ -100,7 +100,7 @@ struct EpisodeItemView: View { } if !actors.isEmpty { - Text("CAST") + L10n.cast.text .font(.body) .fontWeight(.semibold) .foregroundColor(.primary) @@ -133,7 +133,7 @@ struct EpisodeItemView: View { NavigationLink(destination: VideoPlayerView(item: viewModel.item).ignoresSafeArea()) { MediaViewActionButton(icon: "play.fill") } - Text(viewModel.item.getItemProgressString() != "" ? "\(viewModel.item.getItemProgressString()) left" : "Play") + Text(viewModel.item.getItemProgressString() != "" ? "\(viewModel.item.getItemProgressString()) left" : L10n.play) .font(.caption) } VStack { @@ -152,7 +152,7 @@ struct EpisodeItemView: View { }.padding(.top, 50) if !viewModel.similarItems.isEmpty { - Text("More Like This") + L10n.moreLikeThis.text .font(.headline) .fontWeight(.semibold) ScrollView(.horizontal) { diff --git a/JellyfinPlayer tvOS/Views/ItemView/ItemView.swift b/JellyfinPlayer tvOS/Views/ItemView/ItemView.swift index b62d0702..32e707ac 100644 --- a/JellyfinPlayer tvOS/Views/ItemView/ItemView.swift +++ b/JellyfinPlayer tvOS/Views/ItemView/ItemView.swift @@ -40,7 +40,7 @@ struct ItemView: View { } else if item.type == "Episode" { EpisodeItemView(viewModel: .init(item: item)) } else { - Text("Type: \(item.type ?? "") not implemented yet :(") + Text(L10n.notImplementedYetWithType(item.type ?? "")) } } } diff --git a/JellyfinPlayer tvOS/Views/ItemView/MovieItemView.swift b/JellyfinPlayer tvOS/Views/ItemView/MovieItemView.swift index e24db62b..378c40da 100644 --- a/JellyfinPlayer tvOS/Views/ItemView/MovieItemView.swift +++ b/JellyfinPlayer tvOS/Views/ItemView/MovieItemView.swift @@ -77,7 +77,7 @@ struct MovieItemView: View { HStack { VStack(alignment: .trailing) { if studio != nil { - Text("STUDIO") + L10n.studio.text .font(.body) .fontWeight(.semibold) .foregroundColor(.primary) @@ -89,7 +89,7 @@ struct MovieItemView: View { } if director != nil { - Text("DIRECTOR") + L10n.director.text .font(.body) .fontWeight(.semibold) .foregroundColor(.primary) @@ -101,7 +101,7 @@ struct MovieItemView: View { } if !actors.isEmpty { - Text("CAST") + L10n.cast.text .font(.body) .fontWeight(.semibold) .foregroundColor(.primary) @@ -133,7 +133,7 @@ struct MovieItemView: View { }.padding(.top, 50) if !viewModel.similarItems.isEmpty { - Text("More Like This") + L10n.moreLikeThis.text .font(.headline) .fontWeight(.semibold) ScrollView(.horizontal) { diff --git a/JellyfinPlayer tvOS/Views/ItemView/SeasonItemView.swift b/JellyfinPlayer tvOS/Views/ItemView/SeasonItemView.swift index b04a1c6b..475102fd 100644 --- a/JellyfinPlayer tvOS/Views/ItemView/SeasonItemView.swift +++ b/JellyfinPlayer tvOS/Views/ItemView/SeasonItemView.swift @@ -95,7 +95,7 @@ struct SeasonItemView: View { }.padding(.top, 50) if !viewModel.episodes.isEmpty { - Text("Episodes") + L10n.episodes.text .font(.headline) .fontWeight(.semibold) ScrollView(.horizontal) { diff --git a/JellyfinPlayer tvOS/Views/ItemView/SeriesItemView.swift b/JellyfinPlayer tvOS/Views/ItemView/SeriesItemView.swift index 84c46316..6a3a2ee3 100644 --- a/JellyfinPlayer tvOS/Views/ItemView/SeriesItemView.swift +++ b/JellyfinPlayer tvOS/Views/ItemView/SeriesItemView.swift @@ -79,7 +79,7 @@ struct SeriesItemView: View { HStack { VStack(alignment: .trailing) { if studio != nil { - Text("STUDIO") + L10n.studio.text .font(.body) .fontWeight(.semibold) .foregroundColor(.primary) @@ -91,7 +91,7 @@ struct SeriesItemView: View { } if director != nil { - Text("DIRECTOR") + L10n.director.text .font(.body) .fontWeight(.semibold) .foregroundColor(.primary) @@ -103,7 +103,7 @@ struct SeriesItemView: View { } if !actors.isEmpty { - Text("CAST") + L10n.cast.text .font(.body) .fontWeight(.semibold) .foregroundColor(.primary) @@ -135,7 +135,7 @@ struct SeriesItemView: View { } }.padding(.top, 50) if !viewModel.seasons.isEmpty { - Text("Seasons") + L10n.seasons.text .font(.headline) .fontWeight(.semibold) ScrollView(.horizontal) { @@ -153,7 +153,7 @@ struct SeriesItemView: View { } if !viewModel.similarItems.isEmpty { - Text("More Like This") + L10n.moreLikeThis.text .font(.headline) .fontWeight(.semibold) ScrollView(.horizontal) { diff --git a/JellyfinPlayer tvOS/Views/LibraryFilterView.swift b/JellyfinPlayer tvOS/Views/LibraryFilterView.swift index daf9c75c..cf77babe 100644 --- a/JellyfinPlayer tvOS/Views/LibraryFilterView.swift +++ b/JellyfinPlayer tvOS/Views/LibraryFilterView.swift @@ -10,7 +10,7 @@ import Stinsen import SwiftUI struct LibraryFilterView: View { - + @EnvironmentObject var filterRouter: FilterCoordinator.Router @Binding var filters: LibraryFilters var parentId: String = "" @@ -31,32 +31,32 @@ struct LibraryFilterView: View { } else { Form { if viewModel.enabledFilterType.contains(.genre) { - MultiSelector(label: NSLocalizedString("Genres", comment: ""), + MultiSelector(label: L10n.genres, options: viewModel.possibleGenres, optionToString: { $0.name ?? "" }, selected: $viewModel.modifiedFilters.withGenres) } if viewModel.enabledFilterType.contains(.filter) { - MultiSelector(label: NSLocalizedString("Filters", comment: ""), + MultiSelector(label: L10n.filters, options: viewModel.possibleItemFilters, optionToString: { $0.localized }, selected: $viewModel.modifiedFilters.filters) } if viewModel.enabledFilterType.contains(.tag) { - MultiSelector(label: NSLocalizedString("Tags", comment: ""), + MultiSelector(label: L10n.tags, options: viewModel.possibleTags, optionToString: { $0 }, selected: $viewModel.modifiedFilters.tags) } if viewModel.enabledFilterType.contains(.sortBy) { - Picker(selection: $viewModel.selectedSortBy, label: Text("Sort by")) { + Picker(selection: $viewModel.selectedSortBy, label: L10n.sortBy.text) { ForEach(viewModel.possibleSortBys, id: \.self) { so in Text(so.localized).tag(so) } } } if viewModel.enabledFilterType.contains(.sortOrder) { - Picker(selection: $viewModel.selectedSortOrder, label: Text("Display order")) { + Picker(selection: $viewModel.selectedSortOrder, label: L10n.displayOrder.text) { ForEach(viewModel.possibleSortOrders, id: \.self) { so in Text(so.rawValue).tag(so) } @@ -68,7 +68,7 @@ struct LibraryFilterView: View { self.filters = viewModel.modifiedFilters filterRouter.dismissCoordinator() } label: { - Text("Reset") + L10n.reset.text } } } @@ -86,7 +86,7 @@ struct LibraryFilterView: View { self.filters = viewModel.modifiedFilters filterRouter.dismissCoordinator() } label: { - Text("Apply") + L10n.apply.text } } } diff --git a/JellyfinPlayer tvOS/Views/LibrarySearchView.swift b/JellyfinPlayer tvOS/Views/LibrarySearchView.swift index db3490bb..2cc5c706 100644 --- a/JellyfinPlayer tvOS/Views/LibrarySearchView.swift +++ b/JellyfinPlayer tvOS/Views/LibrarySearchView.swift @@ -44,7 +44,7 @@ struct LibrarySearchView: View { var suggestionsListView: some View { ScrollView { LazyVStack(spacing: 8) { - Text("Suggestions") + L10n.suggestions.text .font(.headline) .fontWeight(.bold) .foregroundColor(.primary) diff --git a/JellyfinPlayer tvOS/Views/LibraryView.swift b/JellyfinPlayer tvOS/Views/LibraryView.swift index ca272c9e..0f500499 100644 --- a/JellyfinPlayer tvOS/Views/LibraryView.swift +++ b/JellyfinPlayer tvOS/Views/LibraryView.swift @@ -88,7 +88,7 @@ struct LibraryView: View { .ignoresSafeArea(.all) } else { VStack { - Text("No results.") + L10n.noResults.text Button { } label: { Text("Reload") } diff --git a/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift b/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift index 9e388718..ab87ce24 100644 --- a/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift +++ b/JellyfinPlayer tvOS/Views/MovieLibrariesView.swift @@ -14,7 +14,7 @@ struct MovieLibrariesView: View { @EnvironmentObject var movieLibrariesRouter: MovieLibrariesCoordinator.Router @StateObject var viewModel: MovieLibrariesViewModel var title: String - + var body: some View { if viewModel.isLoading == true { ProgressView() @@ -77,7 +77,7 @@ struct MovieLibrariesView: View { .ignoresSafeArea(.all) } else { VStack { - Text("No results.") + L10n.noResults.text Button { print("movieLibraries reload") } label: { diff --git a/JellyfinPlayer tvOS/Views/NextUpView.swift b/JellyfinPlayer tvOS/Views/NextUpView.swift index 3de41d6b..aa68e8ac 100644 --- a/JellyfinPlayer tvOS/Views/NextUpView.swift +++ b/JellyfinPlayer tvOS/Views/NextUpView.swift @@ -13,13 +13,13 @@ import Stinsen struct NextUpView: View { var items: [BaseItemDto] - + var homeRouter: HomeCoordinator.Router? = RouterStore.shared.retrieve() var body: some View { VStack(alignment: .leading) { if items.count > 0 { - Text("Next Up") + L10n.nextUp.text .font(.headline) .fontWeight(.semibold) .padding(.leading, 90) diff --git a/JellyfinPlayer tvOS/Views/ServerListView.swift b/JellyfinPlayer tvOS/Views/ServerListView.swift index e89388e8..d416ce73 100644 --- a/JellyfinPlayer tvOS/Views/ServerListView.swift +++ b/JellyfinPlayer tvOS/Views/ServerListView.swift @@ -11,10 +11,10 @@ import CoreStore import SwiftUI struct ServerListView: View { - + @EnvironmentObject var serverListRouter: ServerListCoordinator.Router @ObservedObject var viewModel: ServerListViewModel - + @ViewBuilder private var listView: some View { ScrollView { @@ -27,22 +27,22 @@ struct ServerListView: View { Image(systemName: "server.rack") .font(.system(size: 72)) .foregroundColor(.primary) - + VStack(alignment: .leading, spacing: 5) { Text(server.name) .font(.title2) .foregroundColor(.primary) - + Text(server.currentURI) .font(.footnote) .disabled(true) .foregroundColor(.secondary) - + Text(viewModel.userTextFor(server: server)) .font(.footnote) .foregroundColor(.primary) } - + Spacer() } } @@ -60,7 +60,7 @@ struct ServerListView: View { } .padding(.top, 50) } - + @ViewBuilder private var noServerView: some View { VStack { @@ -68,18 +68,18 @@ struct ServerListView: View { .frame(minWidth: 50, maxWidth: 500) .multilineTextAlignment(.center) .font(.callout) - + Button { serverListRouter.route(to: \.connectToServer) } label: { - Text("Connect") + L10n.connect.text .bold() .font(.callout) } .padding(.top, 40) } } - + @ViewBuilder private var innerBody: some View { if viewModel.servers.isEmpty { @@ -89,7 +89,7 @@ struct ServerListView: View { listView } } - + @ViewBuilder private var trailingToolbarContent: some View { if viewModel.servers.isEmpty { @@ -109,7 +109,7 @@ struct ServerListView: View { } } } - + var body: some View { innerBody .navigationTitle("Servers") diff --git a/JellyfinPlayer tvOS/Views/SettingsView.swift b/JellyfinPlayer tvOS/Views/SettingsView.swift index 7442eeeb..21c27444 100644 --- a/JellyfinPlayer tvOS/Views/SettingsView.swift +++ b/JellyfinPlayer tvOS/Views/SettingsView.swift @@ -22,7 +22,7 @@ struct SettingsView: View { var body: some View { Form { - Section(header: Text("Playback settings")) { + Section(header: L10n.playbackSettings.text) { Picker("Default local quality", selection: $inNetworkStreamBitrate) { ForEach(self.viewModel.bitrates, id: \.self) { bitrate in Text(bitrate.name).tag(bitrate.value) @@ -36,7 +36,7 @@ struct SettingsView: View { } } - Section(header: Text("Accessibility")) { + Section(header: L10n.accessibility.text) { Toggle("Automatically show subtitles", isOn: $isAutoSelectSubtitles) SearchablePicker(label: "Preferred subtitle language", options: viewModel.langs, @@ -58,12 +58,12 @@ struct SettingsView: View { Section(header: Text(SessionManager.main.currentLogin.server.name)) { HStack { - Text("Signed in as \(SessionManager.main.currentLogin.user.username)").foregroundColor(.primary) + Text(L10n.signedInAsWithString(SessionManager.main.currentLogin.user.username)).foregroundColor(.primary) Spacer() Button { SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil) } label: { - Text("Switch user").font(.callout) + L10n.switchUser.text.font(.callout) } } Button { diff --git a/JellyfinPlayer tvOS/Views/TVLibrariesView.swift b/JellyfinPlayer tvOS/Views/TVLibrariesView.swift index 3ae4d8df..25c06ceb 100644 --- a/JellyfinPlayer tvOS/Views/TVLibrariesView.swift +++ b/JellyfinPlayer tvOS/Views/TVLibrariesView.swift @@ -14,7 +14,7 @@ struct TVLibrariesView: View { @EnvironmentObject var tvLibrariesRouter: TVLibrariesCoordinator.Router @StateObject var viewModel: TVLibrariesViewModel var title: String - + var body: some View { if viewModel.isLoading == true { ProgressView() @@ -77,7 +77,7 @@ struct TVLibrariesView: View { .ignoresSafeArea(.all) } else { VStack { - Text("No results.") + L10n.noResults.text Button { print("tvLibraries reload") } label: { diff --git a/JellyfinPlayer tvOS/Views/UserListView.swift b/JellyfinPlayer tvOS/Views/UserListView.swift index 85bcbe52..bc7c93f9 100644 --- a/JellyfinPlayer tvOS/Views/UserListView.swift +++ b/JellyfinPlayer tvOS/Views/UserListView.swift @@ -10,10 +10,10 @@ import SwiftUI struct UserListView: View { - + @EnvironmentObject var userListRouter: UserListCoordinator.Router @ObservedObject var viewModel: UserListViewModel - + @ViewBuilder private var listView: some View { ScrollView { @@ -25,9 +25,9 @@ struct UserListView: View { HStack { Text(user.username) .font(.title2) - + Spacer() - + if viewModel.isLoading { ProgressView() } @@ -47,7 +47,7 @@ struct UserListView: View { } .padding(.top, 50) } - + @ViewBuilder private var noUserView: some View { VStack { @@ -55,7 +55,7 @@ struct UserListView: View { .frame(minWidth: 50, maxWidth: 500) .multilineTextAlignment(.center) .font(.callout) - + Button { userListRouter.route(to: \.userSignIn, viewModel.server) } label: { @@ -66,7 +66,7 @@ struct UserListView: View { .padding(.top, 40) } } - + @ViewBuilder private var innerBody: some View { if viewModel.users.isEmpty { @@ -76,7 +76,7 @@ struct UserListView: View { listView } } - + @ViewBuilder private var toolbarContent: some View { if viewModel.users.isEmpty { @@ -91,7 +91,7 @@ struct UserListView: View { } } } - + var body: some View { innerBody .navigationTitle(viewModel.server.name) diff --git a/JellyfinPlayer tvOS/Views/UserSignInView.swift b/JellyfinPlayer tvOS/Views/UserSignInView.swift index f9b59ba6..2e78d2b5 100644 --- a/JellyfinPlayer tvOS/Views/UserSignInView.swift +++ b/JellyfinPlayer tvOS/Views/UserSignInView.swift @@ -11,28 +11,28 @@ import SwiftUI import Stinsen struct UserSignInView: View { - + @ObservedObject var viewModel: UserSignInViewModel @State private var username: String = "" @State private var password: String = "" - + var body: some View { Form { - + Section { - TextField("Username", text: $username) + TextField(L10n.username, text: $username) .disableAutocorrection(true) .autocapitalization(.none) - - SecureField("Password", text: $password) + + SecureField(L10n.password, text: $password) .disableAutocorrection(true) .autocapitalization(.none) - + Button { viewModel.login(username: username, password: password) } label: { HStack { - Text("Connect") + L10n.connect.text Spacer() if viewModel.isLoading { ProgressView() diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/AudioView.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/AudioView.swift index 3d5b4162..9b198f94 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/AudioView.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/AudioView.swift @@ -14,7 +14,7 @@ class AudioViewController: InfoTabViewController { override func viewDidLoad() { super.viewDidLoad() - tabBarItem.title = NSLocalizedString("Audio", comment: "") + tabBarItem.title = "Audio" } diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/MediaInfoView.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/MediaInfoView.swift index 94c3a5c0..e914b3ad 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/MediaInfoView.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/MediaInfoView.swift @@ -16,7 +16,7 @@ class MediaInfoViewController: InfoTabViewController { override func viewDidLoad() { super.viewDidLoad() - tabBarItem.title = NSLocalizedString("Info", comment: "") + tabBarItem.title = "Info" } func setMedia(item: BaseItemDto) { diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/SubtitlesView.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/SubtitlesView.swift index 240d6828..a4e35717 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/SubtitlesView.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/SubtitlesView.swift @@ -14,7 +14,7 @@ class SubtitlesViewController: InfoTabViewController { override func viewDidLoad() { super.viewDidLoad() - tabBarItem.title = NSLocalizedString("Subtitles", comment: "") + tabBarItem.title = "Subtitles" } diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/VideoPlayerViewController.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/VideoPlayerViewController.swift index d99c47e9..3e20b5ef 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/VideoPlayerViewController.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/VideoPlayerViewController.swift @@ -138,7 +138,7 @@ class VideoPlayerViewController: UIViewController, VideoPlayerSettingsDelegate, let builder = DeviceProfileBuilder() builder.setMaxBitrate(bitrate: maxBitrate) let profile = builder.buildProfile() - + let currentUser = SessionManager.main.currentLogin.user let playbackInfo = PlaybackInfoDto(userId: currentUser.id, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, deviceProfile: profile, autoOpenLiveStream: true) diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index d06a9ed3..2fc34365 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 09389CC726819B4600AE350E /* VideoPlayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09389CC626819B4500AE350E /* VideoPlayerModel.swift */; }; 09389CC826819B4600AE350E /* VideoPlayerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09389CC626819B4500AE350E /* VideoPlayerModel.swift */; }; 0959A5FD2686D29800C7C9A9 /* VideoUpNextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0959A5FC2686D29800C7C9A9 /* VideoUpNextView.swift */; }; + 363CADF08820D3B2055CF1D8 /* Pods_JellyfinPlayer_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BE2D324B040DCA2629C110D /* Pods_JellyfinPlayer_tvOS.framework */; }; 531069572684E7EE00CFFDBA /* InfoTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */; }; 531069582684E7EE00CFFDBA /* MediaInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069512684E7EE00CFFDBA /* MediaInfoView.swift */; }; 531069592684E7EE00CFFDBA /* SubtitlesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 531069522684E7EE00CFFDBA /* SubtitlesView.swift */; }; @@ -148,12 +149,11 @@ 53DE4BD2267098F300739748 /* SearchBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DE4BD1267098F300739748 /* SearchBarView.swift */; }; 53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DF641D263D9C0600A7CD1A /* LibraryView.swift */; }; 53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53E4E648263F725B00F67C6B /* MultiSelectorView.swift */; }; - 53EC6E1E267E80AC006DD26A /* Pods_JellyfinPlayer_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EBFE1F64394BCC2EFFF1610D /* Pods_JellyfinPlayer_tvOS.framework */; }; - 53EC6E21267E80B1006DD26A /* Pods_JellyfinPlayer_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F905C1D3D3A0C9E13E7A0BC /* Pods_JellyfinPlayer_iOS.framework */; }; 53EE24E6265060780068F029 /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53EE24E5265060780068F029 /* LibrarySearchView.swift */; }; 53F8377D265FF67C00F456B3 /* VideoPlayerSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */; }; 53F866442687A45F00DCD1D7 /* PortraitItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53F866432687A45F00DCD1D7 /* PortraitItemView.swift */; }; 53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */; }; + 560CA59B3956A4CA13EDAC05 /* Pods_JellyfinPlayer_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 86BAC42C3764D232C8DF8F5E /* Pods_JellyfinPlayer_iOS.framework */; }; 62133890265F83A900A81A2A /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6213388F265F83A900A81A2A /* LibraryListView.swift */; }; 621338932660107500A81A2A /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338922660107500A81A2A /* StringExtensions.swift */; }; 621338B32660A07800A81A2A /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 621338B22660A07800A81A2A /* LazyView.swift */; }; @@ -175,6 +175,10 @@ 625CB5752678C33500530A6E /* LibraryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5742678C33500530A6E /* LibraryListViewModel.swift */; }; 625CB5772678C34300530A6E /* ConnectToServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */; }; 625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */ = {isa = PBXBuildFile; productRef = 625CB5792678C4A400530A6E /* ActivityIndicator */; }; + 6264E88A27384A6F0081A12A /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; }; + 6264E88C273850380081A12A /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6264E88B273850380081A12A /* Strings.swift */; }; + 6264E88D273850380081A12A /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6264E88B273850380081A12A /* Strings.swift */; }; + 6264E88E273850380081A12A /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6264E88B273850380081A12A /* Strings.swift */; }; 62671DB327159C1800199D95 /* ItemCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6220D0BF26D61C5000B8E046 /* ItemCoordinator.swift */; }; 6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; }; 6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6267B3D526710B8900A7371D /* CollectionExtensions.swift */; }; @@ -354,17 +358,17 @@ E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; }; E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD09526C47118007C8DCF /* ErrorMessage.swift */; }; E1FCD09726C47118007C8DCF /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD09526C47118007C8DCF /* ErrorMessage.swift */; }; - E1FCD09926C4F358007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; }; E1FCD09A26C4F35A007C8DCF /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD09526C47118007C8DCF /* ErrorMessage.swift */; }; + EABFD69FA6D5DBB248A494AA /* Pods_WidgetExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59AFF849629F3C787909A911 /* Pods_WidgetExtension.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 628B952B2670CABE0091AF3B /* PBXContainerItemProxy */ = { + 6264E888273848760081A12A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 5377CBE9263B596A003A4E83 /* Project object */; proxyType = 1; remoteGlobalIDString = 628B951F2670CABD0091AF3B; - remoteInfo = WidgetExtensionExtension; + remoteInfo = WidgetExtension; }; /* End PBXContainerItemProxy section */ @@ -396,8 +400,10 @@ 091B5A882683142E00D78B61 /* UDPBroadCastConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UDPBroadCastConnection.swift; sourceTree = ""; }; 09389CC626819B4500AE350E /* VideoPlayerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerModel.swift; sourceTree = ""; }; 0959A5FC2686D29800C7C9A9 /* VideoUpNextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoUpNextView.swift; sourceTree = ""; }; - 3773C07648173CE7FEC083D5 /* Pods-JellyfinPlayer iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.debug.xcconfig"; sourceTree = ""; }; - 3F905C1D3D3A0C9E13E7A0BC /* Pods_JellyfinPlayer_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JellyfinPlayer_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 14E199C7BBA98782CAD2F0D4 /* Pods-JellyfinPlayer iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.release.xcconfig"; sourceTree = ""; }; + 20CA36DDD247EED8D16438A5 /* Pods-JellyfinPlayer tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.release.xcconfig"; sourceTree = ""; }; + 4BDCEE3B49CF70A9E9BA3CD8 /* Pods-WidgetExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetExtension.debug.xcconfig"; path = "Target Support Files/Pods-WidgetExtension/Pods-WidgetExtension.debug.xcconfig"; sourceTree = ""; }; + 4BE2D324B040DCA2629C110D /* Pods_JellyfinPlayer_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JellyfinPlayer_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 531069502684E7EE00CFFDBA /* InfoTabBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InfoTabBarViewController.swift; sourceTree = ""; }; 531069512684E7EE00CFFDBA /* MediaInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaInfoView.swift; sourceTree = ""; }; 531069522684E7EE00CFFDBA /* SubtitlesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubtitlesView.swift; sourceTree = ""; }; @@ -495,6 +501,7 @@ 53F8377C265FF67C00F456B3 /* VideoPlayerSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerSettingsView.swift; sourceTree = ""; }; 53F866432687A45F00DCD1D7 /* PortraitItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemView.swift; sourceTree = ""; }; 53FF7F29263CF3F500585C35 /* LatestMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LatestMediaView.swift; sourceTree = ""; }; + 59AFF849629F3C787909A911 /* Pods_WidgetExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_WidgetExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6213388F265F83A900A81A2A /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = ""; }; 621338922660107500A81A2A /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 621338B22660A07800A81A2A /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; @@ -514,6 +521,7 @@ 625CB5762678C34300530A6E /* ConnectToServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerViewModel.swift; sourceTree = ""; }; 625CB57B2678CE1000530A6E /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; 625CB57D2678E81E00530A6E /* TVVLCKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = TVVLCKit.xcframework; path = Carthage/Build/TVVLCKit.xcframework; sourceTree = ""; }; + 6264E88B273850380081A12A /* Strings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Strings.swift; sourceTree = ""; }; 6267B3D526710B8900A7371D /* CollectionExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtensions.swift; sourceTree = ""; }; 6267B3D92671138200A7371D /* ImageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageExtensions.swift; sourceTree = ""; }; 628B95202670CABD0091AF3B /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -540,8 +548,10 @@ 62EC352E267666A5000E9F2D /* SessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionManager.swift; sourceTree = ""; }; 62EC353326766B03000E9F2D /* DeviceRotationViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceRotationViewModifier.swift; sourceTree = ""; }; 62ECA01726FA685A00E8EBB7 /* DeepLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLink.swift; sourceTree = ""; }; + 772F6DAB8534FD1DB45B7687 /* Pods-JellyfinPlayer iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.debug.xcconfig"; sourceTree = ""; }; + 86BAC42C3764D232C8DF8F5E /* Pods_JellyfinPlayer_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JellyfinPlayer_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AE8C3158265D6F90008AA076 /* bitrates.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = bitrates.json; sourceTree = ""; }; - BEEC50E7EFD4848C0E320941 /* Pods-JellyfinPlayer iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer iOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS.release.xcconfig"; sourceTree = ""; }; + B598C62749E5EFD37280FCC3 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.debug.xcconfig"; sourceTree = ""; }; C40CD921271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoviesLibrariesCoordinator.swift; sourceTree = ""; }; C40CD924271F8D1E000FB198 /* MovieLibrariesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieLibrariesViewModel.swift; sourceTree = ""; }; C40CD927271F8DAB000FB198 /* MovieLibrariesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieLibrariesView.swift; sourceTree = ""; }; @@ -551,8 +561,6 @@ C4BE076D2720FEA8003F4AD1 /* PortraitItemElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortraitItemElement.swift; sourceTree = ""; }; C4E508172703E8190045C9AB /* LibraryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryListView.swift; sourceTree = ""; }; C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibrarySearchView.swift; sourceTree = ""; }; - D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.release.xcconfig"; sourceTree = ""; }; - DE5004F745B19E28744A7DE7 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.debug.xcconfig"; sourceTree = ""; }; E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayButtonRowView.swift; sourceTree = ""; }; E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = ""; }; E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = ""; }; @@ -609,7 +617,7 @@ E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = ""; }; E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; E1FCD09526C47118007C8DCF /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = ""; }; - EBFE1F64394BCC2EFFF1610D /* Pods_JellyfinPlayer_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JellyfinPlayer_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + FDEDADB92FA8523BC8432E45 /* Pods-WidgetExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WidgetExtension.release.xcconfig"; path = "Target Support Files/Pods-WidgetExtension/Pods-WidgetExtension.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -619,7 +627,6 @@ files = ( 53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */, E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */, - 53EC6E1E267E80AC006DD26A /* Pods_JellyfinPlayer_tvOS.framework in Frameworks */, E1218CA0271A2CF200EA0737 /* Nuke in Frameworks */, 6220D0C926D63F3700B8E046 /* Stinsen in Frameworks */, 53A431BF266B0FFE0016769F /* JellyfinAPI in Frameworks */, @@ -630,6 +637,7 @@ E13DD3CD27164CA7009D4DAF /* CoreStore in Frameworks */, E12186DE2718F1C50010884C /* Defaults in Frameworks */, 53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */, + 363CADF08820D3B2055CF1D8 /* Pods_JellyfinPlayer_tvOS.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -641,7 +649,6 @@ 53649AAD269CFAEA00A2D8B7 /* Puppy in Frameworks */, 62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */, E1A99999271A3429008E78C0 /* SwiftUICollection in Frameworks */, - 53EC6E21267E80B1006DD26A /* Pods_JellyfinPlayer_iOS.framework in Frameworks */, E1218C9A271A26BA00EA0737 /* Nuke in Frameworks */, E1B6DCEA271A23880015B715 /* SwiftyJSON in Frameworks */, 53352571265EA0A0006CCA86 /* Introspect in Frameworks */, @@ -649,6 +656,7 @@ 625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */, E1B6DCE8271A23780015B715 /* CombineExt in Frameworks */, 53A431BD266B0FF20016769F /* JellyfinAPI in Frameworks */, + 560CA59B3956A4CA13EDAC05 /* Pods_JellyfinPlayer_iOS.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -664,6 +672,7 @@ E13DD3CF27164E1F009D4DAF /* CoreStore in Frameworks */, 628B95352670CAEA0091AF3B /* JellyfinAPI in Frameworks */, E1218C9C271A26C400EA0737 /* Nuke in Frameworks */, + EABFD69FA6D5DBB248A494AA /* Pods_WidgetExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -794,6 +803,7 @@ 535870752669D60C00D05A09 /* Shared */ = { isa = PBXGroup; children = ( + 6286F09F271C0AA500C40ED5 /* Generated */, 62C29E9D26D0FE5900C1D2E7 /* Coordinators */, E1FCD08E26C466F3007C8DCF /* Errors */, 621338912660106C00A81A2A /* Extensions */, @@ -1029,8 +1039,9 @@ 53D5E3DC264B47EE00BADDC8 /* MobileVLCKit.xcframework */, 628B95212670CABD0091AF3B /* WidgetKit.framework */, 628B95232670CABD0091AF3B /* SwiftUI.framework */, - 3F905C1D3D3A0C9E13E7A0BC /* Pods_JellyfinPlayer_iOS.framework */, - EBFE1F64394BCC2EFFF1610D /* Pods_JellyfinPlayer_tvOS.framework */, + 86BAC42C3764D232C8DF8F5E /* Pods_JellyfinPlayer_iOS.framework */, + 4BE2D324B040DCA2629C110D /* Pods_JellyfinPlayer_tvOS.framework */, + 59AFF849629F3C787909A911 /* Pods_WidgetExtension.framework */, ); name = Frameworks; sourceTree = ""; @@ -1062,6 +1073,14 @@ path = Extensions; sourceTree = ""; }; + 6286F09F271C0AA500C40ED5 /* Generated */ = { + isa = PBXGroup; + children = ( + 6264E88B273850380081A12A /* Strings.swift */, + ); + path = Generated; + sourceTree = ""; + }; 628B95252670CABD0091AF3B /* WidgetExtension */ = { isa = PBXGroup; children = ( @@ -1128,10 +1147,12 @@ C78797A232E2B8774099D1E9 /* Pods */ = { isa = PBXGroup; children = ( - 3773C07648173CE7FEC083D5 /* Pods-JellyfinPlayer iOS.debug.xcconfig */, - BEEC50E7EFD4848C0E320941 /* Pods-JellyfinPlayer iOS.release.xcconfig */, - DE5004F745B19E28744A7DE7 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */, - D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */, + 772F6DAB8534FD1DB45B7687 /* Pods-JellyfinPlayer iOS.debug.xcconfig */, + 14E199C7BBA98782CAD2F0D4 /* Pods-JellyfinPlayer iOS.release.xcconfig */, + B598C62749E5EFD37280FCC3 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */, + 20CA36DDD247EED8D16438A5 /* Pods-JellyfinPlayer tvOS.release.xcconfig */, + 4BDCEE3B49CF70A9E9BA3CD8 /* Pods-WidgetExtension.debug.xcconfig */, + FDEDADB92FA8523BC8432E45 /* Pods-WidgetExtension.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1330,11 +1351,12 @@ isa = PBXNativeTarget; buildConfigurationList = 535870712669D21700D05A09 /* Build configuration list for PBXNativeTarget "JellyfinPlayer tvOS" */; buildPhases = ( - E7370E1AA68C6CB254E46F2C /* [CP] Check Pods Manifest.lock */, + 3D0F2756C71CDF6B9EEBD4E0 /* [CP] Check Pods Manifest.lock */, + 6286F0A3271C0ABA00C40ED5 /* R.swift */, 5358705C2669D21600D05A09 /* Sources */, 5358705D2669D21600D05A09 /* Frameworks */, 5358705E2669D21600D05A09 /* Resources */, - 6AB6F1DD2C8AD942F71C8A32 /* [CP] Embed Pods Frameworks */, + 879C22C1CCC48E68C86E904C /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -1362,19 +1384,20 @@ isa = PBXNativeTarget; buildConfigurationList = 5377CC1B263B596B003A4E83 /* Build configuration list for PBXNativeTarget "JellyfinPlayer iOS" */; buildPhases = ( - 6435C3C2E610FE34AD537AC1 /* [CP] Check Pods Manifest.lock */, + 1C7487D3432E90546DA855B5 /* [CP] Check Pods Manifest.lock */, + 6286F09E271C093000C40ED5 /* R.swift */, 5377CBED263B596A003A4E83 /* Sources */, 5377CBEE263B596A003A4E83 /* Frameworks */, 5377CBEF263B596A003A4E83 /* Resources */, 5302F8322658B74800647A2E /* CopyFiles */, 628B95312670CABE0091AF3B /* Embed App Extensions */, - E8DDF21F62DFCE8CE76666BA /* [CP] Embed Pods Frameworks */, - 83FD120CA10FD0E91DAD83C9 /* [CP] Copy Pods Resources */, + 8D1E0C963DCE6C6F328B3EBB /* [CP] Embed Pods Frameworks */, + DB8CA7C37DF78BEDCE4E37C1 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( - 628B952C2670CABE0091AF3B /* PBXTargetDependency */, + 6264E889273848760081A12A /* PBXTargetDependency */, ); name = "JellyfinPlayer iOS"; packageProductDependencies = ( @@ -1398,6 +1421,7 @@ isa = PBXNativeTarget; buildConfigurationList = 628B952E2670CABE0091AF3B /* Build configuration list for PBXNativeTarget "WidgetExtension" */; buildPhases = ( + D4D3981ADF75BCD341D590C0 /* [CP] Check Pods Manifest.lock */, 628B951C2670CABD0091AF3B /* Sources */, 628B951D2670CABD0091AF3B /* Frameworks */, 628B951E2670CABD0091AF3B /* Resources */, @@ -1572,7 +1596,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 6435C3C2E610FE34AD537AC1 /* [CP] Check Pods Manifest.lock */ = { + 1C7487D3432E90546DA855B5 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1594,41 +1618,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 6AB6F1DD2C8AD942F71C8A32 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 83FD120CA10FD0E91DAD83C9 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - E7370E1AA68C6CB254E46F2C /* [CP] Check Pods Manifest.lock */ = { + 3D0F2756C71CDF6B9EEBD4E0 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1650,7 +1640,60 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - E8DDF21F62DFCE8CE76666BA /* [CP] Embed Pods Frameworks */ = { + 6286F09E271C093000C40ED5 /* R.swift */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = R.swift; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${PROJECT_DIR}/bin/swiftgen\"\n"; + }; + 6286F0A3271C0ABA00C40ED5 /* R.swift */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = R.swift; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\"${PROJECT_DIR}/bin/swiftgen\"\n"; + }; + 879C22C1CCC48E68C86E904C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8D1E0C963DCE6C6F328B3EBB /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -1667,6 +1710,45 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + D4D3981ADF75BCD341D590C0 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-WidgetExtension-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + DB8CA7C37DF78BEDCE4E37C1 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JellyfinPlayer iOS/Pods-JellyfinPlayer iOS-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1770,6 +1852,7 @@ 5321753E2671DE9C005491E6 /* Typings.swift in Sources */, E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */, 53ABFDEB2679753200886593 /* ConnectToServerView.swift in Sources */, + 6264E88D273850380081A12A /* Strings.swift in Sources */, 536D3D76267BA9BB0004248C /* MainTabViewModel.swift in Sources */, E193D5512719432400900D82 /* ServerDetailViewModel.swift in Sources */, 5310695C2684E7EE00CFFDBA /* VideoPlayerViewController.swift in Sources */, @@ -1841,6 +1924,7 @@ E173DA5226D04AAF00CC4EB7 /* ColorExtension.swift in Sources */, 53892770263C25230035E14B /* NextUpView.swift in Sources */, E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */, + 6264E88C273850380081A12A /* Strings.swift in Sources */, C4BE0766271FC109003F4AD1 /* TVLibrariesViewModel.swift in Sources */, 62ECA01826FA685A00E8EBB7 /* DeepLink.swift in Sources */, 535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */, @@ -1929,6 +2013,7 @@ files = ( 53649AB3269D3F5B00A2D8B7 /* LogManager.swift in Sources */, E13DD3CB27164BA8009D4DAF /* UIDeviceExtensions.swift in Sources */, + 6264E88A27384A6F0081A12A /* NetworkError.swift in Sources */, E19169D0272514760085832A /* HTTPScheme.swift in Sources */, 6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */, 628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */, @@ -1936,6 +2021,7 @@ E1AD105926D9A543003E4A08 /* LazyView.swift in Sources */, E11B1B6E2718CDBA006DA3E8 /* JellyfinAPIError.swift in Sources */, 628B95372670CB800091AF3B /* JellyfinWidget.swift in Sources */, + 6264E88E273850380081A12A /* Strings.swift in Sources */, E1AD105426D97161003E4A08 /* BaseItemDtoExtensions.swift in Sources */, E1FCD09A26C4F35A007C8DCF /* ErrorMessage.swift in Sources */, 628B95272670CABD0091AF3B /* NextUpWidget.swift in Sources */, @@ -1943,7 +2029,6 @@ E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */, 6220D0AF26D5EABE00B8E046 /* ViewExtensions.swift in Sources */, E13DD3D7271693CD009D4DAF /* SwiftfinStoreDefaults.swift in Sources */, - E1FCD09926C4F358007C8DCF /* NetworkError.swift in Sources */, E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */, E13DD3CA27164B80009D4DAF /* SwiftfinStore.swift in Sources */, 62EC353226766849000E9F2D /* SessionManager.swift in Sources */, @@ -1955,10 +2040,10 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 628B952C2670CABE0091AF3B /* PBXTargetDependency */ = { + 6264E889273848760081A12A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 628B951F2670CABD0091AF3B /* WidgetExtension */; - targetProxy = 628B952B2670CABE0091AF3B /* PBXContainerItemProxy */; + targetProxy = 6264E888273848760081A12A /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ @@ -2096,7 +2181,7 @@ /* Begin XCBuildConfiguration section */ 535870722669D21700D05A09 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DE5004F745B19E28744A7DE7 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */; + baseConfigurationReference = B598C62749E5EFD37280FCC3 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "Dev App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2126,7 +2211,7 @@ }; 535870732669D21700D05A09 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */; + baseConfigurationReference = 20CA36DDD247EED8D16438A5 /* Pods-JellyfinPlayer tvOS.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -2276,7 +2361,7 @@ }; 5377CC1C263B596B003A4E83 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 3773C07648173CE7FEC083D5 /* Pods-JellyfinPlayer iOS.debug.xcconfig */; + baseConfigurationReference = 772F6DAB8534FD1DB45B7687 /* Pods-JellyfinPlayer iOS.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "AppIcon-Dev"; @@ -2312,7 +2397,7 @@ }; 5377CC1D263B596B003A4E83 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = BEEC50E7EFD4848C0E320941 /* Pods-JellyfinPlayer iOS.release.xcconfig */; + baseConfigurationReference = 14E199C7BBA98782CAD2F0D4 /* Pods-JellyfinPlayer iOS.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -2348,6 +2433,7 @@ }; 628B952F2670CABE0091AF3B /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 4BDCEE3B49CF70A9E9BA3CD8 /* Pods-WidgetExtension.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; @@ -2374,6 +2460,7 @@ }; 628B95302670CABE0091AF3B /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = FDEDADB92FA8523BC8432E45 /* Pods-WidgetExtension.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; diff --git a/JellyfinPlayer.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/JellyfinPlayer.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..21ba9472 --- /dev/null +++ b/JellyfinPlayer.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + DisableBuildSystemDeprecationDiagnostic + + PreviewsEnabled + + + diff --git a/JellyfinPlayer/App/AppDelegate.swift b/JellyfinPlayer/App/AppDelegate.swift index d41c406f..21701dd2 100644 --- a/JellyfinPlayer/App/AppDelegate.swift +++ b/JellyfinPlayer/App/AppDelegate.swift @@ -12,12 +12,12 @@ import UIKit class AppDelegate: NSObject, UIApplicationDelegate { static var orientationLock = UIInterfaceOrientationMask.all - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + // Lazily initialize datastack - let _ = SwiftfinStore.dataStack - + _ = SwiftfinStore.dataStack + return true } diff --git a/JellyfinPlayer/App/EmailHelper.swift b/JellyfinPlayer/App/EmailHelper.swift index 5c054d9f..87fd8523 100644 --- a/JellyfinPlayer/App/EmailHelper.swift +++ b/JellyfinPlayer/App/EmailHelper.swift @@ -11,9 +11,9 @@ import SwiftUI import MessageUI class EmailHelper: NSObject, MFMailComposeViewControllerDelegate { - + public static let shared = EmailHelper() - + override private init() { } func sendLogs(logURL: URL) { diff --git a/JellyfinPlayer/App/JellyfinPlayerApp.swift b/JellyfinPlayer/App/JellyfinPlayerApp.swift index 27440a56..2d58ea07 100644 --- a/JellyfinPlayer/App/JellyfinPlayerApp.swift +++ b/JellyfinPlayer/App/JellyfinPlayerApp.swift @@ -13,7 +13,7 @@ import SwiftUI // MARK: JellyfinPlayerApp @main struct JellyfinPlayerApp: App { - + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @Default(.appAppearance) var appAppearance @@ -35,7 +35,7 @@ struct JellyfinPlayerApp: App { } } } - + private func setupAppearance() { UIApplication.shared.windows.first?.overrideUserInterfaceStyle = appAppearance.style } diff --git a/JellyfinPlayer/AppURLHandler/AppURLHandler.swift b/JellyfinPlayer/AppURLHandler/AppURLHandler.swift index 4c64b916..7f745487 100644 --- a/JellyfinPlayer/AppURLHandler/AppURLHandler.swift +++ b/JellyfinPlayer/AppURLHandler/AppURLHandler.swift @@ -77,8 +77,7 @@ extension AppURLHandler { // /Users/{UserID}/Items/{ItemID} if url.pathComponents[safe: 2]?.lowercased() == "items", let userID = url.pathComponents[safe: 1], - let itemID = url.pathComponents[safe: 3] - { + let itemID = url.pathComponents[safe: 3] { // It would be nice if the ItemViewModel could be initialized to id later. getItem(userID: userID, itemID: itemID) { item in guard let item = item else { return } diff --git a/JellyfinPlayer/Components/EpisodeCardVStackView.swift b/JellyfinPlayer/Components/EpisodeCardVStackView.swift index b7f2bac8..aee29489 100644 --- a/JellyfinPlayer/Components/EpisodeCardVStackView.swift +++ b/JellyfinPlayer/Components/EpisodeCardVStackView.swift @@ -11,10 +11,10 @@ import SwiftUI import JellyfinAPI struct EpisodeCardVStackView: View { - + let items: [BaseItemDto] let selectedAction: (BaseItemDto) -> Void - + private func buildCardOverlayView(item: BaseItemDto) -> some View { HStack { ZStack { @@ -30,7 +30,7 @@ struct EpisodeCardVStackView: View { .padding(.leading, 2) .padding(.bottom, item.userData?.playedPercentage == nil ? 2 : 9) .opacity(1) - + ZStack { if item.userData?.played ?? false { Image(systemName: "circle.fill") @@ -42,7 +42,7 @@ struct EpisodeCardVStackView: View { .opacity(1) } } - + var body: some View { VStack { ForEach(items, id: \.id) { item in @@ -50,7 +50,7 @@ struct EpisodeCardVStackView: View { selectedAction(item) } label: { HStack { - + // MARK: Image ImageView(src: item.getPrimaryImage(maxWidth: 150), bh: item.getPrimaryImageBlurHash(), @@ -65,37 +65,37 @@ struct EpisodeCardVStackView: View { .padding(0), alignment: .bottomLeading ) .overlay(buildCardOverlayView(item: item), alignment: .topTrailing) - + VStack(alignment: .leading) { - + // MARK: Title Text(item.title) .font(.subheadline) .fontWeight(.medium) .foregroundColor(.primary) .lineLimit(2) - + HStack { Text(item.getEpisodeLocator() ?? "") .font(.subheadline) .fontWeight(.medium) .foregroundColor(.secondary) - + Text(item.getItemRuntime()) .font(.subheadline) .fontWeight(.medium) .foregroundColor(.secondary) - + Spacer() } - + // MARK: Overview Text(item.overview ?? "") .font(.footnote) .foregroundColor(.primary) .fixedSize(horizontal: false, vertical: true) .lineLimit(4) - + Spacer() } } diff --git a/JellyfinPlayer/Components/PillHStackView.swift b/JellyfinPlayer/Components/PillHStackView.swift index d9ce7769..d66cf279 100644 --- a/JellyfinPlayer/Components/PillHStackView.swift +++ b/JellyfinPlayer/Components/PillHStackView.swift @@ -10,12 +10,12 @@ import SwiftUI struct PillHStackView: View { - + let title: String let items: [ItemType] // let navigationView: (ItemType) -> NavigationView let selectedAction: (ItemType) -> Void - + var body: some View { VStack(alignment: .leading) { Text(title) @@ -23,7 +23,7 @@ struct PillHStackView: View { .fontWeight(.semibold) .padding(.top, 3) .padding(.leading, 16) - + ScrollView(.horizontal, showsIndicators: false) { HStack { ForEach(items, id: \.title) { item in diff --git a/JellyfinPlayer/Components/PortraitHStackView.swift b/JellyfinPlayer/Components/PortraitHStackView.swift index db9febec..ec2e2ce9 100644 --- a/JellyfinPlayer/Components/PortraitHStackView.swift +++ b/JellyfinPlayer/Components/PortraitHStackView.swift @@ -10,13 +10,13 @@ import SwiftUI struct PortraitImageHStackView: View { - + let items: [ItemType] let maxWidth: Int let horizontalAlignment: HorizontalAlignment let topBarView: () -> TopBarView let selectedAction: (ItemType) -> Void - + init(items: [ItemType], maxWidth: Int, horizontalAlignment: HorizontalAlignment = .leading, topBarView: @escaping () -> TopBarView, selectedAction: @escaping (ItemType) -> Void) { self.items = items self.maxWidth = maxWidth @@ -24,18 +24,18 @@ struct PortraitImageHStackView Void)? - + @objc func didRefresh() { guard let refreshControl = refreshControl else { return } refreshAction?() diff --git a/JellyfinPlayer/Views/BasicAppSettingsView.swift b/JellyfinPlayer/Views/BasicAppSettingsView.swift index ed73b75f..711454b2 100644 --- a/JellyfinPlayer/Views/BasicAppSettingsView.swift +++ b/JellyfinPlayer/Views/BasicAppSettingsView.swift @@ -12,18 +12,18 @@ import Stinsen import SwiftUI struct BasicAppSettingsView: View { - + @EnvironmentObject var basicAppSettingsRouter: BasicAppSettingsCoordinator.Router @ObservedObject var viewModel: BasicAppSettingsViewModel @State var resetTapped: Bool = false - + @Default(.appAppearance) var appAppearance @Default(.defaultHTTPScheme) var defaultHTTPScheme - + var body: some View { Form { Section { - Picker(NSLocalizedString("Appearance", comment: ""), selection: $appAppearance) { + Picker(L10n.appearance, selection: $appAppearance) { ForEach(self.viewModel.appearances, id: \.self) { appearance in Text(appearance.localizedName).tag(appearance.rawValue) } @@ -31,9 +31,9 @@ struct BasicAppSettingsView: View { UIApplication.shared.windows.first?.overrideUserInterfaceStyle = appAppearance.style }) } header: { - Text("Accessibility") + L10n.accessibility.text } - + Section { Picker("Default Scheme", selection: $defaultHTTPScheme) { ForEach(HTTPScheme.allCases, id: \.self) { scheme in @@ -43,19 +43,19 @@ struct BasicAppSettingsView: View { } header: { Text("Networking") } - + Button { resetTapped = true } label: { - Text("Reset") + L10n.reset.text } } - .alert("Reset", isPresented: $resetTapped, actions: { + .alert(L10n.reset, isPresented: $resetTapped, actions: { Button(role: .destructive) { viewModel.reset() basicAppSettingsRouter.dismissCoordinator() } label: { - Text("Reset") + L10n.reset.text } }) .navigationBarTitle("Settings", displayMode: .inline) diff --git a/JellyfinPlayer/Views/ConnectToServerView.swift b/JellyfinPlayer/Views/ConnectToServerView.swift index 624f623b..3bb35d9c 100644 --- a/JellyfinPlayer/Views/ConnectToServerView.swift +++ b/JellyfinPlayer/Views/ConnectToServerView.swift @@ -11,16 +11,16 @@ import Stinsen import SwiftUI struct ConnectToServerView: View { - + @ObservedObject var viewModel: ConnectToServerViewModel @State var uri = "" - + @Default(.defaultHTTPScheme) var defaultHTTPScheme - + var body: some View { List { Section { - TextField(NSLocalizedString("Server URL", comment: ""), text: $uri) + TextField(L10n.serverURL, text: $uri) .disableAutocorrection(true) .autocapitalization(.none) .keyboardType(.URL) @@ -29,7 +29,7 @@ struct ConnectToServerView: View { uri = "\(defaultHTTPScheme.rawValue)://" } } - + if viewModel.isLoading { Button(role: .destructive) { viewModel.cancelConnection() @@ -40,14 +40,14 @@ struct ConnectToServerView: View { Button { viewModel.connectToServer(uri: uri) } label: { - Text("Connect") + L10n.connect.text } .disabled(uri.isEmpty) } } header: { Text("Connect to a Jellyfin server") } - + Section { if viewModel.searching { HStack(alignment: .center, spacing: 5) { @@ -88,9 +88,9 @@ struct ConnectToServerView: View { } } header: { HStack { - Text("Local Servers") + L10n.localServers.text Spacer() - + Button { viewModel.discoverServers() } label: { @@ -106,7 +106,7 @@ struct ConnectToServerView: View { message: Text(viewModel.errorMessage?.displayMessage ?? "Unknown Error"), dismissButton: .cancel()) } - .alert(item: $viewModel.addServerURIPayload) { _ in + .alert(item: $viewModel.addServerURIPayload) { _ in Alert(title: Text("Existing Server"), message: Text("Server \(viewModel.addServerURIPayload?.server.name ?? "") already exists. Add new URL?"), primaryButton: .default(Text("Add URL"), action: { @@ -114,7 +114,7 @@ struct ConnectToServerView: View { }), secondaryButton: .cancel()) } - .navigationTitle("Connect") + .navigationTitle(L10n.connect) .onAppear { viewModel.discoverServers() AppURLHandler.shared.appURLState = .allowedInLogin diff --git a/JellyfinPlayer/Views/HomeView.swift b/JellyfinPlayer/Views/HomeView.swift index 51b3ae1e..26132fb9 100644 --- a/JellyfinPlayer/Views/HomeView.swift +++ b/JellyfinPlayer/Views/HomeView.swift @@ -12,10 +12,10 @@ import Introspect import SwiftUI struct HomeView: View { - + @EnvironmentObject var homeRouter: HomeCoordinator.Router @StateObject var viewModel = HomeViewModel() - + private let refreshHelper = RefreshHelper() @ViewBuilder @@ -31,10 +31,10 @@ struct HomeView: View { if !viewModel.nextUpItems.isEmpty { NextUpView(items: viewModel.nextUpItems) } - + ForEach(viewModel.libraries, id: \.self) { library in HStack { - Text("Latest \(library.name ?? "")") + Text(L10n.latestWithString(library.name ?? "")) .font(.title2) .fontWeight(.bold) Spacer() @@ -45,7 +45,7 @@ struct HomeView: View { title: library.name ?? "")) } label: { HStack { - Text("See All").font(.subheadline).fontWeight(.bold) + L10n.seeAll.text.font(.subheadline).fontWeight(.bold) Image(systemName: "chevron.right").font(Font.subheadline.bold()) } } @@ -58,10 +58,10 @@ struct HomeView: View { } .introspectScrollView { scrollView in let control = UIRefreshControl() - + refreshHelper.refreshControl = control refreshHelper.refreshAction = viewModel.refresh - + control.addTarget(refreshHelper, action: #selector(RefreshHelper.didRefresh), for: .valueChanged) scrollView.refreshControl = control } @@ -70,7 +70,7 @@ struct HomeView: View { var body: some View { innerBody - .navigationTitle(NSLocalizedString("Home", comment: "")) + .navigationTitle(L10n.home) .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { Button { diff --git a/JellyfinPlayer/Views/ItemView/ItemView.swift b/JellyfinPlayer/Views/ItemView/ItemView.swift index de799478..95d37413 100644 --- a/JellyfinPlayer/Views/ItemView/ItemView.swift +++ b/JellyfinPlayer/Views/ItemView/ItemView.swift @@ -28,7 +28,7 @@ struct ItemNavigationView: View { } } -fileprivate struct ItemView: View { +private struct ItemView: View { @EnvironmentObject var itemRouter: ItemCoordinator.Router @State private var videoIsLoading: Bool = false // This variable is only changed by the underlying VLC view. diff --git a/JellyfinPlayer/Views/ItemView/ItemViewBody.swift b/JellyfinPlayer/Views/ItemView/ItemViewBody.swift index 3c819b25..3df1d559 100644 --- a/JellyfinPlayer/Views/ItemView/ItemViewBody.swift +++ b/JellyfinPlayer/Views/ItemView/ItemViewBody.swift @@ -29,7 +29,7 @@ struct ItemViewBody: View { PortraitImageHStackView(items: seriesViewModel.seasons, maxWidth: 150, topBarView: { - Text("Seasons") + L10n.seasons.text .font(.callout) .fontWeight(.semibold) .padding(.top, 3) @@ -41,7 +41,7 @@ struct ItemViewBody: View { // MARK: Genres - PillHStackView(title: "Genres", + PillHStackView(title: L10n.genres, items: viewModel.item.genreItems ?? [], selectedAction: { genre in itemRouter.route(to: \.library, (viewModel: .init(genre: genre), title: genre.title)) @@ -50,7 +50,7 @@ struct ItemViewBody: View { // MARK: Studios if let studios = viewModel.item.studios { - PillHStackView(title: "Studios", + PillHStackView(title: L10n.studios, items: studios) { studio in itemRouter.route(to: \.library, (viewModel: .init(studio: studio), title: studio.name ?? "")) } @@ -79,7 +79,7 @@ struct ItemViewBody: View { PortraitImageHStackView(items: viewModel.similarItems, maxWidth: 150, topBarView: { - Text("More Like This") + L10n.moreLikeThis.text .font(.callout) .fontWeight(.semibold) .padding(.top, 3) diff --git a/JellyfinPlayer/Views/ItemView/Landscape/ItemLandscapeTopBarView.swift b/JellyfinPlayer/Views/ItemView/Landscape/ItemLandscapeTopBarView.swift index 24a9e15a..c0626b1c 100644 --- a/JellyfinPlayer/Views/ItemView/Landscape/ItemLandscapeTopBarView.swift +++ b/JellyfinPlayer/Views/ItemView/Landscape/ItemLandscapeTopBarView.swift @@ -10,22 +10,22 @@ import SwiftUI struct ItemLandscapeTopBarView: View { - + @EnvironmentObject private var viewModel: ItemViewModel - + var body: some View { HStack { VStack(alignment: .leading) { - + // MARK: Name - + Text(viewModel.getItemDisplayName()) .font(.title) .fontWeight(.semibold) .foregroundColor(.primary) .padding(.leading, 16) .padding(.bottom, 10) - + if viewModel.item.itemType.showDetails { // MARK: Runtime Text(viewModel.item.getItemRuntime()) @@ -34,7 +34,7 @@ struct ItemLandscapeTopBarView: View { .foregroundColor(.secondary) .padding(.leading, 16) } - + // MARK: Details HStack { if viewModel.item.productionYear != nil { @@ -53,9 +53,9 @@ struct ItemLandscapeTopBarView: View { .overlay(RoundedRectangle(cornerRadius: 2) .stroke(Color.secondary, lineWidth: 1)) } - + Spacer() - + if viewModel.item.itemType.showDetails { // MARK: Favorite Button { @@ -70,7 +70,7 @@ struct ItemLandscapeTopBarView: View { } } .disabled(viewModel.isLoading) - + // MARK: Watched Button { viewModel.updateWatchState() diff --git a/JellyfinPlayer/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift b/JellyfinPlayer/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift index cb7c28a5..99a069d5 100644 --- a/JellyfinPlayer/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift +++ b/JellyfinPlayer/Views/ItemView/Portrait/ItemPortraitHeaderOverlayView.swift @@ -11,22 +11,22 @@ import SwiftUI import JellyfinAPI struct PortraitHeaderOverlayView: View { - + @EnvironmentObject private var viewModel: ItemViewModel @EnvironmentObject private var videoPlayerItem: VideoPlayerItem - + var body: some View { VStack(alignment: .leading) { HStack(alignment: .bottom, spacing: 12) { - + // MARK: Portrait Image ImageView(src: viewModel.item.portraitHeaderViewURL(maxWidth: 130)) .frame(width: 130, height: 195) .cornerRadius(10) - + VStack(alignment: .leading, spacing: 1) { Spacer() - + // MARK: Name Text(viewModel.getItemDisplayName()) .font(.title2) @@ -34,7 +34,7 @@ struct PortraitHeaderOverlayView: View { .foregroundColor(.primary) .fixedSize(horizontal: false, vertical: true) .padding(.bottom, 10) - + if viewModel.item.itemType.showDetails { // MARK: Runtime if viewModel.shouldDisplayRuntime() { @@ -45,7 +45,7 @@ struct PortraitHeaderOverlayView: View { .lineLimit(1) } } - + // MARK: Details HStack { if let productionYear = viewModel.item.productionYear { @@ -55,7 +55,7 @@ struct PortraitHeaderOverlayView: View { .foregroundColor(.secondary) .lineLimit(1) } - + if let officialRating = viewModel.item.officialRating { Text(officialRating) .font(.subheadline) @@ -70,9 +70,9 @@ struct PortraitHeaderOverlayView: View { } .padding(.bottom, UIDevice.current.userInterfaceIdiom == .pad ? 98 : 30) } - + HStack { - + // MARK: Play Button { if let playButtonItem = viewModel.playButtonItem { @@ -93,9 +93,9 @@ struct PortraitHeaderOverlayView: View { .background(viewModel.playButtonItem == nil ? Color(UIColor.secondarySystemFill) : Color.jellyfinPurple) .cornerRadius(10) }.disabled(viewModel.playButtonItem == nil) - + Spacer() - + if viewModel.item.itemType.showDetails { // MARK: Favorite Button { @@ -112,7 +112,7 @@ struct PortraitHeaderOverlayView: View { } } .disabled(viewModel.isLoading) - + // MARK: Watched Button { viewModel.updateWatchState() diff --git a/JellyfinPlayer/Views/LibraryFilterView.swift b/JellyfinPlayer/Views/LibraryFilterView.swift index 1b1ffb00..5864a6b1 100644 --- a/JellyfinPlayer/Views/LibraryFilterView.swift +++ b/JellyfinPlayer/Views/LibraryFilterView.swift @@ -10,7 +10,7 @@ import Stinsen import SwiftUI struct LibraryFilterView: View { - + @EnvironmentObject var filterRouter: FilterCoordinator.Router @Binding var filters: LibraryFilters var parentId: String = "" @@ -31,32 +31,32 @@ struct LibraryFilterView: View { } else { Form { if viewModel.enabledFilterType.contains(.genre) { - MultiSelector(label: NSLocalizedString("Genres", comment: ""), + MultiSelector(label: L10n.genres, options: viewModel.possibleGenres, optionToString: { $0.name ?? "" }, selected: $viewModel.modifiedFilters.withGenres) } if viewModel.enabledFilterType.contains(.filter) { - MultiSelector(label: NSLocalizedString("Filters", comment: ""), + MultiSelector(label: L10n.filters, options: viewModel.possibleItemFilters, optionToString: { $0.localized }, selected: $viewModel.modifiedFilters.filters) } if viewModel.enabledFilterType.contains(.tag) { - MultiSelector(label: NSLocalizedString("Tags", comment: ""), + MultiSelector(label: L10n.tags, options: viewModel.possibleTags, optionToString: { $0 }, selected: $viewModel.modifiedFilters.tags) } if viewModel.enabledFilterType.contains(.sortBy) { - Picker(selection: $viewModel.selectedSortBy, label: Text("Sort by")) { + Picker(selection: $viewModel.selectedSortBy, label: L10n.sortBy.text) { ForEach(viewModel.possibleSortBys, id: \.self) { so in Text(so.localized).tag(so) } } } if viewModel.enabledFilterType.contains(.sortOrder) { - Picker(selection: $viewModel.selectedSortOrder, label: Text("Display order")) { + Picker(selection: $viewModel.selectedSortOrder, label: L10n.displayOrder.text) { ForEach(viewModel.possibleSortOrders, id: \.self) { so in Text(so.rawValue).tag(so) } @@ -68,11 +68,11 @@ struct LibraryFilterView: View { self.filters = viewModel.modifiedFilters filterRouter.dismissCoordinator() } label: { - Text("Reset") + L10n.reset.text } } } - .navigationBarTitle(NSLocalizedString("Filter Results", comment: ""), displayMode: .inline) + .navigationBarTitle(L10n.filterResults, displayMode: .inline) .toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { Button { @@ -87,7 +87,7 @@ struct LibraryFilterView: View { self.filters = viewModel.modifiedFilters filterRouter.dismissCoordinator() } label: { - Text("Apply") + L10n.apply.text } } } diff --git a/JellyfinPlayer/Views/LibraryListView.swift b/JellyfinPlayer/Views/LibraryListView.swift index baac87b8..83bbd080 100644 --- a/JellyfinPlayer/Views/LibraryListView.swift +++ b/JellyfinPlayer/Views/LibraryListView.swift @@ -23,7 +23,7 @@ struct LibraryListView: View { ZStack { HStack { Spacer() - Text("Your Favorites") + L10n.yourFavorites.text .foregroundColor(.black) .font(.subheadline) .fontWeight(.semibold) @@ -39,12 +39,12 @@ struct LibraryListView: View { .padding(.bottom, 5) NavigationLink(destination: LazyView { - Text("WIP") + L10n.wip.text }) { ZStack { HStack { Spacer() - Text("All Genres") + L10n.allGenres.text .foregroundColor(.black) .font(.subheadline) .fontWeight(.semibold) @@ -98,7 +98,7 @@ struct LibraryListView: View { .padding(.trailing, 16) .padding(.top, 8) } - .navigationTitle(NSLocalizedString("All Media", comment: "")) + .navigationTitle(L10n.allMedia) .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { Button { diff --git a/JellyfinPlayer/Views/LibrarySearchView.swift b/JellyfinPlayer/Views/LibrarySearchView.swift index a9c63a97..632a77ef 100644 --- a/JellyfinPlayer/Views/LibrarySearchView.swift +++ b/JellyfinPlayer/Views/LibrarySearchView.swift @@ -46,7 +46,7 @@ struct LibrarySearchView: View { var suggestionsListView: some View { ScrollView { LazyVStack(spacing: 8) { - Text("Suggestions") + L10n.suggestions.text .font(.headline) .fontWeight(.bold) .foregroundColor(.primary) diff --git a/JellyfinPlayer/Views/LibraryView.swift b/JellyfinPlayer/Views/LibraryView.swift index b0fb15b5..62883cf0 100644 --- a/JellyfinPlayer/Views/LibraryView.swift +++ b/JellyfinPlayer/Views/LibraryView.swift @@ -55,7 +55,7 @@ struct LibraryView: View { Image(systemName: "chevron.left") .font(.system(size: 25)) }.disabled(!viewModel.hasPreviousPage) - Text("Page \(String(viewModel.currentPage + 1)) of \(String(viewModel.totalPages))") + Text(L10n.pageOfWithNumbers(String(viewModel.currentPage + 1), String(viewModel.totalPages))) .font(.subheadline) .fontWeight(.medium) Button { @@ -72,7 +72,7 @@ struct LibraryView: View { } } } else { - Text("No results.") + L10n.noResults.text } } .navigationBarTitle(title, displayMode: .inline) diff --git a/JellyfinPlayer/Views/LoadingView.swift b/JellyfinPlayer/Views/LoadingView.swift index 015ba61f..be7b67a2 100644 --- a/JellyfinPlayer/Views/LoadingView.swift +++ b/JellyfinPlayer/Views/LoadingView.swift @@ -32,7 +32,7 @@ struct LoadingView: View where Content: View { // indicator, with some text underneath showing what we are doing HStack { ProgressView() - Text(text ?? "Loading").fontWeight(.semibold).font(.callout).offset(x: 60) + Text(text ?? L10n.loading).fontWeight(.semibold).font(.callout).offset(x: 60) Spacer() } .padding(EdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 10)) @@ -70,7 +70,7 @@ struct LoadingViewNoBlur: View where Content: View { // indicator, with some text underneath showing what we are doing HStack { ProgressView() - Text(text ?? "Loading").fontWeight(.semibold).font(.callout).offset(x: 60) + Text(text ?? L10n.loading).fontWeight(.semibold).font(.callout).offset(x: 60) Spacer() } .padding(EdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 10)) diff --git a/JellyfinPlayer/Views/NextUpView.swift b/JellyfinPlayer/Views/NextUpView.swift index 5cdd5467..0a556eb0 100644 --- a/JellyfinPlayer/Views/NextUpView.swift +++ b/JellyfinPlayer/Views/NextUpView.swift @@ -17,7 +17,7 @@ struct NextUpView: View { var body: some View { VStack(alignment: .leading) { - Text("Next Up") + L10n.nextUp.text .font(.title2) .fontWeight(.bold) .padding(.leading, 16) diff --git a/JellyfinPlayer/Views/ServerListView.swift b/JellyfinPlayer/Views/ServerListView.swift index 3d454bff..a09f36ff 100644 --- a/JellyfinPlayer/Views/ServerListView.swift +++ b/JellyfinPlayer/Views/ServerListView.swift @@ -1,5 +1,5 @@ // - /* + /* * SwiftFin is subject to the terms of the Mozilla Public * License, v2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. @@ -11,10 +11,10 @@ import CoreStore import SwiftUI struct ServerListView: View { - + @EnvironmentObject var serverListRouter: ServerListCoordinator.Router @ObservedObject var viewModel: ServerListViewModel - + private var listView: some View { ScrollView { LazyVStack { @@ -27,22 +27,22 @@ struct ServerListView: View { .foregroundColor(Color(UIColor.secondarySystemFill)) .frame(height: 100) .cornerRadius(10) - + HStack(spacing: 10) { Image(systemName: "server.rack") .font(.system(size: 36)) .foregroundColor(.primary) - + VStack(alignment: .leading, spacing: 5) { Text(server.name) .font(.title2) .foregroundColor(.primary) - + Text(server.currentURI) .font(.footnote) .disabled(true) .foregroundColor(.secondary) - + Text(viewModel.userTextFor(server: server)) .font(.footnote) .foregroundColor(.primary) @@ -62,13 +62,13 @@ struct ServerListView: View { } } } - + private var noServerView: some View { VStack { Text("Connect to a Jellyfin server to get started") .frame(minWidth: 50, maxWidth: 240) .multilineTextAlignment(.center) - + Button { serverListRouter.route(to: \.connectToServer) } label: { @@ -80,15 +80,15 @@ struct ServerListView: View { .cornerRadius(10) .padding(.horizontal, 30) .padding([.top, .bottom], 20) - - Text("Connect") + + L10n.connect.text .foregroundColor(Color.white) .bold() } } } } - + @ViewBuilder private var innerBody: some View { if viewModel.servers.isEmpty { @@ -98,7 +98,7 @@ struct ServerListView: View { listView } } - + @ViewBuilder private var trailingToolbarContent: some View { if viewModel.servers.isEmpty { @@ -111,7 +111,7 @@ struct ServerListView: View { } } } - + private var leadingToolbarContent: some View { Button { serverListRouter.route(to: \.basicAppSettings) @@ -119,7 +119,7 @@ struct ServerListView: View { Image(systemName: "gearshape.fill") } } - + var body: some View { innerBody .navigationTitle("Servers") diff --git a/JellyfinPlayer/Views/SettingsView.swift b/JellyfinPlayer/Views/SettingsView.swift index 7b7a9ff5..ef21dc0f 100644 --- a/JellyfinPlayer/Views/SettingsView.swift +++ b/JellyfinPlayer/Views/SettingsView.swift @@ -11,7 +11,7 @@ import Stinsen import SwiftUI struct SettingsView: View { - + @EnvironmentObject var settingsRouter: SettingsCoordinator.Router @ObservedObject var viewModel: SettingsViewModel @@ -27,7 +27,7 @@ struct SettingsView: View { var body: some View { Form { Section(header: EmptyView()) { - + // There is a bug where the SettingsView attmempts to remake itself upon signing out // so this check is made if SessionManager.main.currentLogin == nil { @@ -81,7 +81,7 @@ struct SettingsView: View { .font(.callout) } } - + Section(header: Text("Playback")) { Picker("Default local quality", selection: $inNetworkStreamBitrate) { ForEach(self.viewModel.bitrates, id: \.self) { bitrate in @@ -108,7 +108,7 @@ struct SettingsView: View { } } - Section(header: Text("Accessibility")) { + Section(header: L10n.accessibility.text) { Toggle("Automatically show subtitles", isOn: $isAutoSelectSubtitles) SearchablePicker(label: "Preferred subtitle language", options: viewModel.langs, @@ -129,7 +129,7 @@ struct SettingsView: View { .auto }, set: { autoSelectAudioLangcode = $0.isoCode })) - Picker(NSLocalizedString("Appearance", comment: ""), selection: $appAppearance) { + Picker(L10n.appearance, selection: $appAppearance) { ForEach(self.viewModel.appearances, id: \.self) { appearance in Text(appearance.localizedName).tag(appearance.rawValue) } diff --git a/JellyfinPlayer/Views/UserListView.swift b/JellyfinPlayer/Views/UserListView.swift index 868026ed..f3122276 100644 --- a/JellyfinPlayer/Views/UserListView.swift +++ b/JellyfinPlayer/Views/UserListView.swift @@ -10,10 +10,10 @@ import SwiftUI struct UserListView: View { - + @EnvironmentObject var userListRouter: UserListCoordinator.Router @ObservedObject var viewModel: UserListViewModel - + private var listView: some View { ScrollView { LazyVStack { @@ -26,13 +26,13 @@ struct UserListView: View { .foregroundColor(Color(UIColor.secondarySystemFill)) .frame(height: 50) .cornerRadius(10) - + HStack { Text(user.username) .font(.title2) - + Spacer() - + if viewModel.isLoading { ProgressView() } @@ -51,13 +51,13 @@ struct UserListView: View { } } } - + private var noUserView: some View { VStack { Text("Sign in to get started") .frame(minWidth: 50, maxWidth: 240) .multilineTextAlignment(.center) - + Button { userListRouter.route(to: \.userSignIn, viewModel.server) } label: { @@ -69,7 +69,7 @@ struct UserListView: View { .cornerRadius(10) .padding(.horizontal, 30) .padding([.top, .bottom], 20) - + Text("Sign in") .foregroundColor(Color.white) .bold() @@ -77,7 +77,7 @@ struct UserListView: View { } } } - + @ViewBuilder private var innerBody: some View { if viewModel.users.isEmpty { @@ -87,7 +87,7 @@ struct UserListView: View { listView } } - + @ViewBuilder private var toolbarContent: some View { HStack { @@ -106,7 +106,7 @@ struct UserListView: View { } } } - + var body: some View { innerBody .navigationTitle(viewModel.server.name) diff --git a/JellyfinPlayer/Views/UserSignInView.swift b/JellyfinPlayer/Views/UserSignInView.swift index 3173cdd7..72a1390f 100644 --- a/JellyfinPlayer/Views/UserSignInView.swift +++ b/JellyfinPlayer/Views/UserSignInView.swift @@ -11,23 +11,23 @@ import SwiftUI import Stinsen struct UserSignInView: View { - + @ObservedObject var viewModel: UserSignInViewModel @State private var username: String = "" @State private var password: String = "" - + var body: some View { Form { - + Section { - TextField("Username", text: $username) + TextField(L10n.username, text: $username) .disableAutocorrection(true) .autocapitalization(.none) - - SecureField("Password", text: $password) + + SecureField(L10n.password, text: $password) .disableAutocorrection(true) .autocapitalization(.none) - + if viewModel.isLoading { Button(role: .destructive) { viewModel.cancelSignIn() diff --git a/JellyfinPlayer/Views/VideoPlayer/VideoPlayer.swift b/JellyfinPlayer/Views/VideoPlayer/VideoPlayer.swift index bfe64e16..2053e08d 100644 --- a/JellyfinPlayer/Views/VideoPlayer/VideoPlayer.swift +++ b/JellyfinPlayer/Views/VideoPlayer/VideoPlayer.swift @@ -153,7 +153,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe sendProgressReport(eventName: "unpause") } else { sendJellyfinCommand(command: "Seek", options: [ - "position": Int(secondsScrubbedTo), + "position": Int(secondsScrubbedTo) ]) } } @@ -421,7 +421,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe if manifest.type == "Movie" { titleLabel.text = manifest.name ?? "" } else { - titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”" + titleLabel.text = "\(L10n.seasonAndEpisode(String(manifest.parentIndexNumber ?? 0), String(manifest.indexNumber ?? 0))) “\(manifest.name ?? "")”" setupNextUpView() upNextViewModel.delegate = self @@ -664,8 +664,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe subtitleTrackArray.forEach { subtitle in if Defaults[.isAutoSelectSubtitles] { if Defaults[.autoSelectSubtitlesLangCode] == "Auto", - subtitle.languageCode.contains(Locale.current.languageCode ?? "") - { + subtitle.languageCode.contains(Locale.current.languageCode ?? "") { selectedCaptionTrack = subtitle.id mediaPlayer.currentVideoSubTitleIndex = subtitle.id } else if subtitle.languageCode.contains(Defaults[.autoSelectSubtitlesLangCode]) { @@ -816,7 +815,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe shouldShowLoadingScreen = true videoControlsView.isHidden = true - titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”" + titleLabel.text = "\(L10n.seasonAndEpisode(String(manifest.parentIndexNumber ?? 0), String(manifest.indexNumber ?? 0))) “\(manifest.name ?? "")”" setupMediaPlayer() getNextEpisode() @@ -854,7 +853,7 @@ extension PlayerViewController: GCKGenericChannelDelegate { if hasSentRemoteSeek == false { hasSentRemoteSeek = true sendJellyfinCommand(command: "Seek", options: [ - "position": Int(Float(manifest.runTimeTicks! / 10_000_000) * mediaPlayer.position), + "position": Int(Float(manifest.runTimeTicks! / 10_000_000) * mediaPlayer.position) ]) } } @@ -880,7 +879,7 @@ extension PlayerViewController: GCKGenericChannelDelegate { "serverId": SessionManager.main.currentLogin.server.id, "serverVersion": "10.8.0", "receiverName": castSessionManager.currentCastSession!.device.friendlyName!, - "subtitleBurnIn": false, + "subtitleBurnIn": false ] let jsonData = JSON(payload) @@ -935,8 +934,8 @@ extension PlayerViewController: GCKSessionManagerListener { "Name": manifest.name!, "Type": manifest.type!, "MediaType": manifest.mediaType!, - "IsFolder": manifest.isFolder!, - ]], + "IsFolder": manifest.isFolder! + ]] ] sendJellyfinCommand(command: "PlayNow", options: playNowOptions) } @@ -1104,8 +1103,7 @@ struct VLCPlayerWithControls: UIViewControllerRepresentable { typealias UIViewControllerType = PlayerViewController func makeUIViewController(context: UIViewControllerRepresentableContext) -> VLCPlayerWithControls - .UIViewControllerType - { + .UIViewControllerType { let storyboard = UIStoryboard(name: "VideoPlayer", bundle: nil) let customViewController = storyboard.instantiateViewController(withIdentifier: "VideoPlayer") as! PlayerViewController customViewController.manifest = item diff --git a/JellyfinPlayer/Views/VideoPlayer/VideoPlayerCastDeviceSelector.swift b/JellyfinPlayer/Views/VideoPlayer/VideoPlayerCastDeviceSelector.swift index ac784125..2f018c88 100644 --- a/JellyfinPlayer/Views/VideoPlayer/VideoPlayerCastDeviceSelector.swift +++ b/JellyfinPlayer/Views/VideoPlayer/VideoPlayerCastDeviceSelector.swift @@ -56,7 +56,7 @@ struct VideoPlayerCastDeviceSelector: View { delegate?.castPopoverDismissed() } label: { HStack { - Text("Connect") + L10n.connect.text .font(.caption) .fontWeight(.medium) Image(systemName: "bonjour") @@ -66,14 +66,14 @@ struct VideoPlayerCastDeviceSelector: View { } } } else { - Text("No Cast devices found..") + L10n.noCastdevicesfound.text .foregroundColor(.secondary) .font(.subheadline) .fontWeight(.medium) } } .navigationBarTitleDisplayMode(.inline) - .navigationTitle(NSLocalizedString("Select Cast Destination", comment: "")) + .navigationTitle(L10n.selectCastDestination) .toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { if UIDevice.current.userInterfaceIdiom == .phone { @@ -82,7 +82,7 @@ struct VideoPlayerCastDeviceSelector: View { } label: { HStack { Image(systemName: "chevron.left") - Text("Back").font(.callout) + L10n.back.text.font(.callout) } } } diff --git a/JellyfinPlayer/Views/VideoPlayer/VideoPlayerSettingsView.swift b/JellyfinPlayer/Views/VideoPlayer/VideoPlayerSettingsView.swift index 1a10eecc..fb73836a 100644 --- a/JellyfinPlayer/Views/VideoPlayer/VideoPlayerSettingsView.swift +++ b/JellyfinPlayer/Views/VideoPlayer/VideoPlayerSettingsView.swift @@ -40,7 +40,7 @@ struct VideoPlayerSettings: View { var body: some View { Form { - Picker(NSLocalizedString("Closed Captions", comment: ""), selection: $captionTrack) { + Picker(L10n.closedCaptions, selection: $captionTrack) { ForEach(delegate.subtitleTrackArray, id: \.id) { caption in Text(caption.name).tag(caption.id) } @@ -48,14 +48,14 @@ struct VideoPlayerSettings: View { .onChange(of: captionTrack) { track in self.delegate.subtitleTrackChanged(newTrackID: track) } - Picker(NSLocalizedString("Audio Track", comment: ""), selection: $audioTrack) { + Picker(L10n.audioTrack, selection: $audioTrack) { ForEach(delegate.audioTrackArray, id: \.id) { caption in Text(caption.name).tag(caption.id).lineLimit(1) } }.onChange(of: audioTrack) { track in self.delegate.audioTrackChanged(newTrackID: track) } - Picker(NSLocalizedString("Playback Speed", comment: ""), selection: $playbackSpeedSelection) { + Picker(L10n.playbackSpeed, selection: $playbackSpeedSelection) { ForEach(delegate.playbackSpeeds.indices, id: \.self) { speedIndex in let speed = delegate.playbackSpeeds[speedIndex] Text("\(String(speed))x").tag(speedIndex) @@ -65,7 +65,7 @@ struct VideoPlayerSettings: View { self.delegate.playbackSpeedChanged(index: index) }) }.navigationBarTitleDisplayMode(.inline) - .navigationTitle(NSLocalizedString("Audio & Captions", comment: "")) + .navigationTitle(L10n.audioAndCaptions) .toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { if UIDevice.current.userInterfaceIdiom == .phone { @@ -74,7 +74,7 @@ struct VideoPlayerSettings: View { } label: { HStack { Image(systemName: "chevron.left") - Text("Back").font(.callout) + L10n.back.text.font(.callout) } } } diff --git a/JellyfinPlayer/Views/VideoPlayer/VideoUpNextView.swift b/JellyfinPlayer/Views/VideoPlayer/VideoUpNextView.swift index 5348d983..0f886d2c 100644 --- a/JellyfinPlayer/Views/VideoPlayer/VideoUpNextView.swift +++ b/JellyfinPlayer/Views/VideoPlayer/VideoUpNextView.swift @@ -32,7 +32,7 @@ struct VideoUpNextView: View { } label: { HStack { VStack { - Text("Play Next") + L10n.playNext.text .foregroundColor(.white) .font(.subheadline) .fontWeight(.semibold) diff --git a/Podfile b/Podfile index 603c1693..42260767 100644 --- a/Podfile +++ b/Podfile @@ -1,11 +1,20 @@ +use_frameworks! +inhibit_all_warnings! +def shared_pods + pod 'SwiftGen' +end + target 'JellyfinPlayer iOS' do platform :ios, '14.0' - use_frameworks! + shared_pods pod 'google-cast-sdk' pod 'MobileVLCKit' end target 'JellyfinPlayer tvOS' do platform :tvos, '14.0' - use_frameworks! + shared_pods pod 'TVVLCKit' +end +target 'WidgetExtension' do + shared_pods end \ No newline at end of file diff --git a/Shared/Coordinators/BasicAppSettingsCoordinator.swift b/Shared/Coordinators/BasicAppSettingsCoordinator.swift index d9ee38bb..f9b0d5f5 100644 --- a/Shared/Coordinators/BasicAppSettingsCoordinator.swift +++ b/Shared/Coordinators/BasicAppSettingsCoordinator.swift @@ -12,11 +12,11 @@ import Stinsen import SwiftUI final class BasicAppSettingsCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \BasicAppSettingsCoordinator.start) - + @Root var start = makeStart - + @ViewBuilder func makeStart() -> some View { BasicAppSettingsView(viewModel: BasicAppSettingsViewModel()) } diff --git a/Shared/Coordinators/ConnectToServerCoodinator.swift b/Shared/Coordinators/ConnectToServerCoodinator.swift index 45d47f03..ef2321f7 100644 --- a/Shared/Coordinators/ConnectToServerCoodinator.swift +++ b/Shared/Coordinators/ConnectToServerCoodinator.swift @@ -12,16 +12,16 @@ import Stinsen import SwiftUI final class ConnectToServerCoodinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \ConnectToServerCoodinator.start) @Root var start = makeStart @Route(.push) var userSignIn = makeUserSignIn - + func makeUserSignIn(server: SwiftfinStore.State.Server) -> UserSignInCoordinator { return UserSignInCoordinator(viewModel: .init(server: server)) } - + @ViewBuilder func makeStart() -> some View { ConnectToServerView(viewModel: ConnectToServerViewModel()) } diff --git a/Shared/Coordinators/FilterCoordinator.swift b/Shared/Coordinators/FilterCoordinator.swift index fa845d0c..7ff6b7c5 100644 --- a/Shared/Coordinators/FilterCoordinator.swift +++ b/Shared/Coordinators/FilterCoordinator.swift @@ -14,9 +14,9 @@ import SwiftUI typealias FilterCoordinatorParams = (filters: Binding, enabledFilterType: [FilterType], parentId: String) final class FilterCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \FilterCoordinator.start) - + @Root var start = makeStart @Binding var filters: LibraryFilters diff --git a/Shared/Coordinators/HomeCoordinator.swift b/Shared/Coordinators/HomeCoordinator.swift index e30c79af..e373de0b 100644 --- a/Shared/Coordinators/HomeCoordinator.swift +++ b/Shared/Coordinators/HomeCoordinator.swift @@ -13,7 +13,7 @@ import Stinsen import SwiftUI final class HomeCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \HomeCoordinator.start) @Root var start = makeStart @@ -34,11 +34,11 @@ final class HomeCoordinator: NavigationCoordinatable { func makeItem(item: BaseItemDto) -> ItemCoordinator { ItemCoordinator(item: item) } - + func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator { return NavigationViewCoordinator(ItemCoordinator(item: item)) } - + func makeModalLibrary(params: LibraryCoordinatorParams) -> NavigationViewCoordinator { return NavigationViewCoordinator(LibraryCoordinator(viewModel: params.viewModel, title: params.title)) } diff --git a/Shared/Coordinators/ItemCoordinator.swift b/Shared/Coordinators/ItemCoordinator.swift index 5e578efb..286c2bd2 100644 --- a/Shared/Coordinators/ItemCoordinator.swift +++ b/Shared/Coordinators/ItemCoordinator.swift @@ -13,7 +13,7 @@ import Stinsen import SwiftUI final class ItemCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \ItemCoordinator.start) @Root var start = makeStart diff --git a/Shared/Coordinators/LibraryCoordinator.swift b/Shared/Coordinators/LibraryCoordinator.swift index a5ef3495..50a4485a 100644 --- a/Shared/Coordinators/LibraryCoordinator.swift +++ b/Shared/Coordinators/LibraryCoordinator.swift @@ -15,7 +15,7 @@ import SwiftUI typealias LibraryCoordinatorParams = (viewModel: LibraryViewModel, title: String) final class LibraryCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \LibraryCoordinator.start) @Root var start = makeStart @@ -49,7 +49,7 @@ final class LibraryCoordinator: NavigationCoordinatable { func makeItem(item: BaseItemDto) -> ItemCoordinator { ItemCoordinator(item: item) } - + func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator { return NavigationViewCoordinator(ItemCoordinator(item: item)) } diff --git a/Shared/Coordinators/LibraryListCoordinator.swift b/Shared/Coordinators/LibraryListCoordinator.swift index 88377644..e79f25c2 100644 --- a/Shared/Coordinators/LibraryListCoordinator.swift +++ b/Shared/Coordinators/LibraryListCoordinator.swift @@ -12,7 +12,7 @@ import Stinsen import SwiftUI final class LibraryListCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \LibraryListCoordinator.start) @Root var start = makeStart diff --git a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift index 7d72d2c9..3172a5b0 100644 --- a/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/iOSMainCoordinator.swift @@ -25,13 +25,13 @@ final class MainCoordinator: NavigationCoordinatable { } else { self.stack = NavigationStack(initial: \MainCoordinator.serverList) } - + ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory DataLoader.sharedUrlCache.diskCapacity = 1000 * 1024 * 1024 // 1000MB disk WidgetCenter.shared.reloadAllTimelines() UIScrollView.appearance().keyboardDismissMode = .onDrag - + // Back bar button item setup let backButtonBackgroundImage = UIImage(systemName: "chevron.backward.circle.fill") let barAppearance = UINavigationBar.appearance() diff --git a/Shared/Coordinators/MainCoordinator/iOSMainTabCoordinator.swift b/Shared/Coordinators/MainCoordinator/iOSMainTabCoordinator.swift index 3be7131b..cfa95e16 100644 --- a/Shared/Coordinators/MainCoordinator/iOSMainTabCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/iOSMainTabCoordinator.swift @@ -26,7 +26,7 @@ final class MainTabCoordinator: TabCoordinatable { @ViewBuilder func makeHomeTab(isActive: Bool) -> some View { Image(systemName: "house") - Text("Home") + L10n.home.text } func makeAllMedia() -> NavigationViewCoordinator { @@ -35,14 +35,14 @@ final class MainTabCoordinator: TabCoordinatable { @ViewBuilder func makeAllMediaTab(isActive: Bool) -> some View { Image(systemName: "folder") - Text("All Media") + L10n.allMedia.text } @ViewBuilder func customize(_ view: AnyView) -> some View { view.onAppear { AppURLHandler.shared.appURLState = .allowed // TODO: todo - DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { AppURLHandler.shared.processLaunchedURLIfNeeded() } } diff --git a/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift b/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift index 05d77b94..122a870f 100644 --- a/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/tvOSMainCoordinator.swift @@ -17,14 +17,14 @@ final class MainCoordinator: NavigationCoordinatable { @Root var mainTab = makeMainTab @Root var serverList = makeServerList - + init() { if SessionManager.main.currentLogin != nil { self.stack = NavigationStack(initial: \MainCoordinator.mainTab) } else { self.stack = NavigationStack(initial: \MainCoordinator.serverList) } - + ImageCache.shared.costLimit = 125 * 1024 * 1024 // 125MB memory DataLoader.sharedUrlCache.diskCapacity = 1000 * 1024 * 1024 // 1000MB disk diff --git a/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift b/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift index 8be5a5c6..ea0a5be7 100644 --- a/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift +++ b/Shared/Coordinators/MainCoordinator/tvOSMainTabCoordinator.swift @@ -19,24 +19,24 @@ final class MainTabCoordinator: TabCoordinatable { \MainTabCoordinator.other, \MainTabCoordinator.settings ]) - + @Route(tabItem: makeHomeTab) var home = makeHome @Route(tabItem: makeTvTab) var tv = makeTv @Route(tabItem: makeMoviesTab) var movies = makeMovies @Route(tabItem: makeOtherTab) var other = makeOther @Route(tabItem: makeSettingsTab) var settings = makeSettings - + func makeHome() -> NavigationViewCoordinator { return NavigationViewCoordinator(HomeCoordinator()) } - + @ViewBuilder func makeHomeTab(isActive: Bool) -> some View { HStack { Image(systemName: "house") - Text("Home") + L10n.home.text } } - + func makeTv() -> NavigationViewCoordinator { return NavigationViewCoordinator(TVLibrariesCoordinator(viewModel: TVLibrariesViewModel(), title: "TV Shows")) } @@ -47,7 +47,7 @@ final class MainTabCoordinator: TabCoordinatable { Text("TV Shows") } } - + func makeMovies() -> NavigationViewCoordinator { return NavigationViewCoordinator(MovieLibrariesCoordinator(viewModel: MovieLibrariesViewModel(), title: "Movies")) } @@ -69,11 +69,11 @@ final class MainTabCoordinator: TabCoordinatable { Text("Other") } } - + func makeSettings() -> NavigationViewCoordinator { return NavigationViewCoordinator(SettingsCoordinator()) } - + @ViewBuilder func makeSettingsTab(isActive: Bool) -> some View { HStack { Image(systemName: "gearshape.fill") diff --git a/Shared/Coordinators/MoviesLibrariesCoordinator.swift b/Shared/Coordinators/MoviesLibrariesCoordinator.swift index 9c530d3c..9adda2e5 100644 --- a/Shared/Coordinators/MoviesLibrariesCoordinator.swift +++ b/Shared/Coordinators/MoviesLibrariesCoordinator.swift @@ -13,7 +13,7 @@ import Stinsen import SwiftUI final class MovieLibrariesCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \MovieLibrariesCoordinator.start) @Root var start = makeStart diff --git a/Shared/Coordinators/SearchCoordinator.swift b/Shared/Coordinators/SearchCoordinator.swift index 9d66e6b4..40388163 100644 --- a/Shared/Coordinators/SearchCoordinator.swift +++ b/Shared/Coordinators/SearchCoordinator.swift @@ -13,7 +13,7 @@ import SwiftUI import JellyfinAPI final class SearchCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \SearchCoordinator.start) @Root var start = makeStart diff --git a/Shared/Coordinators/ServerListCoordinator.swift b/Shared/Coordinators/ServerListCoordinator.swift index d60abebd..ba773b7b 100644 --- a/Shared/Coordinators/ServerListCoordinator.swift +++ b/Shared/Coordinators/ServerListCoordinator.swift @@ -12,26 +12,26 @@ import Stinsen import SwiftUI final class ServerListCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \ServerListCoordinator.start) - + @Root var start = makeStart @Route(.push) var connectToServer = makeConnectToServer @Route(.push) var userList = makeUserList @Route(.modal) var basicAppSettings = makeBasicAppSettings - + func makeConnectToServer() -> ConnectToServerCoodinator { ConnectToServerCoodinator() } - + func makeUserList(server: SwiftfinStore.State.Server) -> UserListCoordinator { UserListCoordinator(viewModel: .init(server: server)) } - + func makeBasicAppSettings() -> NavigationViewCoordinator { NavigationViewCoordinator(BasicAppSettingsCoordinator()) } - + @ViewBuilder func makeStart() -> some View { ServerListView(viewModel: ServerListViewModel()) } diff --git a/Shared/Coordinators/SettingsCoordinator.swift b/Shared/Coordinators/SettingsCoordinator.swift index f24bab01..c0664d7e 100644 --- a/Shared/Coordinators/SettingsCoordinator.swift +++ b/Shared/Coordinators/SettingsCoordinator.swift @@ -12,7 +12,7 @@ import Stinsen import SwiftUI final class SettingsCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \SettingsCoordinator.start) @Root var start = makeStart diff --git a/Shared/Coordinators/TVLibrariesCoordinator.swift b/Shared/Coordinators/TVLibrariesCoordinator.swift index 2ad50744..b3f85337 100644 --- a/Shared/Coordinators/TVLibrariesCoordinator.swift +++ b/Shared/Coordinators/TVLibrariesCoordinator.swift @@ -13,7 +13,7 @@ import Stinsen import SwiftUI final class TVLibrariesCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \TVLibrariesCoordinator.start) @Root var start = makeStart diff --git a/Shared/Coordinators/UserListCoordinator.swift b/Shared/Coordinators/UserListCoordinator.swift index da2e67bd..949c1018 100644 --- a/Shared/Coordinators/UserListCoordinator.swift +++ b/Shared/Coordinators/UserListCoordinator.swift @@ -1,5 +1,5 @@ // - /* + /* * SwiftFin is subject to the terms of the Mozilla Public * License, v2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. @@ -12,27 +12,27 @@ import Stinsen import SwiftUI final class UserListCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \UserListCoordinator.start) - + @Root var start = makeStart @Route(.push) var userSignIn = makeUserSignIn @Route(.push) var serverDetail = makeServerDetail - + let viewModel: UserListViewModel - + init(viewModel: UserListViewModel) { self.viewModel = viewModel } - + func makeUserSignIn(server: SwiftfinStore.State.Server) -> UserSignInCoordinator { return UserSignInCoordinator(viewModel: .init(server: server)) } - + func makeServerDetail(server: SwiftfinStore.State.Server) -> ServerDetailCoordinator { return ServerDetailCoordinator(viewModel: .init(server: server)) } - + @ViewBuilder func makeStart() -> some View { UserListView(viewModel: viewModel) } diff --git a/Shared/Coordinators/UserSignInCoordinator.swift b/Shared/Coordinators/UserSignInCoordinator.swift index f4e03a87..09d45e1e 100644 --- a/Shared/Coordinators/UserSignInCoordinator.swift +++ b/Shared/Coordinators/UserSignInCoordinator.swift @@ -12,17 +12,17 @@ import Stinsen import SwiftUI final class UserSignInCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \UserSignInCoordinator.start) - + @Root var start = makeStart - + let viewModel: UserSignInViewModel - + init(viewModel: UserSignInViewModel) { self.viewModel = viewModel } - + @ViewBuilder func makeStart() -> some View { UserSignInView(viewModel: viewModel) } diff --git a/Shared/Coordinators/VideoPlayerCoordinator.swift b/Shared/Coordinators/VideoPlayerCoordinator.swift index 921d52f9..b4feb6fd 100644 --- a/Shared/Coordinators/VideoPlayerCoordinator.swift +++ b/Shared/Coordinators/VideoPlayerCoordinator.swift @@ -13,11 +13,11 @@ import Stinsen import SwiftUI final class VideoPlayerCoordinator: NavigationCoordinatable { - + let stack = NavigationStack(initial: \VideoPlayerCoordinator.start) @Root var start = makeStart - + let item: BaseItemDto init(item: BaseItemDto) { diff --git a/Shared/Errors/ErrorMessage.swift b/Shared/Errors/ErrorMessage.swift index 0f14fe59..6b2ddb8b 100644 --- a/Shared/Errors/ErrorMessage.swift +++ b/Shared/Errors/ErrorMessage.swift @@ -16,7 +16,7 @@ struct ErrorMessage: Identifiable { let title: String let displayMessage: String let logConstructor: LogConstructor - + // Chosen value such that if an error has this code, don't show the code to the UI // This was chosen because of its unlikelyhood to ever be used static let noShowErrorCode = -69420 diff --git a/Shared/Errors/NetworkError.swift b/Shared/Errors/NetworkError.swift index 10a3c2d2..0905158c 100644 --- a/Shared/Errors/NetworkError.swift +++ b/Shared/Errors/NetworkError.swift @@ -85,13 +85,13 @@ enum NetworkError: Error { logMessage = "Cannot connect to host." logConstructor.message = logMessage errorMessage = ErrorMessage(code: err._code, - title: "Error", + title: L10n.error, displayMessage: displayMessage, logConstructor: logConstructor) default: logConstructor.message = logMessage errorMessage = ErrorMessage(code: err._code, - title: "Error", + title: L10n.error, displayMessage: displayMessage, logConstructor: logConstructor) } @@ -111,7 +111,7 @@ enum NetworkError: Error { case .error: logConstructor.message = logMessage errorMessage = ErrorMessage(code: 0, - title: "Error", + title: L10n.error, displayMessage: displayMessage, logConstructor: logConstructor) } @@ -140,7 +140,7 @@ enum NetworkError: Error { default: logConstructor.message = logMessage errorMessage = ErrorMessage(code: code, - title: "Error", + title: L10n.error, displayMessage: displayMessage, logConstructor: logConstructor) } diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift index 6ca62ffd..b4b23403 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+Stackable.swift @@ -15,11 +15,11 @@ extension BaseItemDto: PortraitImageStackable { public func imageURLContsructor(maxWidth: Int) -> URL { return self.getPrimaryImage(maxWidth: maxWidth) } - + public var title: String { return self.name ?? "" } - + public var description: String? { switch self.itemType { case .season: @@ -31,11 +31,11 @@ extension BaseItemDto: PortraitImageStackable { return nil } } - + public var blurHash: String { return self.getPrimaryImageBlurHash() } - + public var failureInitials: String { guard let name = self.name else { return "" } let initials = name.split(separator: " ").compactMap({ String($0).first }) diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift index 48979800..b6e5e8bc 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDtoExtensions.swift @@ -14,7 +14,7 @@ import UIKit // 001fC^ = dark grey plain blurhash public extension BaseItemDto { - + // MARK: Images func getSeriesBackdropImageBlurHash() -> String { @@ -80,7 +80,7 @@ public extension BaseItemDto { func getEpisodeLocator() -> String? { if let seasonNo = parentIndexNumber, let episodeNo = indexNumber { - return "S\(seasonNo):E\(episodeNo)" + return L10n.seasonAndEpisode(String(seasonNo), String(episodeNo)) } return nil } @@ -152,17 +152,17 @@ public extension BaseItemDto { return "\(String(progminutes))m" } } - + // MARK: ItemType - + enum ItemType: String { case movie = "Movie" case season = "Season" case episode = "Episode" case series = "Series" - + case unknown - + var showDetails: Bool { switch self { case .season, .series: @@ -172,14 +172,14 @@ public extension BaseItemDto { } } } - + var itemType: ItemType { guard let originalType = self.type, let knownType = ItemType(rawValue: originalType) else { return .unknown } return knownType } - + // MARK: PortraitHeaderViewURL - + func portraitHeaderViewURL(maxWidth: Int) -> URL { switch self.itemType { case .movie, .season, .series: diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift index 6d9a06a3..6135ef69 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemPersonExtensions.swift @@ -10,7 +10,7 @@ import JellyfinAPI import UIKit extension BaseItemPerson { - + // MARK: Get Image func getImage(baseURL: String, maxWidth: Int) -> URL { let imageType = "Primary" @@ -28,10 +28,9 @@ extension BaseItemPerson { return imageBlurHashes?.primary?[imgTag] ?? "001fC^" } - - + // MARK: First Role - + // Jellyfin will grab all roles the person played in the show which makes the role // text too long. This will grab the first role which: // - assumes that the most important role is the first @@ -40,16 +39,16 @@ extension BaseItemPerson { guard let role = self.role else { return nil } let split = role.split(separator: "/") guard split.count > 1 else { return role } - + guard let firstRole = split.first?.trimmingCharacters(in: CharacterSet(charactersIn: " ")), let lastRole = split.last?.trimmingCharacters(in: CharacterSet(charactersIn: " ")) else { return role } - + var final = firstRole - + if let lastOpenIndex = lastRole.lastIndex(of: "("), let lastClosingIndex = lastRole.lastIndex(of: ")") { let roleText = lastRole[lastOpenIndex...lastClosingIndex] final.append(" \(roleText)") } - + return final } } @@ -59,19 +58,19 @@ extension BaseItemPerson: PortraitImageStackable { public func imageURLContsructor(maxWidth: Int) -> URL { return self.getImage(baseURL: SessionManager.main.currentLogin.server.currentURI, maxWidth: maxWidth) } - + public var title: String { return self.name ?? "" } - + public var description: String? { return self.firstRole() } - + public var blurHash: String { return self.getBlurHash() } - + public var failureInitials: String { guard let name = self.name else { return "" } let initials = name.split(separator: " ").compactMap({ String($0).first }) @@ -81,7 +80,7 @@ extension BaseItemPerson: PortraitImageStackable { // MARK: DiplayedType extension BaseItemPerson { - + // Only displayed person types. // Will ignore people like "GuestStar" enum DisplayedType: String, CaseIterable { @@ -89,7 +88,7 @@ extension BaseItemPerson { case director = "Director" case writer = "Writer" case producer = "Producer" - + static var allCasesRaw: [String] { return self.allCases.map({ $0.rawValue }) } diff --git a/Shared/Extensions/JellyfinAPIExtensions/JellyfinAPIError.swift b/Shared/Extensions/JellyfinAPIExtensions/JellyfinAPIError.swift index f74a5a3c..0bfe3a81 100644 --- a/Shared/Extensions/JellyfinAPIExtensions/JellyfinAPIError.swift +++ b/Shared/Extensions/JellyfinAPIExtensions/JellyfinAPIError.swift @@ -10,13 +10,13 @@ import Foundation struct JellyfinAPIError: Error { - + private let message: String - + init(_ message: String) { self.message = message } - + var localizedDescription: String { return message } diff --git a/Shared/Extensions/StringExtensions.swift b/Shared/Extensions/StringExtensions.swift index 7261ddb9..64b4a63d 100644 --- a/Shared/Extensions/StringExtensions.swift +++ b/Shared/Extensions/StringExtensions.swift @@ -6,6 +6,7 @@ */ import Foundation +import SwiftUI extension String { func removeRegexMatches(pattern: String, replaceWith: String = "") -> String { @@ -31,4 +32,8 @@ extension String { return "\(padString)\(self)" } + + var text: Text { + Text(self) + } } diff --git a/Shared/Generated/Strings.swift b/Shared/Generated/Strings.swift new file mode 100644 index 00000000..792f5bd1 --- /dev/null +++ b/Shared/Generated/Strings.swift @@ -0,0 +1,184 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +internal enum L10n { + /// Accessibility + internal static let accessibility = L10n.tr("Localizable", "accessibility") + /// All Genres + internal static let allGenres = L10n.tr("Localizable", "allGenres") + /// All Media + internal static let allMedia = L10n.tr("Localizable", "allMedia") + /// Appearance + internal static let appearance = L10n.tr("Localizable", "appearance") + /// Apply + internal static let apply = L10n.tr("Localizable", "apply") + /// Audio & Captions + internal static let audioAndCaptions = L10n.tr("Localizable", "audioAndCaptions") + /// Audio Track + internal static let audioTrack = L10n.tr("Localizable", "audioTrack") + /// Back + internal static let back = L10n.tr("Localizable", "back") + /// CAST + internal static let cast = L10n.tr("Localizable", "cast") + /// Change Server + internal static let changeServer = L10n.tr("Localizable", "changeServer") + /// Closed Captions + internal static let closedCaptions = L10n.tr("Localizable", "closedCaptions") + /// Connect + internal static let connect = L10n.tr("Localizable", "connect") + /// Connect Manually + internal static let connectManually = L10n.tr("Localizable", "connectManually") + /// Connect to Jellyfin + internal static let connectToJellyfin = L10n.tr("Localizable", "connectToJellyfin") + /// Connect to Server + internal static let connectToServer = L10n.tr("Localizable", "connectToServer") + /// Continue Watching + internal static let continueWatching = L10n.tr("Localizable", "continueWatching") + /// Dark + internal static let dark = L10n.tr("Localizable", "dark") + /// DIRECTOR + internal static let director = L10n.tr("Localizable", "director") + /// Discovered Servers + internal static let discoveredServers = L10n.tr("Localizable", "discoveredServers") + /// Display order + internal static let displayOrder = L10n.tr("Localizable", "displayOrder") + /// Empty Next Up + internal static let emptyNextUp = L10n.tr("Localizable", "emptyNextUp") + /// Episodes + internal static let episodes = L10n.tr("Localizable", "episodes") + /// Error + internal static let error = L10n.tr("Localizable", "error") + /// Filter Results + internal static let filterResults = L10n.tr("Localizable", "filterResults") + /// Filters + internal static let filters = L10n.tr("Localizable", "filters") + /// Genres + internal static let genres = L10n.tr("Localizable", "genres") + /// Home + internal static let home = L10n.tr("Localizable", "home") + /// Latest %@ + internal static func latestWithString(_ p1: Any) -> String { + return L10n.tr("Localizable", "latestWithString", String(describing: p1)) + } + /// Library + internal static let library = L10n.tr("Localizable", "library") + /// Light + internal static let light = L10n.tr("Localizable", "light") + /// Loading + internal static let loading = L10n.tr("Localizable", "loading") + /// Local Servers + internal static let localServers = L10n.tr("Localizable", "localServers") + /// Login + internal static let login = L10n.tr("Localizable", "login") + /// Login to %@ + internal static func loginToWithString(_ p1: Any) -> String { + return L10n.tr("Localizable", "loginToWithString", String(describing: p1)) + } + /// More Like This + internal static let moreLikeThis = L10n.tr("Localizable", "moreLikeThis") + /// Next Up + internal static let nextUp = L10n.tr("Localizable", "nextUp") + /// No Cast devices found.. + internal static let noCastdevicesfound = L10n.tr("Localizable", "noCastdevicesfound") + /// No results. + internal static let noResults = L10n.tr("Localizable", "noResults") + /// Type: %@ not implemented yet :( + internal static func notImplementedYetWithType(_ p1: Any) -> String { + return L10n.tr("Localizable", "notImplementedYetWithType", String(describing: p1)) + } + /// Ok + internal static let ok = L10n.tr("Localizable", "ok") + /// Other User + internal static let otherUser = L10n.tr("Localizable", "otherUser") + /// Page %1$@ of %2$@ + internal static func pageOfWithNumbers(_ p1: Any, _ p2: Any) -> String { + return L10n.tr("Localizable", "pageOfWithNumbers", String(describing: p1), String(describing: p2)) + } + /// Password + internal static let password = L10n.tr("Localizable", "password") + /// Play + internal static let play = L10n.tr("Localizable", "play") + /// Playback settings + internal static let playbackSettings = L10n.tr("Localizable", "playbackSettings") + /// Playback Speed + internal static let playbackSpeed = L10n.tr("Localizable", "playbackSpeed") + /// Play Next + internal static let playNext = L10n.tr("Localizable", "playNext") + /// Reset + internal static let reset = L10n.tr("Localizable", "reset") + /// Search... + internal static let search = L10n.tr("Localizable", "search") + /// S%1$@:E%2$@ + internal static func seasonAndEpisode(_ p1: Any, _ p2: Any) -> String { + return L10n.tr("Localizable", "seasonAndEpisode", String(describing: p1), String(describing: p2)) + } + /// Seasons + internal static let seasons = L10n.tr("Localizable", "seasons") + /// See All + internal static let seeAll = L10n.tr("Localizable", "seeAll") + /// Select Cast Destination + internal static let selectCastDestination = L10n.tr("Localizable", "selectCastDestination") + /// Server Information + internal static let serverInformation = L10n.tr("Localizable", "serverInformation") + /// Server URL + internal static let serverURL = L10n.tr("Localizable", "serverURL") + /// Signed in as %@ + internal static func signedInAsWithString(_ p1: Any) -> String { + return L10n.tr("Localizable", "signedInAsWithString", String(describing: p1)) + } + /// Sort by + internal static let sortBy = L10n.tr("Localizable", "sortBy") + /// STUDIO + internal static let studio = L10n.tr("Localizable", "studio") + /// Studios + internal static let studios = L10n.tr("Localizable", "studios") + /// Suggestions + internal static let suggestions = L10n.tr("Localizable", "suggestions") + /// Switch user + internal static let switchUser = L10n.tr("Localizable", "switchUser") + /// System + internal static let system = L10n.tr("Localizable", "system") + /// Tags + internal static let tags = L10n.tr("Localizable", "tags") + /// Try again + internal static let tryAgain = L10n.tr("Localizable", "tryAgain") + /// Username + internal static let username = L10n.tr("Localizable", "username") + /// Who's watching? + internal static let whosWatching = L10n.tr("Localizable", "WhosWatching") + /// WIP + internal static let wip = L10n.tr("Localizable", "wip") + /// Your Favorites + internal static let yourFavorites = L10n.tr("Localizable", "yourFavorites") +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension L10n { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + let format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table) + return String(format: format, locale: Locale.current, arguments: args) + } +} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type diff --git a/Shared/Objects/AppAppearance.swift b/Shared/Objects/AppAppearance.swift index a3bc58a5..fdf6ba8d 100644 --- a/Shared/Objects/AppAppearance.swift +++ b/Shared/Objects/AppAppearance.swift @@ -16,7 +16,14 @@ enum AppAppearance: String, CaseIterable, Defaults.Serializable { case light var localizedName: String { - return NSLocalizedString(self.rawValue.capitalized, comment: "") + switch self { + case .system: + return L10n.system + case .dark: + return L10n.dark + case .light: + return L10n.light + } } var style: UIUserInterfaceStyle { diff --git a/Shared/Objects/DetailItem.swift b/Shared/Objects/DetailItem.swift index bf5b1792..fd017f2d 100644 --- a/Shared/Objects/DetailItem.swift +++ b/Shared/Objects/DetailItem.swift @@ -18,11 +18,8 @@ enum DetailItemType: String { } struct DetailItem { - + let baseItem: BaseItemDto let type: DetailItemType - - - - + } diff --git a/Shared/Objects/Typings.swift b/Shared/Objects/Typings.swift index 72a46e14..64293d42 100644 --- a/Shared/Objects/Typings.swift +++ b/Shared/Objects/Typings.swift @@ -73,11 +73,11 @@ enum ItemType: String { case movie = "Movie" case series = "Series" case season = "Season" - + var localized: String { switch self { case .episode: - return "Episodes" + return L10n.episodes case .movie: return "Movies" case .series: diff --git a/Shared/ServerDiscovery/ServerDiscovery.swift b/Shared/ServerDiscovery/ServerDiscovery.swift index 6f34789d..841b8889 100644 --- a/Shared/ServerDiscovery/ServerDiscovery.swift +++ b/Shared/ServerDiscovery/ServerDiscovery.swift @@ -46,7 +46,7 @@ public class ServerDiscovery { case name = "Name" } } - + private let broadcastConn: UDPBroadcastConnection public init() { diff --git a/Shared/Singleton/SessionManager.swift b/Shared/Singleton/SessionManager.swift index 333ba811..8666860b 100644 --- a/Shared/Singleton/SessionManager.swift +++ b/Shared/Singleton/SessionManager.swift @@ -19,66 +19,66 @@ typealias CurrentLogin = (server: SwiftfinStore.State.Server, user: SwiftfinStor // MARK: NewSessionManager final class SessionManager { - + // MARK: currentLogin private(set) var currentLogin: CurrentLogin! - + // MARK: main static let main = SessionManager() - + private init() { if let lastUserID = SwiftfinStore.Defaults.suite[.lastServerUserID], let user = try? SwiftfinStore.dataStack.fetchOne(From(), [Where("id == %@", lastUserID)]) { - + guard let server = user.server, let accessToken = user.accessToken else { fatalError("No associated server or access token for last user?") } guard let existingServer = SwiftfinStore.dataStack.fetchExisting(server) else { return } - + JellyfinAPI.basePath = server.currentURI setAuthHeader(with: accessToken.value) currentLogin = (server: existingServer.state, user: user.state) } } - + func fetchServers() -> [SwiftfinStore.State.Server] { let servers = try! SwiftfinStore.dataStack.fetchAll(From()) return servers.map({ $0.state }) } - + func fetchUsers(for server: SwiftfinStore.State.Server) -> [SwiftfinStore.State.User] { guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(From(), Where("id == %@", server.id)) else { fatalError("No stored server associated with given state server?") } return storedServer.users.map({ $0.state }).sorted(by: { $0.username < $1.username }) } - + // Connects to a server at the given uri, storing if successful func connectToServer(with uri: String) -> AnyPublisher { var uriComponents = URLComponents(string: uri) ?? URLComponents() - + if uriComponents.scheme == nil { uriComponents.scheme = SwiftfinStore.Defaults.suite[.defaultHTTPScheme].rawValue } - + var uri = uriComponents.string ?? "" - + if uri.last == "/" { uri = String(uri.dropLast()) } - + JellyfinAPI.basePath = uri - + return SystemAPI.getPublicSystemInfo() .tryMap({ response -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in - + let transaction = SwiftfinStore.dataStack.beginUnsafe() let newServer = transaction.create(Into()) - + guard let name = response.serverName, let id = response.id, let os = response.operatingSystem, let version = response.version else { throw JellyfinAPIError("Missing server data from network call") } - + newServer.uris = [uri] newServer.currentURI = uri newServer.name = name @@ -86,13 +86,13 @@ final class SessionManager { newServer.os = os newServer.version = version newServer.users = [] - + // Check for existing server on device if let existingServer = try? SwiftfinStore.dataStack.fetchOne(From(), [Where("id == %@", newServer.id)]) { throw SwiftfinStore.Errors.existingServer(existingServer.state) } - + return (newServer, transaction) }) .handleEvents(receiveOutput: { (_, transaction) in @@ -103,21 +103,21 @@ final class SessionManager { }) .eraseToAnyPublisher() } - + func addURIToServer(server: SwiftfinStore.State.Server, uri: String) -> AnyPublisher { return Just(server) .tryMap { server -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in - + let transaction = SwiftfinStore.dataStack.beginUnsafe() - + guard let existingServer = try? SwiftfinStore.dataStack.fetchOne(From(), [Where("id == %@", server.id)]) else { fatalError("No stored server associated with given state server?") } - + guard let editServer = transaction.edit(existingServer) else { fatalError("Can't get proxy for existing object?") } editServer.uris.insert(uri) - + return (editServer, transaction) } .handleEvents(receiveOutput: { (server, transaction) in @@ -128,25 +128,25 @@ final class SessionManager { }) .eraseToAnyPublisher() } - + func setServerCurrentURI(server: SwiftfinStore.State.Server, uri: String) -> AnyPublisher { return Just(server) .tryMap { server -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in - + let transaction = SwiftfinStore.dataStack.beginUnsafe() - + guard let existingServer = try? SwiftfinStore.dataStack.fetchOne(From(), [Where("id == %@", server.id)]) else { fatalError("No stored server associated with given state server?") } - + if !existingServer.uris.contains(uri) { fatalError("Attempting to set current uri while server doesn't contain it?") } - + guard let editServer = transaction.edit(existingServer) else { fatalError("Can't get proxy for existing object?") } editServer.currentURI = uri - + return (editServer, transaction) } .handleEvents(receiveOutput: { (server, transaction) in @@ -157,57 +157,57 @@ final class SessionManager { }) .eraseToAnyPublisher() } - + // Logs in a user with an associated server, storing if successful func loginUser(server: SwiftfinStore.State.Server, username: String, password: String) -> AnyPublisher { setAuthHeader(with: "") - + JellyfinAPI.basePath = server.currentURI - + return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password)) .tryMap({ response -> (SwiftfinStore.Models.StoredServer, SwiftfinStore.Models.StoredUser, UnsafeDataTransaction) in - + guard let accessToken = response.accessToken else { throw JellyfinAPIError("Access token missing from network call") } - + let transaction = SwiftfinStore.dataStack.beginUnsafe() let newUser = transaction.create(Into()) - + guard let username = response.user?.name, let id = response.user?.id else { throw JellyfinAPIError("Missing user data from network call") } - + newUser.username = username newUser.id = id newUser.appleTVID = "" - + // Check for existing user on device if let existingUser = try? SwiftfinStore.dataStack.fetchOne(From(), [Where("id == %@", newUser.id)]) { throw SwiftfinStore.Errors.existingUser(existingUser.state) } - + let newAccessToken = transaction.create(Into()) newAccessToken.value = accessToken newUser.accessToken = newAccessToken - + guard let userServer = try? SwiftfinStore.dataStack.fetchOne(From(), [Where("id == %@", server.id)]) else { fatalError("No stored server associated with given state server?") } - + guard let editUserServer = transaction.edit(userServer) else { fatalError("Can't get proxy for existing object?") } editUserServer.users.insert(newUser) - + return (editUserServer, newUser, transaction) }) .handleEvents(receiveOutput: { [unowned self] (server, user, transaction) in setAuthHeader(with: user.accessToken?.value ?? "") try? transaction.commitAndWait() - + // Fetch for the right queue let currentServer = SwiftfinStore.dataStack.fetchExisting(server)! let currentUser = SwiftfinStore.dataStack.fetchExisting(user)! - + SwiftfinStore.Defaults.suite[.lastServerUserID] = user.id - + currentLogin = (server: currentServer.state, user: currentUser.state) SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil) }) @@ -216,7 +216,7 @@ final class SessionManager { }) .eraseToAnyPublisher() } - + func loginUser(server: SwiftfinStore.State.Server, user: SwiftfinStore.State.User) { JellyfinAPI.basePath = server.currentURI SwiftfinStore.Defaults.suite[.lastServerUserID] = user.id @@ -224,7 +224,7 @@ final class SessionManager { currentLogin = (server: server, user: user) SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil) } - + func logout() { currentLogin = nil JellyfinAPI.basePath = "" @@ -232,66 +232,66 @@ final class SessionManager { SwiftfinStore.Defaults.suite[.lastServerUserID] = nil SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil) } - + func purge() { // Delete all servers let servers = fetchServers() - + for server in servers { delete(server: server) } - + // Delete UserDefaults SwiftfinStore.Defaults.suite.removeAll() - + SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didPurge, object: nil) } - + func delete(user: SwiftfinStore.State.User) { guard let storedUser = try? SwiftfinStore.dataStack.fetchOne(From(), [Where("id == %@", user.id)]) else { fatalError("No stored user for state user?")} _delete(user: storedUser, transaction: nil) } - + func delete(server: SwiftfinStore.State.Server) { guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(From(), [Where("id == %@", server.id)]) else { fatalError("No stored server for state server?")} _delete(server: storedServer, transaction: nil) } - + private func _delete(user: SwiftfinStore.Models.StoredUser, transaction: UnsafeDataTransaction?) { guard let storedAccessToken = user.accessToken else { fatalError("No access token for stored user?")} - + let transaction = transaction == nil ? SwiftfinStore.dataStack.beginUnsafe() : transaction! transaction.delete(storedAccessToken) transaction.delete(user) try? transaction.commitAndWait() } - + private func _delete(server: SwiftfinStore.Models.StoredServer, transaction: UnsafeDataTransaction?) { let transaction = transaction == nil ? SwiftfinStore.dataStack.beginUnsafe() : transaction! - + for user in server.users { _delete(user: user, transaction: transaction) } - + transaction.delete(server) try? transaction.commitAndWait() } - + private func setAuthHeader(with accessToken: String) { let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String var deviceName = UIDevice.current.name deviceName = deviceName.folding(options: .diacriticInsensitive, locale: .current) deviceName = String(deviceName.unicodeScalars.filter { CharacterSet.urlQueryAllowed.contains($0) }) - + let platform: String #if os(tvOS) platform = "tvOS" #else platform = "iOS" #endif - + var header = "MediaBrowser " header.append("Client=\"Jellyfin \(platform)\", ") header.append("Device=\"\(deviceName)\", ") diff --git a/Shared/Singleton/SwiftfinNotificationCenter.swift b/Shared/Singleton/SwiftfinNotificationCenter.swift index 2bd2d680..a5d8407f 100644 --- a/Shared/Singleton/SwiftfinNotificationCenter.swift +++ b/Shared/Singleton/SwiftfinNotificationCenter.swift @@ -10,11 +10,11 @@ import Foundation enum SwiftfinNotificationCenter { - + static let main: NotificationCenter = { return NotificationCenter() }() - + enum Keys { static let didSignIn = Notification.Name("didSignIn") static let didSignOut = Notification.Name("didSignOut") diff --git a/Shared/SwiftfinStore/SwiftfinStore.swift b/Shared/SwiftfinStore/SwiftfinStore.swift index 4dd50ef8..db7fb87c 100644 --- a/Shared/SwiftfinStore/SwiftfinStore.swift +++ b/Shared/SwiftfinStore/SwiftfinStore.swift @@ -1,5 +1,5 @@ // - /* + /* * SwiftFin is subject to the terms of the Mozilla Public * License, v2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. @@ -12,12 +12,12 @@ import CoreStore import Defaults enum SwiftfinStore { - + // MARK: State // Safe, copyable representations of their underlying CoreStoredObject // Relationships are represented by the related object's IDs or value enum State { - + struct Server { let uris: Set let currentURI: String @@ -26,7 +26,7 @@ enum SwiftfinStore { let os: String let version: String let userIDs: [String] - + fileprivate init(uris: Set, currentURI: String, name: String, id: String, os: String, version: String, usersIDs: [String]) { self.uris = uris self.currentURI = currentURI @@ -36,7 +36,7 @@ enum SwiftfinStore { self.version = version self.userIDs = usersIDs } - + static var sample: Server { return Server(uris: ["https://www.notaurl.com", "http://www.maybeaurl.org"], currentURI: "https://www.notaurl.com", @@ -47,20 +47,20 @@ enum SwiftfinStore { usersIDs: ["1", "2"]) } } - + struct User { let username: String let id: String let serverID: String let accessToken: String - + fileprivate init(username: String, id: String, serverID: String, accessToken: String) { self.username = username self.id = id self.serverID = serverID self.accessToken = accessToken } - + static var sample: User { return User(username: "JohnnyAppleseed", id: "123abc", @@ -69,33 +69,33 @@ enum SwiftfinStore { } } } - + // MARK: Models enum Models { - + final class StoredServer: CoreStoreObject { - + @Field.Coded("uris", coder: FieldCoders.Json.self) var uris: Set = [] - + @Field.Stored("currentURI") var currentURI: String = "" - + @Field.Stored("name") var name: String = "" - + @Field.Stored("id") var id: String = "" - + @Field.Stored("os") var os: String = "" - + @Field.Stored("version") var version: String = "" - + @Field.Relationship("users", inverse: \StoredUser.$server) var users: Set - + var state: State.Server { return State.Server(uris: uris, currentURI: currentURI, @@ -106,24 +106,24 @@ enum SwiftfinStore { usersIDs: users.map({ $0.id })) } } - + final class StoredUser: CoreStoreObject { - + @Field.Stored("username") var username: String = "" - + @Field.Stored("id") var id: String = "" - + @Field.Stored("appleTVID") var appleTVID: String = "" - + @Field.Relationship("server") var server: StoredServer? - + @Field.Relationship("accessToken", inverse: \StoredAccessToken.$user) var accessToken: StoredAccessToken? - + var state: State.User { guard let server = server else { fatalError("No server associated with user") } guard let accessToken = accessToken else { fatalError("No access token associated with user") } @@ -133,23 +133,23 @@ enum SwiftfinStore { accessToken: accessToken.value) } } - + final class StoredAccessToken: CoreStoreObject { - + @Field.Stored("value") var value: String = "" - + @Field.Relationship("user") var user: StoredUser? } } - + // MARK: Errors enum Errors { case existingServer(State.Server) case existingUser(State.User) } - + // MARK: dataStack static let dataStack: DataStack = { let schema = CoreStoreSchema(modelVersion: "V1", @@ -163,7 +163,7 @@ enum SwiftfinStore { "Server": [0x936b46acd8e8f0e3, 0x59890d4d9f3f885f, 0x819cf7a4abf98b22, 0xe16125c5af885a06], "User": [0x845de08a74bc53ed, 0xe95a406a29f3a5d0, 0x9eda732821a15ea9, 0xb5afa531e41ce8a] ]) - + let _dataStack = DataStack(schema) try! _dataStack.addStorageAndWait( SQLiteStore( @@ -177,7 +177,7 @@ enum SwiftfinStore { // MARK: LocalizedError extension SwiftfinStore.Errors: LocalizedError { - + var title: String { switch self { case .existingServer(_): @@ -186,7 +186,7 @@ extension SwiftfinStore.Errors: LocalizedError { return "Existing User" } } - + var errorDescription: String? { switch self { case .existingServer(let server): diff --git a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift index ab6a28d0..22146519 100644 --- a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift +++ b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift @@ -11,9 +11,9 @@ import Defaults import Foundation extension SwiftfinStore { - + enum Defaults { - + static let suite: UserDefaults = { return UserDefaults(suiteName: "swiftfinstore-defaults")! }() @@ -22,7 +22,7 @@ extension SwiftfinStore { extension Defaults.Keys { static let lastServerUserID = Defaults.Key("lastServerUserID", suite: SwiftfinStore.Defaults.suite) - + static let defaultHTTPScheme = Key("defaultHTTPScheme", default: .http, suite: SwiftfinStore.Defaults.suite) static let inNetworkBandwidth = Key("InNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.suite) static let outOfNetworkBandwidth = Key("OutOfNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.suite) diff --git a/Shared/ViewModels/BasicAppSettingsViewModel.swift b/Shared/ViewModels/BasicAppSettingsViewModel.swift index ca3d477e..9cc1fa43 100644 --- a/Shared/ViewModels/BasicAppSettingsViewModel.swift +++ b/Shared/ViewModels/BasicAppSettingsViewModel.swift @@ -10,9 +10,9 @@ import SwiftUI final class BasicAppSettingsViewModel: ViewModel { - + let appearances = AppAppearance.allCases - + func reset() { SessionManager.main.purge() } diff --git a/Shared/ViewModels/ConnectToServerViewModel.swift b/Shared/ViewModels/ConnectToServerViewModel.swift index fca80514..dcfa2c6f 100644 --- a/Shared/ViewModels/ConnectToServerViewModel.swift +++ b/Shared/ViewModels/ConnectToServerViewModel.swift @@ -13,25 +13,25 @@ import JellyfinAPI import Stinsen struct AddServerURIPayload: Identifiable { - + let server: SwiftfinStore.State.Server let uri: String - + var id: String { return server.id.appending(uri) } } final class ConnectToServerViewModel: ViewModel { - + @RouterObject var router: ConnectToServerCoodinator.Router? @Published var discoveredServers: Set = [] @Published var searching = false @Published var addServerURIPayload: AddServerURIPayload? var backAddServerURIPayload: AddServerURIPayload? - + private let discovery = ServerDiscovery() - + var alertTitle: String { var message: String = "" if errorMessage?.code != ErrorMessage.noShowErrorCode { @@ -95,7 +95,7 @@ final class ConnectToServerViewModel: ViewModel { } } } - + func addURIToServer(addServerURIPayload: AddServerURIPayload) { SessionManager.main.addURIToServer(server: addServerURIPayload.server, uri: addServerURIPayload.uri) .sink { completion in @@ -113,12 +113,12 @@ final class ConnectToServerViewModel: ViewModel { } .store(in: &cancellables) } - + func cancelConnection() { for cancellable in cancellables { cancellable.cancel() } - + self.isLoading = false } } diff --git a/Shared/ViewModels/HomeViewModel.swift b/Shared/ViewModels/HomeViewModel.swift index 5b76dbb9..1101ba8f 100644 --- a/Shared/ViewModels/HomeViewModel.swift +++ b/Shared/ViewModels/HomeViewModel.swift @@ -67,9 +67,9 @@ final class HomeViewModel: ViewModel { self.handleAPIRequestError(completion: completion) } }, receiveValue: { response in - + var newLibraries: [BaseItemDto] = [] - + response.items!.forEach { item in LogManager.shared.log.debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")") if item.collectionType == "movies" || item.collectionType == "tvshows" { @@ -88,13 +88,13 @@ final class HomeViewModel: ViewModel { } }, receiveValue: { response in let excludeIDs = response.configuration?.latestItemsExcludes != nil ? response.configuration!.latestItemsExcludes! : [] - + for excludeID in excludeIDs { newLibraries.removeAll { library in return library.id == excludeID } } - + self.libraries = newLibraries }) .store(in: &self.cancellables) @@ -116,7 +116,7 @@ final class HomeViewModel: ViewModel { } }, receiveValue: { response in LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) resume items") - + self.resumeItems = response.items ?? [] }) .store(in: &cancellables) @@ -133,7 +133,7 @@ final class HomeViewModel: ViewModel { } }, receiveValue: { response in LogManager.shared.log.debug("Retrieved \(String(response.items!.count)) nextup items") - + self.nextUpItems = response.items ?? [] }) .store(in: &cancellables) diff --git a/Shared/ViewModels/ItemViewModel.swift b/Shared/ViewModels/ItemViewModel.swift index 7b2dc5c3..adb14d20 100644 --- a/Shared/ViewModels/ItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel.swift @@ -11,7 +11,7 @@ import Foundation import JellyfinAPI class ItemViewModel: ViewModel { - + @Published var item: BaseItemDto @Published var playButtonItem: BaseItemDto? @Published var similarItems: [BaseItemDto] = [] @@ -20,32 +20,32 @@ class ItemViewModel: ViewModel { init(item: BaseItemDto) { self.item = item - + switch item.itemType { case .episode, .movie: self.playButtonItem = item default: () } - + isFavorited = item.userData?.isFavorite ?? false isWatched = item.userData?.played ?? false super.init() getSimilarItems() } - + func playButtonText() -> String { - return item.getItemProgressString() == "" ? "Play" : item.getItemProgressString() + return item.getItemProgressString() == "" ? L10n.play : item.getItemProgressString() } - + func getItemDisplayName() -> String { return item.name ?? "" } - + func shouldDisplayRuntime() -> Bool { return true } - + func getSimilarItems() { LibraryAPI.getSimilarItems(itemId: item.id!, userId: SessionManager.main.currentLogin.user.id, limit: 20, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) .trackActivity(loading) diff --git a/Shared/ViewModels/LibraryViewModel.swift b/Shared/ViewModels/LibraryViewModel.swift index f0839708..ae134564 100644 --- a/Shared/ViewModels/LibraryViewModel.swift +++ b/Shared/ViewModels/LibraryViewModel.swift @@ -36,7 +36,7 @@ final class LibraryViewModel: ViewModel { // temp @Published var filters: LibraryFilters - + private let columns: Int private var libraries = [BaseItemDto]() @@ -64,11 +64,10 @@ final class LibraryViewModel: ViewModel { self.columns = columns super.init() - $filters .sink(receiveValue: requestItems(with:)) .store(in: &cancellables) - + } func requestItems(with filters: LibraryFilters) { @@ -147,7 +146,7 @@ final class LibraryViewModel: ViewModel { currentPage -= 1 requestItems(with: filters) } - + private func calculateRows(for itemList: [BaseItemDto]) -> [LibraryRow] { guard itemList.count > 0 else { return [] } let rowCount = itemList.count / columns diff --git a/Shared/ViewModels/MovieLibrariesViewModel.swift b/Shared/ViewModels/MovieLibrariesViewModel.swift index 17bd4567..80bb2838 100644 --- a/Shared/ViewModels/MovieLibrariesViewModel.swift +++ b/Shared/ViewModels/MovieLibrariesViewModel.swift @@ -14,19 +14,19 @@ import Stinsen import SwiftUICollection final class MovieLibrariesViewModel: ViewModel { - + @Published var rows = [LibraryRow]() @Published var totalPages = 0 @Published var currentPage = 0 @Published var hasNextPage = false @Published var hasPreviousPage = false - + private var libraries = [BaseItemDto]() private let columns: Int - + @RouterObject var router: MovieLibrariesCoordinator.Router? - + init( columns: Int = 7 ) { @@ -35,9 +35,9 @@ final class MovieLibrariesViewModel: ViewModel { requestLibraries() } - + func requestLibraries() { - + UserViewsAPI.getUserViews( userId: SessionManager.main.currentLogin.user.id) .trackActivity(loading) @@ -60,7 +60,7 @@ final class MovieLibrariesViewModel: ViewModel { }) .store(in: &cancellables) } - + private func calculateRows() -> [LibraryRow] { guard libraries.count > 0 else { return [] } let rowCount = libraries.count / columns diff --git a/Shared/ViewModels/SeasonItemViewModel.swift b/Shared/ViewModels/SeasonItemViewModel.swift index c3d1a739..c5a00a40 100644 --- a/Shared/ViewModels/SeasonItemViewModel.swift +++ b/Shared/ViewModels/SeasonItemViewModel.swift @@ -24,8 +24,8 @@ final class SeasonItemViewModel: ItemViewModel { } override func playButtonText() -> String { - guard let playButtonItem = playButtonItem else { return "Play" } - guard let episodeLocator = playButtonItem.getEpisodeLocator() else { return "Play" } + guard let playButtonItem = playButtonItem else { return L10n.play } + guard let episodeLocator = playButtonItem.getEpisodeLocator() else { return L10n.play } return episodeLocator } @@ -70,7 +70,7 @@ final class SeasonItemViewModel: ItemViewModel { playButtonItem = firstEpisode } } - + func routeToSeriesItem() { guard let id = item.seriesId else { return } UserLibraryAPI.getItem(userId: SessionManager.main.currentLogin.user.id, itemId: id) diff --git a/Shared/ViewModels/SeriesItemViewModel.swift b/Shared/ViewModels/SeriesItemViewModel.swift index b204c87b..94f3dc6c 100644 --- a/Shared/ViewModels/SeriesItemViewModel.swift +++ b/Shared/ViewModels/SeriesItemViewModel.swift @@ -21,19 +21,19 @@ final class SeriesItemViewModel: ItemViewModel { requestSeasons() getNextUp() } - + override func playButtonText() -> String { - guard let playButtonItem = playButtonItem else { return "Play" } - guard let episodeLocator = playButtonItem.getEpisodeLocator() else { return "Play" } + guard let playButtonItem = playButtonItem else { return L10n.play } + guard let episodeLocator = playButtonItem.getEpisodeLocator() else { return L10n.play } return episodeLocator } - + override func shouldDisplayRuntime() -> Bool { return false } - private func getNextUp() { - + private func getNextUp() { + LogManager.shared.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))") TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], seriesId: self.item.id!, enableUserData: true) .trackActivity(loading) diff --git a/Shared/ViewModels/ServerListViewModel.swift b/Shared/ViewModels/ServerListViewModel.swift index e0f6402e..638e9555 100644 --- a/Shared/ViewModels/ServerListViewModel.swift +++ b/Shared/ViewModels/ServerListViewModel.swift @@ -11,11 +11,11 @@ import Foundation import SwiftUI class ServerListViewModel: ObservableObject { - + @Published var servers: [SwiftfinStore.State.Server] = [] - + init() { - + // Oct. 15, 2021 // This is a workaround since Stinsen doesn't have the ability to rebuild a root at the time of writing. // Feature request issue: https://github.com/rundfunk47/stinsen/issues/33 @@ -23,11 +23,11 @@ class ServerListViewModel: ObservableObject { let nc = SwiftfinNotificationCenter.main nc.addObserver(self, selector: #selector(didPurge), name: SwiftfinNotificationCenter.Keys.didPurge, object: nil) } - + func fetchServers() { self.servers = SessionManager.main.fetchServers() } - + func userTextFor(server: SwiftfinStore.State.Server) -> String { if server.userIDs.count == 1 { return "1 user" @@ -35,12 +35,12 @@ class ServerListViewModel: ObservableObject { return "\(server.userIDs.count) users" } } - + func remove(server: SwiftfinStore.State.Server) { SessionManager.main.delete(server: server) fetchServers() } - + @objc private func didPurge() { fetchServers() } diff --git a/Shared/ViewModels/TVLibrariesViewModel.swift b/Shared/ViewModels/TVLibrariesViewModel.swift index 6d5a5ef4..1d5abe81 100644 --- a/Shared/ViewModels/TVLibrariesViewModel.swift +++ b/Shared/ViewModels/TVLibrariesViewModel.swift @@ -14,19 +14,19 @@ import Stinsen import SwiftUICollection final class TVLibrariesViewModel: ViewModel { - + @Published var rows = [LibraryRow]() @Published var totalPages = 0 @Published var currentPage = 0 @Published var hasNextPage = false @Published var hasPreviousPage = false - + private var libraries = [BaseItemDto]() private let columns: Int - + @RouterObject var router: TVLibrariesCoordinator.Router? - + init( columns: Int = 7 ) { @@ -35,9 +35,9 @@ final class TVLibrariesViewModel: ViewModel { requestLibraries() } - + func requestLibraries() { - + UserViewsAPI.getUserViews( userId: SessionManager.main.currentLogin.user.id) .trackActivity(loading) @@ -60,7 +60,7 @@ final class TVLibrariesViewModel: ViewModel { }) .store(in: &cancellables) } - + private func calculateRows() -> [LibraryRow] { guard libraries.count > 0 else { return [] } let rowCount = libraries.count / columns diff --git a/Shared/ViewModels/UserListViewModel.swift b/Shared/ViewModels/UserListViewModel.swift index 4d3433e8..68f4dff2 100644 --- a/Shared/ViewModels/UserListViewModel.swift +++ b/Shared/ViewModels/UserListViewModel.swift @@ -1,5 +1,5 @@ // - /* + /* * SwiftFin is subject to the terms of the Mozilla Public * License, v2.0. If a copy of the MPL was not distributed with this * file, you can obtain one at https://mozilla.org/MPL/2.0/. @@ -11,34 +11,34 @@ import Foundation import SwiftUI class UserListViewModel: ViewModel { - + @Published var users: [SwiftfinStore.State.User] = [] - + var server: SwiftfinStore.State.Server - + init(server: SwiftfinStore.State.Server) { self.server = server - + super.init() - + let nc = SwiftfinNotificationCenter.main nc.addObserver(self, selector: #selector(didChangeCurrentLoginURI), name: SwiftfinNotificationCenter.Keys.didChangeServerCurrentURI, object: nil) } - + @objc func didChangeCurrentLoginURI(_ notification: Notification) { guard let newServerState = notification.object as? SwiftfinStore.State.Server else { fatalError("Need to have new state server") } self.server = newServerState } - + func fetchUsers() { self.users = SessionManager.main.fetchUsers(for: server) } - + func login(user: SwiftfinStore.State.User) { self.isLoading = true SessionManager.main.loginUser(server: server, user: user) } - + func remove(user: SwiftfinStore.State.User) { SessionManager.main.delete(user: user) fetchUsers() diff --git a/Shared/ViewModels/UserSignInViewModel.swift b/Shared/ViewModels/UserSignInViewModel.swift index e58e122d..2884dbac 100644 --- a/Shared/ViewModels/UserSignInViewModel.swift +++ b/Shared/ViewModels/UserSignInViewModel.swift @@ -12,14 +12,14 @@ import Foundation import Stinsen final class UserSignInViewModel: ViewModel { - + @RouterObject var router: UserSignInCoordinator.Router? let server: SwiftfinStore.State.Server - + init(server: SwiftfinStore.State.Server) { self.server = server } - + var alertTitle: String { var message: String = "" if errorMessage?.code != ErrorMessage.noShowErrorCode { @@ -28,27 +28,27 @@ final class UserSignInViewModel: ViewModel { message.append(contentsOf: "\(errorMessage?.title ?? "Unkown Error")") return message } - + func login(username: String, password: String) { LogManager.shared.log.debug("Attempting to login to server at \"\(server.currentURI)\"", tag: "login") LogManager.shared.log.debug("username: \(username), password: \(password)", tag: "login") - + SessionManager.main.loginUser(server: server, username: username, password: password) .trackActivity(loading) .sink { completion in self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "login", completion: completion) } receiveValue: { _ in - + } .store(in: &cancellables) } - + func cancelSignIn() { for cancellable in cancellables { cancellable.cancel() } - + self.isLoading = false } } diff --git a/Shared/ViewModels/ViewModel.swift b/Shared/ViewModels/ViewModel.swift index 75aefcf7..18f023cc 100644 --- a/Shared/ViewModels/ViewModel.swift +++ b/Shared/ViewModels/ViewModel.swift @@ -30,7 +30,7 @@ class ViewModel: ObservableObject { break case .failure(let error): let logConstructor = LogConstructor(message: "__NOTHING__", tag: tag, level: logLevel, function: function, file: file, line: line) - + switch error { case is ErrorResponse: let networkError: NetworkError @@ -52,7 +52,7 @@ class ViewModel: ObservableObject { self.errorMessage = networkError.errorMessage networkError.logMessage() - + case is SwiftfinStore.Errors: let swiftfinError = error as! SwiftfinStore.Errors let errorMessage = ErrorMessage(code: ErrorMessage.noShowErrorCode, @@ -61,7 +61,7 @@ class ViewModel: ObservableObject { logConstructor: logConstructor) self.errorMessage = errorMessage LogManager.shared.log.error("Request failed: \(swiftfinError.errorDescription ?? "")") - + default: let genericErrorMessage = ErrorMessage(code: ErrorMessage.noShowErrorCode, title: "Generic Error", diff --git a/Shared/Views/ImageView.swift b/Shared/Views/ImageView.swift index 10044b21..8176fd13 100644 --- a/Shared/Views/ImageView.swift +++ b/Shared/Views/ImageView.swift @@ -19,13 +19,13 @@ struct ImageView: View { self.blurhash = bh self.failureInitials = failureInitials } - + @ViewBuilder private var placeholderImage: some View { Image(uiImage: UIImage(blurHash: blurhash, size: CGSize(width: 8, height: 8)) ?? UIImage(blurHash: "001fC^", size: CGSize(width: 8, height: 8))!) .resizable() } - + @ViewBuilder private var failureImage: some View { ZStack { diff --git a/Shared/Views/SearchBarView.swift b/Shared/Views/SearchBarView.swift index ac4b8f32..ce6db7f9 100644 --- a/Shared/Views/SearchBarView.swift +++ b/Shared/Views/SearchBarView.swift @@ -16,7 +16,7 @@ struct SearchBar: View { var body: some View { HStack(spacing: 8) { - TextField(NSLocalizedString("Search...", comment: ""), text: $text) + TextField(L10n.search, text: $text) .padding(8) .padding(.horizontal, 16) #if os(iOS) diff --git a/Translations/de.lproj/Localizable.strings b/Translations/de.lproj/Localizable.strings index 5eb5a3a0..f9e5a71a 100644 Binary files a/Translations/de.lproj/Localizable.strings and b/Translations/de.lproj/Localizable.strings differ diff --git a/Translations/el.lproj/Localizable.strings b/Translations/el.lproj/Localizable.strings index b76ab027..0fa73620 100644 Binary files a/Translations/el.lproj/Localizable.strings and b/Translations/el.lproj/Localizable.strings differ diff --git a/Translations/en.lproj/Localizable.strings b/Translations/en.lproj/Localizable.strings index 5ce5535c..727666f6 100644 Binary files a/Translations/en.lproj/Localizable.strings and b/Translations/en.lproj/Localizable.strings differ diff --git a/Translations/es.lproj/Localizable.strings b/Translations/es.lproj/Localizable.strings index 6347d91c..57f8b377 100644 Binary files a/Translations/es.lproj/Localizable.strings and b/Translations/es.lproj/Localizable.strings differ diff --git a/Translations/fr.lproj/Localizable.strings b/Translations/fr.lproj/Localizable.strings index 5f7a0391..d705b16e 100644 Binary files a/Translations/fr.lproj/Localizable.strings and b/Translations/fr.lproj/Localizable.strings differ diff --git a/Translations/he.lproj/Localizable.strings b/Translations/he.lproj/Localizable.strings index ad384ebe..88d320ef 100644 Binary files a/Translations/he.lproj/Localizable.strings and b/Translations/he.lproj/Localizable.strings differ diff --git a/Translations/it.lproj/Localizable.strings b/Translations/it.lproj/Localizable.strings index 89444a6b..1145e9d8 100644 Binary files a/Translations/it.lproj/Localizable.strings and b/Translations/it.lproj/Localizable.strings differ diff --git a/Translations/kk.lproj/Localizable.strings b/Translations/kk.lproj/Localizable.strings index 42eb2f9f..7b30edbf 100644 Binary files a/Translations/kk.lproj/Localizable.strings and b/Translations/kk.lproj/Localizable.strings differ diff --git a/Translations/ko.lproj/Localizable.strings b/Translations/ko.lproj/Localizable.strings index ddc4290d..ea8b838a 100644 Binary files a/Translations/ko.lproj/Localizable.strings and b/Translations/ko.lproj/Localizable.strings differ diff --git a/Translations/ru.lproj/Localizable.strings b/Translations/ru.lproj/Localizable.strings index d73aac30..78c61d61 100644 Binary files a/Translations/ru.lproj/Localizable.strings and b/Translations/ru.lproj/Localizable.strings differ diff --git a/Translations/sk.lproj/Localizable.strings b/Translations/sk.lproj/Localizable.strings index 019d0bcb..ada02282 100644 Binary files a/Translations/sk.lproj/Localizable.strings and b/Translations/sk.lproj/Localizable.strings differ diff --git a/Translations/sl.lproj/Localizable.strings b/Translations/sl.lproj/Localizable.strings index 372ef123..e65b482e 100644 Binary files a/Translations/sl.lproj/Localizable.strings and b/Translations/sl.lproj/Localizable.strings differ diff --git a/Translations/sv.lproj/Localizable.strings b/Translations/sv.lproj/Localizable.strings index 17ef9415..de7cb803 100644 Binary files a/Translations/sv.lproj/Localizable.strings and b/Translations/sv.lproj/Localizable.strings differ diff --git a/Translations/ta.lproj/Localizable.strings b/Translations/ta.lproj/Localizable.strings index 15dcbfcb..e69de29b 100644 Binary files a/Translations/ta.lproj/Localizable.strings and b/Translations/ta.lproj/Localizable.strings differ diff --git a/Translations/vi.lproj/Localizable.strings b/Translations/vi.lproj/Localizable.strings index 43a6af61..997256f7 100644 Binary files a/Translations/vi.lproj/Localizable.strings and b/Translations/vi.lproj/Localizable.strings differ diff --git a/Translations/zh-Hans.lproj/Localizable.strings b/Translations/zh-Hans.lproj/Localizable.strings index 6caed672..dea9c8c8 100644 Binary files a/Translations/zh-Hans.lproj/Localizable.strings and b/Translations/zh-Hans.lproj/Localizable.strings differ diff --git a/WidgetExtension/NextUpWidget.swift b/WidgetExtension/NextUpWidget.swift index 7fe9c78c..93e0a88e 100644 --- a/WidgetExtension/NextUpWidget.swift +++ b/WidgetExtension/NextUpWidget.swift @@ -24,9 +24,8 @@ struct NextUpWidgetProvider: TimelineProvider { } func getSnapshot(in context: Context, completion: @escaping (NextUpEntry) -> Void) { - guard let currentLogin = SessionManager.main.currentLogin else { return } - + let currentDate = Date() let server = currentLogin.server let savedUser = currentLogin.user @@ -68,9 +67,8 @@ struct NextUpWidgetProvider: TimelineProvider { } func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { - guard let currentLogin = SessionManager.main.currentLogin else { return } - + let currentDate = Date() let entryDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)! let server = currentLogin.server @@ -137,7 +135,7 @@ struct NextUpEntryView: View { } .background(Color.blue) } else if entry.items.isEmpty { - Text("Empty Next Up") + L10n.emptyNextUp.text .font(.body) .bold() .foregroundColor(.primary) @@ -216,7 +214,7 @@ extension NextUpEntryView { .fontWeight(.semibold) .foregroundColor(.primary) .lineLimit(1) - Text("\(item.0.name ?? "") · S\(item.0.parentIndexNumber ?? 0):E\(item.0.indexNumber ?? 0)") + Text("\(item.0.name ?? "") · \(L10n.seasonAndEpisode(String(item.0.parentIndexNumber ?? 0), String(item.0.indexNumber ?? 0)))") .font(.caption) .fontWeight(.semibold) .foregroundColor(.secondary) @@ -243,7 +241,8 @@ extension NextUpEntryView { .fontWeight(.semibold) .foregroundColor(.primary) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) - Text("\(item.0.name ?? "") · S\(item.0.parentIndexNumber ?? 0):E\(item.0.indexNumber ?? 0)") + + Text("\(item.0.name ?? "") · \(L10n.seasonAndEpisode(String(item.0.parentIndexNumber ?? 0), String(item.0.indexNumber ?? 0)))") .font(.caption) .fontWeight(.semibold) .foregroundColor(.secondary) @@ -305,7 +304,7 @@ extension NextUpEntryView { .fontWeight(.semibold) .foregroundColor(.white) .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) - Text("\(firstItem.0.name ?? "") · S\(firstItem.0.parentIndexNumber ?? 0):E\(firstItem.0.indexNumber ?? 0)") + Text("\(firstItem.0.name ?? "") · \(L10n.seasonAndEpisode(String(firstItem.0.parentIndexNumber ?? 0), String(firstItem.0.indexNumber ?? 0)))") .font(.caption) .fontWeight(.semibold) .foregroundColor(.gray) @@ -347,7 +346,7 @@ struct NextUpWidget: Widget { provider: NextUpWidgetProvider()) { entry in NextUpEntryView(entry: entry) } - .configurationDisplayName("Next Up") + .configurationDisplayName(L10n.nextUp) .description("Keep watching where you left off or see what's up next.") .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) } @@ -366,7 +365,7 @@ struct NextUpWidget_Previews: PreviewProvider { (.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"), UIImage(named: "WidgetHeaderSymbol")), (.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemMedium)) @@ -377,7 +376,7 @@ struct NextUpWidget_Previews: PreviewProvider { (.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"), UIImage(named: "WidgetHeaderSymbol")), (.init(name: "Name2", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series2"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemLarge)) @@ -392,7 +391,7 @@ struct NextUpWidget_Previews: PreviewProvider { (.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"), UIImage(named: "WidgetHeaderSymbol")), (.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemMedium)) @@ -404,7 +403,7 @@ struct NextUpWidget_Previews: PreviewProvider { (.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"), UIImage(named: "WidgetHeaderSymbol")), (.init(name: "Name2", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series2"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemLarge)) @@ -417,7 +416,7 @@ struct NextUpWidget_Previews: PreviewProvider { NextUpEntryView(entry: .init(date: Date(), items: [ (.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemMedium)) @@ -427,7 +426,7 @@ struct NextUpWidget_Previews: PreviewProvider { (.init(name: "Name0", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series0"), UIImage(named: "WidgetHeaderSymbol")), (.init(name: "Name1", indexNumber: 10, parentIndexNumber: 0, seriesName: "Series1"), - UIImage(named: "WidgetHeaderSymbol")), + UIImage(named: "WidgetHeaderSymbol")) ], error: nil)) .previewContext(WidgetPreviewContext(family: .systemLarge)) diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Info.plist b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Info.plist new file mode 100644 index 00000000..01d943f2 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Info.plist @@ -0,0 +1,42 @@ + + + + + BuildMachineOSBuild + + CFBundleDevelopmentRegion + en + CFBundleExecutable + SwiftGen_SwiftGenCLI + CFBundleIdentifier + SwiftGen.SwiftGenCLI.resources + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + SwiftGen_SwiftGenCLI + CFBundlePackageType + BNDL + CFBundleSupportedPlatforms + + MacOSX + + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 13A233 + DTPlatformName + macosx + DTPlatformVersion + 11.3 + DTSDKBuild + 20E214 + DTSDKName + macosx11.3 + DTXcode + 1300 + DTXcodeBuild + 13A233 + LSMinimumSystemVersion + 10.11 + + diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift4.stencil new file mode 100644 index 00000000..af604776 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift4.stencil @@ -0,0 +1,43 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if palettes %} +{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit + {% if enumName != 'NSColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %} +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit + {% if enumName != 'UIColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %} +#endif + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Colors + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} extension {{enumName}} { +{% macro h2f hex %}{{hex|hexToInt|int255toFloat}}{% endmacro %} +{% macro enumBlock colors accessPrefix %} + {% for color in colors %} + /// 0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}} (r: {{color.red|hexToInt}}, g: {{color.green|hexToInt}}, b: {{color.blue|hexToInt}}, a: {{color.alpha|hexToInt}}) + {{accessPrefix}}static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = #colorLiteral(red: {% call h2f color.red %}, green: {% call h2f color.green %}, blue: {% call h2f color.blue %}, alpha: {% call h2f color.alpha %}) + {% endfor %} +{% endmacro %} + {% if palettes.count > 1 or param.forceFileNameEnum %} + {% set accessPrefix %}{{accessModifier}} {% endset %} + {% for palette in palettes %} + enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock palette.colors accessPrefix %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock palettes.first.colors "" %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length +{% else %} +// No color found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift5.stencil new file mode 100644 index 00000000..af604776 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/literals-swift5.stencil @@ -0,0 +1,43 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if palettes %} +{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit + {% if enumName != 'NSColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %} +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit + {% if enumName != 'UIColor' %}{{accessModifier}} enum {{enumName}} { }{% endif %} +#endif + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Colors + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} extension {{enumName}} { +{% macro h2f hex %}{{hex|hexToInt|int255toFloat}}{% endmacro %} +{% macro enumBlock colors accessPrefix %} + {% for color in colors %} + /// 0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}} (r: {{color.red|hexToInt}}, g: {{color.green|hexToInt}}, b: {{color.blue|hexToInt}}, a: {{color.alpha|hexToInt}}) + {{accessPrefix}}static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = #colorLiteral(red: {% call h2f color.red %}, green: {% call h2f color.green %}, blue: {% call h2f color.blue %}, alpha: {% call h2f color.alpha %}) + {% endfor %} +{% endmacro %} + {% if palettes.count > 1 or param.forceFileNameEnum %} + {% set accessPrefix %}{{accessModifier}} {% endset %} + {% for palette in palettes %} + enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock palette.colors accessPrefix %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock palettes.first.colors "" %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length +{% else %} +// No color found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift4.stencil new file mode 100644 index 00000000..57c2d797 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift4.stencil @@ -0,0 +1,84 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if palettes %} +{% set colorAlias %}{{param.colorAliasName|default:"Color"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit.NSColor + {{accessModifier}} typealias {{colorAlias}} = NSColor +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit.UIColor + {{accessModifier}} typealias {{colorAlias}} = UIColor +#endif + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Colors + +// swiftlint:disable identifier_name line_length type_body_length +{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %} +{{accessModifier}} struct {{enumName}} { + {{accessModifier}} let rgbaValue: UInt32 + {{accessModifier}} var color: {{colorAlias}} { return {{colorAlias}}(named: self) } + +{% macro rgbaValue color %}0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}{% endmacro %} +{% macro enumBlock colors %} + {% for color in colors %} + /// + /// Alpha: {{color.alpha|hexToInt|int255toFloat|percent}}
(0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}) + {{accessModifier}} static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}(rgbaValue: {% call rgbaValue color %}) + {% endfor %} +{% endmacro %} + {% if palettes.count > 1 or param.forceFileNameEnum %} + {% for palette in palettes %} + {{accessModifier}} enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock palette.colors %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock palettes.first.colors %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +internal extension {{colorAlias}} { + convenience init(rgbaValue: UInt32) { + let components = RGBAComponents(rgbaValue: rgbaValue).normalized + self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3]) + } +} + +private struct RGBAComponents { + let rgbaValue: UInt32 + + private var shifts: [UInt32] { + [ + rgbaValue >> 24, // red + rgbaValue >> 16, // green + rgbaValue >> 8, // blue + rgbaValue // alpha + ] + } + + private var components: [CGFloat] { + shifts.map { + CGFloat($0 & 0xff) + } + } + + var normalized: [CGFloat] { + components.map { $0 / 255.0 } + } +} + +{{accessModifier}} extension {{colorAlias}} { + convenience init(named color: {{enumName}}) { + self.init(rgbaValue: color.rgbaValue) + } +} +{% else %} +// No color found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift5.stencil new file mode 100644 index 00000000..57c2d797 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/colors/swift5.stencil @@ -0,0 +1,84 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if palettes %} +{% set colorAlias %}{{param.colorAliasName|default:"Color"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit.NSColor + {{accessModifier}} typealias {{colorAlias}} = NSColor +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit.UIColor + {{accessModifier}} typealias {{colorAlias}} = UIColor +#endif + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Colors + +// swiftlint:disable identifier_name line_length type_body_length +{% set enumName %}{{param.enumName|default:"ColorName"}}{% endset %} +{{accessModifier}} struct {{enumName}} { + {{accessModifier}} let rgbaValue: UInt32 + {{accessModifier}} var color: {{colorAlias}} { return {{colorAlias}}(named: self) } + +{% macro rgbaValue color %}0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}{% endmacro %} +{% macro enumBlock colors %} + {% for color in colors %} + /// + /// Alpha: {{color.alpha|hexToInt|int255toFloat|percent}}
(0x{{color.red}}{{color.green}}{{color.blue}}{{color.alpha}}) + {{accessModifier}} static let {{color.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}(rgbaValue: {% call rgbaValue color %}) + {% endfor %} +{% endmacro %} + {% if palettes.count > 1 or param.forceFileNameEnum %} + {% for palette in palettes %} + {{accessModifier}} enum {{palette.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock palette.colors %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock palettes.first.colors %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +internal extension {{colorAlias}} { + convenience init(rgbaValue: UInt32) { + let components = RGBAComponents(rgbaValue: rgbaValue).normalized + self.init(red: components[0], green: components[1], blue: components[2], alpha: components[3]) + } +} + +private struct RGBAComponents { + let rgbaValue: UInt32 + + private var shifts: [UInt32] { + [ + rgbaValue >> 24, // red + rgbaValue >> 16, // green + rgbaValue >> 8, // blue + rgbaValue // alpha + ] + } + + private var components: [CGFloat] { + shifts.map { + CGFloat($0 & 0xff) + } + } + + var normalized: [CGFloat] { + components.map { $0 / 255.0 } + } +} + +{{accessModifier}} extension {{colorAlias}} { + convenience init(named color: {{enumName}}) { + self.init(rgbaValue: color.rgbaValue) + } +} +{% else %} +// No color found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift4.stencil new file mode 100644 index 00000000..9832876e --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift4.stencil @@ -0,0 +1,211 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +// swiftlint:disable superfluous_disable_command implicit_return +// swiftlint:disable sorted_imports +import CoreData +import Foundation +{% for import in param.extraImports %} +import {{ import }} +{% empty %} +{# If extraImports is a single String instead of an array, `for` considers it empty but we still have to check if there's a single String value #} +{% if param.extraImports %}import {{ param.extraImports }}{% endif %} +{% endfor %} + +// swiftlint:disable attributes file_length vertical_whitespace_closing_braces +// swiftlint:disable identifier_name line_length type_body_length +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} + +{% for model in models %} +{% for name, entity in model.entities %} +{% set superclass %}{{ model.entities[entity.superEntity].className|default:"NSManagedObject" }}{% endset %} +{% set entityClassName %}{{ entity.className|default:"NSManagedObject" }}{% endset %} +// MARK: - {{ entity.name }} + +{% if not entity.shouldGenerateCode %} +// Note: '{{ entity.name }}' has codegen enabled for Xcode, skipping code generation. + +{% elif entityClassName|contains:"." %} +// Warning: '{{ entityClassName }}' cannot be a valid type name, skipping code generation. + +{% else %} +{% if param.generateObjcName %} +@objc({{ entityClassName }}) +{% endif %} +{{ accessModifier }} class {{ entityClassName }}: {{ superclass }} { + {% set override %}{% if superclass != "NSManagedObject" %}override {% endif %}{% endset %} + {{ override }}{{ accessModifier }} class var entityName: String { + return "{{ entity.name }}" + } + + {{ override }}{{ accessModifier }} class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? { + return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext) + } + + @available(*, deprecated, renamed: "makeFetchRequest", message: "To avoid collisions with the less concrete method in `NSManagedObject`, please use `makeFetchRequest()` instead.") + @nonobjc {{ accessModifier }} class func fetchRequest() -> NSFetchRequest<{{ entityClassName }}> { + return NSFetchRequest<{{ entityClassName }}>(entityName: entityName) + } + + @nonobjc {{ accessModifier }} class func makeFetchRequest() -> NSFetchRequest<{{ entityClassName }}> { + return NSFetchRequest<{{ entityClassName }}>(entityName: entityName) + } + + // swiftlint:disable discouraged_optional_boolean discouraged_optional_collection + {% for attribute in entity.attributes %} + {% if attribute.userInfo.RawType %} + {% set rawType attribute.userInfo.RawType %} + {% set unwrapOptional attribute.userInfo.unwrapOptional %} + {{ accessModifier }} var {{ attribute.name }}: {{ rawType }}{% if not unwrapOptional %}?{% endif %} { + get { + let key = "{{ attribute.name }}" + willAccessValue(forKey: key) + defer { didAccessValue(forKey: key) } + + {% if unwrapOptional %} + guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue, + let result = {{ rawType }}(rawValue: value) else { + fatalError("Could not convert value for key '\(key)' to type '{{ rawType }}'") + } + return result + {% else %} + guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue else { + return nil + } + return {{ rawType }}(rawValue: value) + {% endif %} + } + set { + let key = "{{ attribute.name }}" + willChangeValue(forKey: key) + defer { didChangeValue(forKey: key) } + + setPrimitiveValue(newValue{% if not unwrapOptional %}?{% endif %}.rawValue, forKey: key) + } + } + {% elif attribute.usesScalarValueType and attribute.isOptional %} + {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}? { + get { + let key = "{{ attribute.name }}" + willAccessValue(forKey: key) + defer { didAccessValue(forKey: key) } + + return primitiveValue(forKey: key) as? {{ attribute.typeName }} + } + set { + let key = "{{ attribute.name }}" + willChangeValue(forKey: key) + defer { didChangeValue(forKey: key) } + + setPrimitiveValue(newValue, forKey: key) + } + } + {% else %} + @NSManaged {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}{% if attribute.isOptional %}?{% endif %} + {% endif %} + {% endfor %} + {% for relationship in entity.relationships %} + {% if relationship.isToMany %} + @NSManaged {{ accessModifier }} var {{ relationship.name }}: {% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}>{% endif %}{% if relationship.isOptional %}?{% endif %} + {% else %} + @NSManaged {{ accessModifier }} var {{ relationship.name }}: {{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% if relationship.isOptional %}?{% endif %} + {% endif %} + {% endfor %} + {% for fetchedProperty in entity.fetchedProperties %} + @NSManaged {{ accessModifier }} var {{ fetchedProperty.name }}: [{{ fetchedProperty.fetchRequest.entity }}] + {% endfor %} + // swiftlint:enable discouraged_optional_boolean discouraged_optional_collection +} + +{% for relationship in entity.relationships where relationship.isToMany %} +{% set destinationEntityClassName %}{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% endset %} +{% set collectionClassName %}{% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ destinationEntityClassName }}>{% endif %}{% endset %} +{% set relationshipName %}{{ relationship.name | upperFirstLetter }}{% endset %} +// MARK: Relationship {{ relationshipName }} + +extension {{ entityClassName }} { + {% if relationship.isOrdered %} + @objc(insertObject:in{{ relationshipName }}AtIndex:) + @NSManaged public func insertInto{{ relationshipName }}(_ value: {{ destinationEntityClassName }}, at idx: Int) + + @objc(removeObjectFrom{{ relationshipName }}AtIndex:) + @NSManaged public func removeFrom{{ relationshipName }}(at idx: Int) + + @objc(insert{{ relationshipName }}:atIndexes:) + @NSManaged public func insertInto{{ relationshipName }}(_ values: [{{ destinationEntityClassName }}], at indexes: NSIndexSet) + + @objc(remove{{ relationshipName }}AtIndexes:) + @NSManaged public func removeFrom{{ relationshipName }}(at indexes: NSIndexSet) + + @objc(replaceObjectIn{{ relationshipName }}AtIndex:withObject:) + @NSManaged public func replace{{ relationshipName }}(at idx: Int, with value: {{ destinationEntityClassName }}) + + @objc(replace{{ relationshipName }}AtIndexes:with{{ relationshipName }}:) + @NSManaged public func replace{{ relationshipName }}(at indexes: NSIndexSet, with values: [{{ destinationEntityClassName }}]) + + {% endif %} + @objc(add{{ relationshipName }}Object:) + @NSManaged public func addTo{{ relationshipName }}(_ value: {{ destinationEntityClassName }}) + + @objc(remove{{ relationshipName }}Object:) + @NSManaged public func removeFrom{{ relationshipName }}(_ value: {{ destinationEntityClassName }}) + + @objc(add{{ relationshipName }}:) + @NSManaged public func addTo{{ relationshipName }}(_ values: {{ collectionClassName }}) + + @objc(remove{{ relationshipName }}:) + @NSManaged public func removeFrom{{ relationshipName }}(_ values: {{ collectionClassName }}) +} + +{% endfor %} +{% if model.fetchRequests[entity.name].count > 0 %} +// MARK: Fetch Requests + +extension {{ entityClassName }} { + {% for fetchRequest in model.fetchRequests[entity.name] %} + {% set resultTypeName %}{% filter removeNewlines:"leading" %} + {% if fetchRequest.resultType == "Object" %} + {{ entityClassName }} + {% elif fetchRequest.resultType == "Object ID" %} + NSManagedObjectID + {% elif fetchRequest.resultType == "Dictionary" %} + [String: Any] + {% endif %} + {% endfilter %}{% endset %} + class func fetch{{ fetchRequest.name | upperFirstLetter }}({% filter removeNewlines:"leading" %} + managedObjectContext: NSManagedObjectContext + {% for variableName, variableType in fetchRequest.substitutionVariables %} + , {{ variableName | lowerFirstWord }}: {{ variableType }} + {% endfor %} + {% endfilter %}) throws -> [{{ resultTypeName }}] { + guard let persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator else { + fatalError("Managed object context has no persistent store coordinator for getting fetch request templates") + } + let model = persistentStoreCoordinator.managedObjectModel + let substitutionVariables: [String: Any] = [ + {% for variableName, variableType in fetchRequest.substitutionVariables %} + "{{ variableName }}": {{ variableName | lowerFirstWord }}{{ "," if not forloop.last }} + {% empty %} + : + {% endfor %} + ] + + guard let fetchRequest = model.fetchRequestFromTemplate(withName: "{{ fetchRequest.name }}", substitutionVariables: substitutionVariables) else { + fatalError("No fetch request template named '{{ fetchRequest.name }}' found.") + } + + guard let result = try managedObjectContext.fetch(fetchRequest) as? [{{ resultTypeName }}] else { + fatalError("Unable to cast fetch result to correct result type.") + } + + return result + } + + {% endfor %} +} + +{% endif %} +{% endif %} +{% endfor %} +{% endfor %} +// swiftlint:enable identifier_name line_length type_body_length diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift5.stencil new file mode 100644 index 00000000..9832876e --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/coredata/swift5.stencil @@ -0,0 +1,211 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +// swiftlint:disable superfluous_disable_command implicit_return +// swiftlint:disable sorted_imports +import CoreData +import Foundation +{% for import in param.extraImports %} +import {{ import }} +{% empty %} +{# If extraImports is a single String instead of an array, `for` considers it empty but we still have to check if there's a single String value #} +{% if param.extraImports %}import {{ param.extraImports }}{% endif %} +{% endfor %} + +// swiftlint:disable attributes file_length vertical_whitespace_closing_braces +// swiftlint:disable identifier_name line_length type_body_length +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} + +{% for model in models %} +{% for name, entity in model.entities %} +{% set superclass %}{{ model.entities[entity.superEntity].className|default:"NSManagedObject" }}{% endset %} +{% set entityClassName %}{{ entity.className|default:"NSManagedObject" }}{% endset %} +// MARK: - {{ entity.name }} + +{% if not entity.shouldGenerateCode %} +// Note: '{{ entity.name }}' has codegen enabled for Xcode, skipping code generation. + +{% elif entityClassName|contains:"." %} +// Warning: '{{ entityClassName }}' cannot be a valid type name, skipping code generation. + +{% else %} +{% if param.generateObjcName %} +@objc({{ entityClassName }}) +{% endif %} +{{ accessModifier }} class {{ entityClassName }}: {{ superclass }} { + {% set override %}{% if superclass != "NSManagedObject" %}override {% endif %}{% endset %} + {{ override }}{{ accessModifier }} class var entityName: String { + return "{{ entity.name }}" + } + + {{ override }}{{ accessModifier }} class func entity(in managedObjectContext: NSManagedObjectContext) -> NSEntityDescription? { + return NSEntityDescription.entity(forEntityName: entityName, in: managedObjectContext) + } + + @available(*, deprecated, renamed: "makeFetchRequest", message: "To avoid collisions with the less concrete method in `NSManagedObject`, please use `makeFetchRequest()` instead.") + @nonobjc {{ accessModifier }} class func fetchRequest() -> NSFetchRequest<{{ entityClassName }}> { + return NSFetchRequest<{{ entityClassName }}>(entityName: entityName) + } + + @nonobjc {{ accessModifier }} class func makeFetchRequest() -> NSFetchRequest<{{ entityClassName }}> { + return NSFetchRequest<{{ entityClassName }}>(entityName: entityName) + } + + // swiftlint:disable discouraged_optional_boolean discouraged_optional_collection + {% for attribute in entity.attributes %} + {% if attribute.userInfo.RawType %} + {% set rawType attribute.userInfo.RawType %} + {% set unwrapOptional attribute.userInfo.unwrapOptional %} + {{ accessModifier }} var {{ attribute.name }}: {{ rawType }}{% if not unwrapOptional %}?{% endif %} { + get { + let key = "{{ attribute.name }}" + willAccessValue(forKey: key) + defer { didAccessValue(forKey: key) } + + {% if unwrapOptional %} + guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue, + let result = {{ rawType }}(rawValue: value) else { + fatalError("Could not convert value for key '\(key)' to type '{{ rawType }}'") + } + return result + {% else %} + guard let value = primitiveValue(forKey: key) as? {{ rawType }}.RawValue else { + return nil + } + return {{ rawType }}(rawValue: value) + {% endif %} + } + set { + let key = "{{ attribute.name }}" + willChangeValue(forKey: key) + defer { didChangeValue(forKey: key) } + + setPrimitiveValue(newValue{% if not unwrapOptional %}?{% endif %}.rawValue, forKey: key) + } + } + {% elif attribute.usesScalarValueType and attribute.isOptional %} + {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}? { + get { + let key = "{{ attribute.name }}" + willAccessValue(forKey: key) + defer { didAccessValue(forKey: key) } + + return primitiveValue(forKey: key) as? {{ attribute.typeName }} + } + set { + let key = "{{ attribute.name }}" + willChangeValue(forKey: key) + defer { didChangeValue(forKey: key) } + + setPrimitiveValue(newValue, forKey: key) + } + } + {% else %} + @NSManaged {{ accessModifier }} var {{ attribute.name }}: {{ attribute.typeName }}{% if attribute.isOptional %}?{% endif %} + {% endif %} + {% endfor %} + {% for relationship in entity.relationships %} + {% if relationship.isToMany %} + @NSManaged {{ accessModifier }} var {{ relationship.name }}: {% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}>{% endif %}{% if relationship.isOptional %}?{% endif %} + {% else %} + @NSManaged {{ accessModifier }} var {{ relationship.name }}: {{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% if relationship.isOptional %}?{% endif %} + {% endif %} + {% endfor %} + {% for fetchedProperty in entity.fetchedProperties %} + @NSManaged {{ accessModifier }} var {{ fetchedProperty.name }}: [{{ fetchedProperty.fetchRequest.entity }}] + {% endfor %} + // swiftlint:enable discouraged_optional_boolean discouraged_optional_collection +} + +{% for relationship in entity.relationships where relationship.isToMany %} +{% set destinationEntityClassName %}{{ model.entities[relationship.destinationEntity].className|default:"NSManagedObject" }}{% endset %} +{% set collectionClassName %}{% if relationship.isOrdered %}NSOrderedSet{% else %}Set<{{ destinationEntityClassName }}>{% endif %}{% endset %} +{% set relationshipName %}{{ relationship.name | upperFirstLetter }}{% endset %} +// MARK: Relationship {{ relationshipName }} + +extension {{ entityClassName }} { + {% if relationship.isOrdered %} + @objc(insertObject:in{{ relationshipName }}AtIndex:) + @NSManaged public func insertInto{{ relationshipName }}(_ value: {{ destinationEntityClassName }}, at idx: Int) + + @objc(removeObjectFrom{{ relationshipName }}AtIndex:) + @NSManaged public func removeFrom{{ relationshipName }}(at idx: Int) + + @objc(insert{{ relationshipName }}:atIndexes:) + @NSManaged public func insertInto{{ relationshipName }}(_ values: [{{ destinationEntityClassName }}], at indexes: NSIndexSet) + + @objc(remove{{ relationshipName }}AtIndexes:) + @NSManaged public func removeFrom{{ relationshipName }}(at indexes: NSIndexSet) + + @objc(replaceObjectIn{{ relationshipName }}AtIndex:withObject:) + @NSManaged public func replace{{ relationshipName }}(at idx: Int, with value: {{ destinationEntityClassName }}) + + @objc(replace{{ relationshipName }}AtIndexes:with{{ relationshipName }}:) + @NSManaged public func replace{{ relationshipName }}(at indexes: NSIndexSet, with values: [{{ destinationEntityClassName }}]) + + {% endif %} + @objc(add{{ relationshipName }}Object:) + @NSManaged public func addTo{{ relationshipName }}(_ value: {{ destinationEntityClassName }}) + + @objc(remove{{ relationshipName }}Object:) + @NSManaged public func removeFrom{{ relationshipName }}(_ value: {{ destinationEntityClassName }}) + + @objc(add{{ relationshipName }}:) + @NSManaged public func addTo{{ relationshipName }}(_ values: {{ collectionClassName }}) + + @objc(remove{{ relationshipName }}:) + @NSManaged public func removeFrom{{ relationshipName }}(_ values: {{ collectionClassName }}) +} + +{% endfor %} +{% if model.fetchRequests[entity.name].count > 0 %} +// MARK: Fetch Requests + +extension {{ entityClassName }} { + {% for fetchRequest in model.fetchRequests[entity.name] %} + {% set resultTypeName %}{% filter removeNewlines:"leading" %} + {% if fetchRequest.resultType == "Object" %} + {{ entityClassName }} + {% elif fetchRequest.resultType == "Object ID" %} + NSManagedObjectID + {% elif fetchRequest.resultType == "Dictionary" %} + [String: Any] + {% endif %} + {% endfilter %}{% endset %} + class func fetch{{ fetchRequest.name | upperFirstLetter }}({% filter removeNewlines:"leading" %} + managedObjectContext: NSManagedObjectContext + {% for variableName, variableType in fetchRequest.substitutionVariables %} + , {{ variableName | lowerFirstWord }}: {{ variableType }} + {% endfor %} + {% endfilter %}) throws -> [{{ resultTypeName }}] { + guard let persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator else { + fatalError("Managed object context has no persistent store coordinator for getting fetch request templates") + } + let model = persistentStoreCoordinator.managedObjectModel + let substitutionVariables: [String: Any] = [ + {% for variableName, variableType in fetchRequest.substitutionVariables %} + "{{ variableName }}": {{ variableName | lowerFirstWord }}{{ "," if not forloop.last }} + {% empty %} + : + {% endfor %} + ] + + guard let fetchRequest = model.fetchRequestFromTemplate(withName: "{{ fetchRequest.name }}", substitutionVariables: substitutionVariables) else { + fatalError("No fetch request template named '{{ fetchRequest.name }}' found.") + } + + guard let result = try managedObjectContext.fetch(fetchRequest) as? [{{ resultTypeName }}] else { + fatalError("Unable to cast fetch result to correct result type.") + } + + return result + } + + {% endfor %} +} + +{% endif %} +{% endif %} +{% endfor %} +{% endfor %} +// swiftlint:enable identifier_name line_length type_body_length diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift4.stencil new file mode 100644 index 00000000..09df24de --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift4.stencil @@ -0,0 +1,103 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if groups.count > 0 %} +{% set enumName %}{{param.enumName|default:"Files"}}{% endset %} +{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length line_length implicit_return + +// MARK: - Files + +{% macro groupBlock group %} + {% for file in group.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in group.directories %} + {% call dirBlock dir %} + {% endfor %} +{% endmacro %} +{% macro fileBlock file %} + /// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %} + {% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %} + {{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}") +{% endmacro %} +{% macro dirBlock directory %} + {% for file in directory.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in directory.directories %} + {% call dirBlock dir %} + {% endfor %} +{% endmacro %} +// swiftlint:disable explicit_type_interface identifier_name +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{{accessModifier}} enum {{enumName}} { + {% if groups.count > 1 or param.forceFileNameEnum %} + {% for group in groups %} + {{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call groupBlock group %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call groupBlock groups.first %} + {% endif %} +} +// swiftlint:enable explicit_type_interface identifier_name +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +{{accessModifier}} struct {{resourceType}} { + {{accessModifier}} let name: String + {{accessModifier}} let ext: String? + {{accessModifier}} let relativePath: String + {{accessModifier}} let mimeType: String + + {{accessModifier}} var url: URL { + return url(locale: nil) + } + + {{accessModifier}} func url(locale: Locale?) -> URL { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + let url = bundle.url( + forResource: name, + withExtension: ext, + subdirectory: relativePath, + localization: locale?.identifier + ) + guard let result = url else { + let file = name + (ext.flatMap { ".\($0)" } ?? "") + fatalError("Could not locate file named \(file)") + } + return result + } + + {{accessModifier}} var path: String { + return path(locale: nil) + } + + {{accessModifier}} func path(locale: Locale?) -> String { + return url(locale: locale).path + } +} +{% if not param.bundle %} + +// swiftlint:disable convenience_type explicit_type_interface +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type explicit_type_interface +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift5.stencil new file mode 100644 index 00000000..09df24de --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/flat-swift5.stencil @@ -0,0 +1,103 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if groups.count > 0 %} +{% set enumName %}{{param.enumName|default:"Files"}}{% endset %} +{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length line_length implicit_return + +// MARK: - Files + +{% macro groupBlock group %} + {% for file in group.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in group.directories %} + {% call dirBlock dir %} + {% endfor %} +{% endmacro %} +{% macro fileBlock file %} + /// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %} + {% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %} + {{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}") +{% endmacro %} +{% macro dirBlock directory %} + {% for file in directory.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in directory.directories %} + {% call dirBlock dir %} + {% endfor %} +{% endmacro %} +// swiftlint:disable explicit_type_interface identifier_name +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{{accessModifier}} enum {{enumName}} { + {% if groups.count > 1 or param.forceFileNameEnum %} + {% for group in groups %} + {{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call groupBlock group %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call groupBlock groups.first %} + {% endif %} +} +// swiftlint:enable explicit_type_interface identifier_name +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +{{accessModifier}} struct {{resourceType}} { + {{accessModifier}} let name: String + {{accessModifier}} let ext: String? + {{accessModifier}} let relativePath: String + {{accessModifier}} let mimeType: String + + {{accessModifier}} var url: URL { + return url(locale: nil) + } + + {{accessModifier}} func url(locale: Locale?) -> URL { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + let url = bundle.url( + forResource: name, + withExtension: ext, + subdirectory: relativePath, + localization: locale?.identifier + ) + guard let result = url else { + let file = name + (ext.flatMap { ".\($0)" } ?? "") + fatalError("Could not locate file named \(file)") + } + return result + } + + {{accessModifier}} var path: String { + return path(locale: nil) + } + + {{accessModifier}} func path(locale: Locale?) -> String { + return url(locale: locale).path + } +} +{% if not param.bundle %} + +// swiftlint:disable convenience_type explicit_type_interface +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type explicit_type_interface +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift4.stencil new file mode 100644 index 00000000..6d6db960 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift4.stencil @@ -0,0 +1,107 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if groups.count > 0 %} +{% set enumName %}{{param.enumName|default:"Files"}}{% endset %} +{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length line_length implicit_return + +// MARK: - Files + +{% macro groupBlock group %} + {% for file in group.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in group.directories %} + {% call dirBlock dir "" %} + {% endfor %} +{% endmacro %} +{% macro fileBlock file %} + /// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %} + {% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %} + {{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}") +{% endmacro %} +{% macro dirBlock directory parent %} + {% set fullDir %}{{parent}}{{directory.name}}/{% endset %} + /// {{ fullDir }} + {{accessModifier}} enum {{directory.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% for file in directory.files %} + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + {% endfor %} + {% for dir in directory.directories %} + {% filter indent:2 %}{% call dirBlock dir fullDir %}{% endfilter %} + {% endfor %} + } +{% endmacro %} +// swiftlint:disable explicit_type_interface identifier_name +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{{accessModifier}} enum {{enumName}} { + {% if groups.count > 1 or param.forceFileNameEnum %} + {% for group in groups %} + {{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call groupBlock group %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call groupBlock groups.first %} + {% endif %} +} +// swiftlint:enable explicit_type_interface identifier_name +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +{{accessModifier}} struct {{resourceType}} { + {{accessModifier}} let name: String + {{accessModifier}} let ext: String? + {{accessModifier}} let relativePath: String + {{accessModifier}} let mimeType: String + + {{accessModifier}} var url: URL { + return url(locale: nil) + } + + {{accessModifier}} func url(locale: Locale?) -> URL { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + let url = bundle.url( + forResource: name, + withExtension: ext, + subdirectory: relativePath, + localization: locale?.identifier + ) + guard let result = url else { + let file = name + (ext.flatMap { ".\($0)" } ?? "") + fatalError("Could not locate file named \(file)") + } + return result + } + + {{accessModifier}} var path: String { + return path(locale: nil) + } + + {{accessModifier}} func path(locale: Locale?) -> String { + return url(locale: locale).path + } +} +{% if not param.bundle %} + +// swiftlint:disable convenience_type explicit_type_interface +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type explicit_type_interface +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift5.stencil new file mode 100644 index 00000000..6d6db960 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/files/structured-swift5.stencil @@ -0,0 +1,107 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if groups.count > 0 %} +{% set enumName %}{{param.enumName|default:"Files"}}{% endset %} +{% set useExt %}{% if param.useExtension|default:"true" %}true{% endif %}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set resourceType %}{{param.resourceTypeName|default:"File"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length line_length implicit_return + +// MARK: - Files + +{% macro groupBlock group %} + {% for file in group.files %} + {% call fileBlock file %} + {% endfor %} + {% for dir in group.directories %} + {% call dirBlock dir "" %} + {% endfor %} +{% endmacro %} +{% macro fileBlock file %} + /// {% if file.path and param.preservePath %}{{file.path}}/{% endif %}{{file.name}}{% if file.ext %}.{{file.ext}}{% endif %} + {% set identifier %}{{ file.name }}{% if useExt %}.{{ file.ext }}{% endif %}{% endset %} + {{accessModifier}} static let {{identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{resourceType}}(name: "{{file.name}}", ext: {% if file.ext %}"{{file.ext}}"{% else %}nil{% endif %}, relativePath: "{{file.path if param.preservePath}}", mimeType: "{{file.mimeType}}") +{% endmacro %} +{% macro dirBlock directory parent %} + {% set fullDir %}{{parent}}{{directory.name}}/{% endset %} + /// {{ fullDir }} + {{accessModifier}} enum {{directory.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% for file in directory.files %} + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + {% endfor %} + {% for dir in directory.directories %} + {% filter indent:2 %}{% call dirBlock dir fullDir %}{% endfilter %} + {% endfor %} + } +{% endmacro %} +// swiftlint:disable explicit_type_interface identifier_name +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{{accessModifier}} enum {{enumName}} { + {% if groups.count > 1 or param.forceFileNameEnum %} + {% for group in groups %} + {{accessModifier}} enum {{group.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call groupBlock group %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call groupBlock groups.first %} + {% endif %} +} +// swiftlint:enable explicit_type_interface identifier_name +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +{{accessModifier}} struct {{resourceType}} { + {{accessModifier}} let name: String + {{accessModifier}} let ext: String? + {{accessModifier}} let relativePath: String + {{accessModifier}} let mimeType: String + + {{accessModifier}} var url: URL { + return url(locale: nil) + } + + {{accessModifier}} func url(locale: Locale?) -> URL { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + let url = bundle.url( + forResource: name, + withExtension: ext, + subdirectory: relativePath, + localization: locale?.identifier + ) + guard let result = url else { + let file = name + (ext.flatMap { ".\($0)" } ?? "") + fatalError("Could not locate file named \(file)") + } + return result + } + + {{accessModifier}} var path: String { + return path(locale: nil) + } + + {{accessModifier}} func path(locale: Locale?) -> String { + return url(locale: locale).path + } +} +{% if not param.bundle %} + +// swiftlint:disable convenience_type explicit_type_interface +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type explicit_type_interface +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift4.stencil new file mode 100644 index 00000000..744d6a4c --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift4.stencil @@ -0,0 +1,110 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if families %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set fontType %}{{param.fontTypeName|default:"FontConvertible"}}{% endset %} +#if os(macOS) + import AppKit.NSFont +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit.UIFont +#endif + +// Deprecated typealiases +@available(*, deprecated, renamed: "{{fontType}}.Font", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.fontAliasName|default:"Font"}} = {{fontType}}.Font + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length +// swiftlint:disable implicit_return + +// MARK: - Fonts + +// swiftlint:disable identifier_name line_length type_body_length +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} +{{accessModifier}} enum {{param.enumName|default:"FontFamily"}} { + {% for family in families %} + {{accessModifier}} enum {{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% for font in family.fonts %} + {{accessModifier}} static let {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{fontType}}(name: "{{font.name}}", family: "{{family.name}}", path: "{% call transformPath font.path %}") + {% endfor %} + {{accessModifier}} static let all: [{{fontType}}] = [{% for font in family.fonts %}{{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{{ ", " if not forloop.last }}{% endfor %}] + } + {% endfor %} + {{accessModifier}} static let allCustomFonts: [{{fontType}}] = [{% for family in families %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.all{{ ", " if not forloop.last }}{% endfor %}].flatMap { $0 } + {{accessModifier}} static func registerAllCustomFonts() { + allCustomFonts.forEach { $0.register() } + } +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +{{accessModifier}} struct {{fontType}} { + {{accessModifier}} let name: String + {{accessModifier}} let family: String + {{accessModifier}} let path: String + + #if os(macOS) + {{accessModifier}} typealias Font = NSFont + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Font = UIFont + #endif + + {{accessModifier}} func font(size: CGFloat) -> Font! { + return Font(font: self, size: size) + } + + {{accessModifier}} func register() { + // swiftlint:disable:next conditional_returns_on_newline + guard let url = url else { return } + CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil) + } + + fileprivate var url: URL? { + {% if param.lookupFunction %} + return {{param.lookupFunction}}(name, family, path) + {% else %} + return {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil) + {% endif %} + } +} + +{{accessModifier}} extension {{fontType}}.Font { + convenience init?(font: {{fontType}}, size: CGFloat) { + #if os(iOS) || os(tvOS) || os(watchOS) + if !UIFont.fontNames(forFamilyName: font.family).contains(font.name) { + font.register() + } + #elseif os(macOS) + if let url = font.url, CTFontManagerGetScopeForURL(url as CFURL) == .none { + font.register() + } + #endif + + self.init(name: font.name, size: size) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No fonts found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift5.stencil new file mode 100644 index 00000000..5a268b58 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/fonts/swift5.stencil @@ -0,0 +1,113 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if families %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set fontType %}{{param.fontTypeName|default:"FontConvertible"}}{% endset %} +#if os(macOS) + import AppKit.NSFont +#elseif os(iOS) || os(tvOS) || os(watchOS) + import UIKit.UIFont +#endif + +// Deprecated typealiases +@available(*, deprecated, renamed: "{{fontType}}.Font", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.fontAliasName|default:"Font"}} = {{fontType}}.Font + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Fonts + +// swiftlint:disable identifier_name line_length type_body_length +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} +{{accessModifier}} enum {{param.enumName|default:"FontFamily"}} { + {% for family in families %} + {{accessModifier}} enum {{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% for font in family.fonts %} + {{accessModifier}} static let {{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{fontType}}(name: "{{font.name}}", family: "{{family.name}}", path: "{% call transformPath font.path %}") + {% endfor %} + {{accessModifier}} static let all: [{{fontType}}] = [{% for font in family.fonts %}{{font.style|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{{ ", " if not forloop.last }}{% endfor %}] + } + {% endfor %} + {{accessModifier}} static let allCustomFonts: [{{fontType}}] = [{% for family in families %}{{family.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.all{{ ", " if not forloop.last }}{% endfor %}].flatMap { $0 } + {{accessModifier}} static func registerAllCustomFonts() { + allCustomFonts.forEach { $0.register() } + } +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +{{accessModifier}} struct {{fontType}} { + {{accessModifier}} let name: String + {{accessModifier}} let family: String + {{accessModifier}} let path: String + + #if os(macOS) + {{accessModifier}} typealias Font = NSFont + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Font = UIFont + #endif + + {{accessModifier}} func font(size: CGFloat) -> Font { + guard let font = Font(font: self, size: size) else { + fatalError("Unable to initialize font '\(name)' (\(family))") + } + return font + } + + {{accessModifier}} func register() { + // swiftlint:disable:next conditional_returns_on_newline + guard let url = url else { return } + CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil) + } + + fileprivate var url: URL? { + // swiftlint:disable:next implicit_return + {% if param.lookupFunction %} + return {{param.lookupFunction}}(name, family, path) + {% else %} + return {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil) + {% endif %} + } +} + +{{accessModifier}} extension {{fontType}}.Font { + convenience init?(font: {{fontType}}, size: CGFloat) { + #if os(iOS) || os(tvOS) || os(watchOS) + if !UIFont.fontNames(forFamilyName: font.family).contains(font.name) { + font.register() + } + #elseif os(macOS) + if let url = font.url, CTFontManagerGetScopeForURL(url as CFURL) == .none { + font.register() + } + #endif + + self.init(name: font.name, size: size) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No fonts found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift4.stencil new file mode 100644 index 00000000..9ad52fff --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift4.stencil @@ -0,0 +1,157 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if platform and storyboards %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %} +{% set prefix %}{% if isAppKit %}NS{% else %}UI{% endif %}{% endset %} +{% set controller %}{% if isAppKit %}Controller{% else %}ViewController{% endif %}{% endset %} +// swiftlint:disable sorted_imports +import Foundation +{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %} +import {{module}} +{% endfor %} + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length implicit_return + +// MARK: - Storyboard Scenes + +// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name +{% macro moduleName item %}{% filter removeNewlines %} + {% if item.moduleIsPlaceholder %} + {{ env.PRODUCT_MODULE_NAME|default:param.module }} + {% else %} + {{ item.module }} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro className item %}{% filter removeNewlines %} + {% set module %}{% call moduleName item %}{% endset %} + {% if module and ( not param.ignoreTargetModule or module != env.PRODUCT_MODULE_NAME and module != param.module ) %} + {{module}}. + {% endif %} + {{item.type}} +{% endfilter %}{% endmacro %} +{{accessModifier}} enum {{param.enumName|default:"StoryboardScene"}} { + {% for storyboard in storyboards %} + {% set storyboardName %}{{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}{% endset %} + {{accessModifier}} enum {{storyboardName}}: StoryboardType { + {{accessModifier}} static let storyboardName = "{{storyboard.name}}" + {% if storyboard.initialScene %} + + {% set sceneClass %}{% call className storyboard.initialScene %}{% endset %} + {{accessModifier}} static let initialScene = InitialSceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self) + {% endif %} + {% for scene in storyboard.scenes %} + + {% set sceneID %}{{scene.identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set sceneClass %}{% call className scene %}{% endset %} + {{accessModifier}} static let {{sceneID}} = SceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self, identifier: "{{scene.identifier}}") + {% endfor %} + } + {% endfor %} +} +// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name + +// MARK: - Implementation Details + +{{accessModifier}} protocol StoryboardType { + static var storyboardName: String { get } +} + +{{accessModifier}} extension StoryboardType { + static var storyboard: {{prefix}}Storyboard { + let name = {% if isAppKit %}NSStoryboard.Name({% endif %}self.storyboardName{% if isAppKit %}){% endif %} + {% if param.lookupFunction %} + return {{param.lookupFunction}}(name) + {% else %} + return {{prefix}}Storyboard(name: name, bundle: {{param.bundle|default:"BundleToken.bundle"}}) + {% endif %} + } +} + +{{accessModifier}} struct SceneType { + {{accessModifier}} let storyboard: StoryboardType.Type + {{accessModifier}} let identifier: String + + {{accessModifier}} func instantiate() -> T { + let identifier = {% if isAppKit %}NSStoryboard.SceneIdentifier({% endif %}self.identifier{% if isAppKit %}){% endif %} + guard let controller = storyboard.storyboard.instantiate{{controller}}(withIdentifier: identifier) as? T else { + fatalError("{{controller}} '\(identifier)' is not of the expected class \(T.self).") + } + return controller + } + + {% if isAppKit %} + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController { + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController { + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + {% else %} + @available(iOS 13.0, tvOS 13.0, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + {% endif %} +} + +{{accessModifier}} struct InitialSceneType { + {{accessModifier}} let storyboard: StoryboardType.Type + + {{accessModifier}} func instantiate() -> T { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}() as? T else { + fatalError("{{controller}} is not of the expected class \(T.self).") + } + return controller + } + + {% if isAppKit %} + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + {% else %} + @available(iOS 13.0, tvOS 13.0, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + {% endif %} +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% elif storyboards %} +// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately +{% else %} +// No storyboard found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift5.stencil new file mode 100644 index 00000000..5f29f8ba --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/scenes-swift5.stencil @@ -0,0 +1,159 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if platform and storyboards %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %} +{% set prefix %}{% if isAppKit %}NS{% else %}UI{% endif %}{% endset %} +{% set controller %}{% if isAppKit %}Controller{% else %}ViewController{% endif %}{% endset %} +// swiftlint:disable sorted_imports +import Foundation +{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %} +import {{module}} +{% endfor %} + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length implicit_return + +// MARK: - Storyboard Scenes + +// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name +{% macro moduleName item %}{% filter removeNewlines %} + {% if item.moduleIsPlaceholder %} + {{ env.PRODUCT_MODULE_NAME|default:param.module }} + {% else %} + {{ item.module }} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro className item %}{% filter removeNewlines %} + {% set module %}{% call moduleName item %}{% endset %} + {% if module and ( not param.ignoreTargetModule or module != env.PRODUCT_MODULE_NAME and module != param.module ) %} + {{module}}. + {% endif %} + {{item.type}} +{% endfilter %}{% endmacro %} +{{accessModifier}} enum {{param.enumName|default:"StoryboardScene"}} { + {% for storyboard in storyboards %} + {% set storyboardName %}{{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}{% endset %} + {{accessModifier}} enum {{storyboardName}}: StoryboardType { + {{accessModifier}} static let storyboardName = "{{storyboard.name}}" + {% if storyboard.initialScene %} + + {% set sceneClass %}{% call className storyboard.initialScene %}{% endset %} + {{accessModifier}} static let initialScene = InitialSceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self) + {% endif %} + {% for scene in storyboard.scenes %} + + {% set sceneID %}{{scene.identifier|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set sceneClass %}{% call className scene %}{% endset %} + {{accessModifier}} static let {{sceneID}} = SceneType<{{sceneClass}}>(storyboard: {{storyboardName}}.self, identifier: "{{scene.identifier}}") + {% endfor %} + } + {% endfor %} +} +// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name + +// MARK: - Implementation Details + +{{accessModifier}} protocol StoryboardType { + static var storyboardName: String { get } +} + +{{accessModifier}} extension StoryboardType { + static var storyboard: {{prefix}}Storyboard { + let name = {% if isAppKit %}NSStoryboard.Name({% endif %}self.storyboardName{% if isAppKit %}){% endif %} + {% if param.lookupFunction %} + return {{param.lookupFunction}}(name) + {% else %} + return {{prefix}}Storyboard(name: name, bundle: {{param.bundle|default:"BundleToken.bundle"}}) + {% endif %} + } +} + +{{accessModifier}} struct SceneType { + {{accessModifier}} let storyboard: StoryboardType.Type + {{accessModifier}} let identifier: String + + {{accessModifier}} func instantiate() -> T { + let identifier = {% if isAppKit %}NSStoryboard.SceneIdentifier({% endif %}self.identifier{% if isAppKit %}){% endif %} + guard let controller = storyboard.storyboard.instantiate{{controller}}(withIdentifier: identifier) as? T else { + fatalError("{{controller}} '\(identifier)' is not of the expected class \(T.self).") + } + return controller + } + + {% if isAppKit %} + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController { + let identifier = NSStoryboard.SceneIdentifier(self.identifier) + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController { + let identifier = NSStoryboard.SceneIdentifier(self.identifier) + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + {% else %} + @available(iOS 13.0, tvOS 13.0, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { + return storyboard.storyboard.instantiate{{controller}}(identifier: identifier, creator: block) + } + {% endif %} +} + +{{accessModifier}} struct InitialSceneType { + {{accessModifier}} let storyboard: StoryboardType.Type + + {{accessModifier}} func instantiate() -> T { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}() as? T else { + fatalError("{{controller}} is not of the expected class \(T.self).") + } + return controller + } + + {% if isAppKit %} + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSViewController { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + + @available(macOS 10.15, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T where T: NSWindowController { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + {% else %} + @available(iOS 13.0, tvOS 13.0, *) + {{accessModifier}} func instantiate(creator block: @escaping (NSCoder) -> T?) -> T { + guard let controller = storyboard.storyboard.instantiateInitial{{controller}}(creator: block) else { + fatalError("Storyboard \(storyboard.storyboardName) does not have an initial scene.") + } + return controller + } + {% endif %} +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% elif storyboards %} +// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately +{% else %} +// No storyboard found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift4.stencil new file mode 100644 index 00000000..476d5464 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift4.stencil @@ -0,0 +1,60 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if platform and storyboards %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %} +// swiftlint:disable sorted_imports +import Foundation +{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %} +import {{module}} +{% endfor %} + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Storyboard Segues + +// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name +{{accessModifier}} enum {{param.enumName|default:"StoryboardSegue"}} { + {% for storyboard in storyboards where storyboard.segues %} + {{accessModifier}} enum {{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}: String, SegueType { + {% for segue in storyboard.segues %} + {% set segueID %}{{segue.identifier|swiftIdentifier:"pretty"|lowerFirstWord}}{% endset %} + case {{segueID|escapeReservedKeywords}}{% if segueID != segue.identifier %} = "{{segue.identifier}}"{% endif %} + {% endfor %} + } + {% endfor %} +} +// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name + +// MARK: - Implementation Details + +{{accessModifier}} protocol SegueType: RawRepresentable {} + +{{accessModifier}} extension {% if isAppKit %}NSSeguePerforming{% else %}UIViewController{% endif %} { + func perform(segue: S, sender: Any? = nil) where S.RawValue == String { + let identifier = {% if isAppKit %}NSStoryboardSegue.Identifier({% endif %}segue.rawValue{% if isAppKit %}){% endif %} + performSegue{% if isAppKit %}?{% endif %}(withIdentifier: identifier, sender: sender) + } +} + +{{accessModifier}} extension SegueType where RawValue == String { + init?(_ segue: {% if isAppKit %}NS{% else %}UI{% endif %}StoryboardSegue) { + {% if isAppKit %} + #if swift(>=4.2) + guard let identifier = segue.identifier else { return nil } + #else + guard let identifier = segue.identifier?.rawValue else { return nil } + #endif + {% else %} + guard let identifier = segue.identifier else { return nil } + {% endif %} + self.init(rawValue: identifier) + } +} +{% elif storyboards %} +// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately +{% else %} +// No storyboard found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift5.stencil new file mode 100644 index 00000000..476d5464 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/ib/segues-swift5.stencil @@ -0,0 +1,60 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if platform and storyboards %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set isAppKit %}{% if platform == "macOS" %}true{% endif %}{% endset %} +// swiftlint:disable sorted_imports +import Foundation +{% for module in modules where module != env.PRODUCT_MODULE_NAME and module != param.module %} +import {{module}} +{% endfor %} + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Storyboard Segues + +// swiftlint:disable explicit_type_interface identifier_name line_length type_body_length type_name +{{accessModifier}} enum {{param.enumName|default:"StoryboardSegue"}} { + {% for storyboard in storyboards where storyboard.segues %} + {{accessModifier}} enum {{storyboard.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}: String, SegueType { + {% for segue in storyboard.segues %} + {% set segueID %}{{segue.identifier|swiftIdentifier:"pretty"|lowerFirstWord}}{% endset %} + case {{segueID|escapeReservedKeywords}}{% if segueID != segue.identifier %} = "{{segue.identifier}}"{% endif %} + {% endfor %} + } + {% endfor %} +} +// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name + +// MARK: - Implementation Details + +{{accessModifier}} protocol SegueType: RawRepresentable {} + +{{accessModifier}} extension {% if isAppKit %}NSSeguePerforming{% else %}UIViewController{% endif %} { + func perform(segue: S, sender: Any? = nil) where S.RawValue == String { + let identifier = {% if isAppKit %}NSStoryboardSegue.Identifier({% endif %}segue.rawValue{% if isAppKit %}){% endif %} + performSegue{% if isAppKit %}?{% endif %}(withIdentifier: identifier, sender: sender) + } +} + +{{accessModifier}} extension SegueType where RawValue == String { + init?(_ segue: {% if isAppKit %}NS{% else %}UI{% endif %}StoryboardSegue) { + {% if isAppKit %} + #if swift(>=4.2) + guard let identifier = segue.identifier else { return nil } + #else + guard let identifier = segue.identifier?.rawValue else { return nil } + #endif + {% else %} + guard let identifier = segue.identifier else { return nil } + {% endif %} + self.init(rawValue: identifier) + } +} +{% elif storyboards %} +// Mixed AppKit and UIKit storyboard files found, please invoke swiftgen with these separately +{% else %} +// No storyboard found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift4.stencil new file mode 100644 index 00000000..62ca48db --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift4.stencil @@ -0,0 +1,82 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - JSON Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift5.stencil new file mode 100644 index 00000000..62ca48db --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/inline-swift5.stencil @@ -0,0 +1,82 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - JSON Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift4.stencil new file mode 100644 index 00000000..c2466c7e --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift4.stencil @@ -0,0 +1,112 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - JSON Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}") + {% elif document.metadata.type == "Dictionary" %} + private static let _document = JSONDocument(path: "{% call transformPath file.path %}") + + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}") + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = _document["{{key}}"] +{% endfilter %}{% endmacro %} +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +private func objectFromJSON(at path: String) -> T { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let json = try? JSONSerialization.jsonObject(with: Data(contentsOf: url), options: []), + let result = json as? T else { + fatalError("Unable to load JSON at path: \(path)") + } + return result +} + +private struct JSONDocument { + let data: [String: Any] + + init(path: String) { + self.data = objectFromJSON(at: path) + } + + subscript(key: String) -> T { + guard let result = data[key] as? T else { + fatalError("Property '\(key)' is not of type \(T.self)") + } + return result + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift5.stencil new file mode 100644 index 00000000..c2466c7e --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/json/runtime-swift5.stencil @@ -0,0 +1,112 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - JSON Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}") + {% elif document.metadata.type == "Dictionary" %} + private static let _document = JSONDocument(path: "{% call transformPath file.path %}") + + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = objectFromJSON(at: "{% call transformPath file.path %}") + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = _document["{{key}}"] +{% endfilter %}{% endmacro %} +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} enum {{param.enumName|default:"JSONFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +private func objectFromJSON(at path: String) -> T { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let json = try? JSONSerialization.jsonObject(with: Data(contentsOf: url), options: []), + let result = json as? T else { + fatalError("Unable to load JSON at path: \(path)") + } + return result +} + +private struct JSONDocument { + let data: [String: Any] + + init(path: String) { + self.data = objectFromJSON(at: path) + } + + subscript(key: String) -> T { + guard let result = data[key] as? T else { + fatalError("Property '\(key)' is not of type \(T.self)") + } + return result + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift4.stencil new file mode 100644 index 00000000..c8e88310 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift4.stencil @@ -0,0 +1,82 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Plist Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Date" %} + Date(timeIntervalSinceReferenceDate: {{ value.timeIntervalSinceReferenceDate }}) + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift5.stencil new file mode 100644 index 00000000..c8e88310 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/inline-swift5.stencil @@ -0,0 +1,82 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Plist Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Date" %} + Date(timeIntervalSinceReferenceDate: {{ value.timeIntervalSinceReferenceDate }}) + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift4.stencil new file mode 100644 index 00000000..a498a8f2 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift4.stencil @@ -0,0 +1,117 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Plist Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = arrayFromPlist(at: "{% call transformPath file.path %}") + {% elif document.metadata.type == "Dictionary" %} + private static let _document = PlistDocument(path: "{% call transformPath file.path %}") + + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value %} + {% endfor %} + {% else %} + // Unsupported root type `{{rootType}}` + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = _document["{{key}}"] +{% endfilter %}{% endmacro %} +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +private func arrayFromPlist(at path: String) -> [T] { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let data = NSArray(contentsOf: url) as? [T] else { + fatalError("Unable to load PLIST at path: \(path)") + } + return data +} + +private struct PlistDocument { + let data: [String: Any] + + init(path: String) { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let data = NSDictionary(contentsOf: url) as? [String: Any] else { + fatalError("Unable to load PLIST at path: \(path)") + } + self.data = data + } + + subscript(key: String) -> T { + guard let result = data[key] as? T else { + fatalError("Property '\(key)' is not of type \(T.self)") + } + return result + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift5.stencil new file mode 100644 index 00000000..a498a8f2 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/plist/runtime-swift5.stencil @@ -0,0 +1,117 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - Plist Files +{% macro fileBlock file %} + {% call documentBlock file file.document %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = arrayFromPlist(at: "{% call transformPath file.path %}") + {% elif document.metadata.type == "Dictionary" %} + private static let _document = PlistDocument(path: "{% call transformPath file.path %}") + + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value %} + {% endfor %} + {% else %} + // Unsupported root type `{{rootType}}` + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = _document["{{key}}"] +{% endfilter %}{% endmacro %} +{% macro transformPath path %}{% filter removeNewlines %} + {% if param.preservePath %} + {{path}} + {% else %} + {{path|basename}} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length type_body_length +{{accessModifier}} enum {{param.enumName|default:"PlistFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length type_body_length + +// MARK: - Implementation Details + +private func arrayFromPlist(at path: String) -> [T] { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let data = NSArray(contentsOf: url) as? [T] else { + fatalError("Unable to load PLIST at path: \(path)") + } + return data +} + +private struct PlistDocument { + let data: [String: Any] + + init(path: String) { + {% if param.lookupFunction %} + guard let url = {{param.lookupFunction}}(path), + {% else %} + guard let url = {{param.bundle|default:"BundleToken.bundle"}}.url(forResource: path, withExtension: nil), + {% endif %} + let data = NSDictionary(contentsOf: url) as? [String: Any] else { + fatalError("Unable to load PLIST at path: \(path)") + } + self.data = data + } + + subscript(key: String) -> T { + guard let result = data[key] as? T else { + fatalError("Property '\(key)' is not of type \(T.self)") + } + return result + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift4.stencil new file mode 100644 index 00000000..5bb4a128 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift4.stencil @@ -0,0 +1,99 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + _ p{{forloop.counter}}: Any + {% else %} + _ p{{forloop.counter}}: {{type}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + String(describing: p{{forloop.counter}}) + {% elif type == "UnsafeRawPointer" %} + Int(bitPattern: p{{forloop.counter}}) + {% else %} + p{{forloop.counter}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro recursiveBlock table item %} + {% for string in item.strings %} + {% if not param.noComments %} + {% for line in string.translation|split:"\n" %} + /// {{line}} + {% endfor %} + {% endif %} + {% if string.types %} + {{accessModifier}} static func {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { + return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %}) + } + {% elif param.lookupFunction %} + {# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #} + {{accessModifier}} static var {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") } + {% else %} + {{accessModifier}} static let {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}") + {% endif %} + {% endfor %} + {% for child in item.children %} + {% call recursiveBlock table child %} + {% endfor %} +{% endmacro %} +// swiftlint:disable function_parameter_count identifier_name line_length type_body_length +{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} +{{accessModifier}} enum {{enumName}} { + {% if tables.count > 1 or param.forceFileNameEnum %} + {% for table in tables %} + {{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call recursiveBlock tables.first.name tables.first.levels %} + {% endif %} +} +// swiftlint:enable function_parameter_count identifier_name line_length type_body_length + +// MARK: - Implementation Details + +extension {{enumName}} { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + {% if param.lookupFunction %} + let format = {{ param.lookupFunction }}(key, table) + {% else %} + let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table) + {% endif %} + return String(format: format, locale: Locale.current, arguments: args) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No string found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift5.stencil new file mode 100644 index 00000000..5bb4a128 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/flat-swift5.stencil @@ -0,0 +1,99 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + _ p{{forloop.counter}}: Any + {% else %} + _ p{{forloop.counter}}: {{type}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + String(describing: p{{forloop.counter}}) + {% elif type == "UnsafeRawPointer" %} + Int(bitPattern: p{{forloop.counter}}) + {% else %} + p{{forloop.counter}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro recursiveBlock table item %} + {% for string in item.strings %} + {% if not param.noComments %} + {% for line in string.translation|split:"\n" %} + /// {{line}} + {% endfor %} + {% endif %} + {% if string.types %} + {{accessModifier}} static func {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { + return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %}) + } + {% elif param.lookupFunction %} + {# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #} + {{accessModifier}} static var {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") } + {% else %} + {{accessModifier}} static let {{string.key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}") + {% endif %} + {% endfor %} + {% for child in item.children %} + {% call recursiveBlock table child %} + {% endfor %} +{% endmacro %} +// swiftlint:disable function_parameter_count identifier_name line_length type_body_length +{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} +{{accessModifier}} enum {{enumName}} { + {% if tables.count > 1 or param.forceFileNameEnum %} + {% for table in tables %} + {{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call recursiveBlock tables.first.name tables.first.levels %} + {% endif %} +} +// swiftlint:enable function_parameter_count identifier_name line_length type_body_length + +// MARK: - Implementation Details + +extension {{enumName}} { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + {% if param.lookupFunction %} + let format = {{ param.lookupFunction }}(key, table) + {% else %} + let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table) + {% endif %} + return String(format: format, locale: Locale.current, arguments: args) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No string found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-h.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-h.stencil new file mode 100644 index 00000000..7c502917 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-h.stencil @@ -0,0 +1,68 @@ +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +#import + +NS_ASSUME_NONNULL_BEGIN + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + ({% call paramTranslate type %})p{{ forloop.counter }}{{ " :" if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + p{{forloop.counter}}{{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro paramTranslate swiftType %} + {% if swiftType == "Any" %} + id + {% elif swiftType == "CChar" %} + char + {% elif swiftType == "Float" %} + float + {% elif swiftType == "Int" %} + NSInteger + {% elif swiftType == "String" %} + id + {% elif swiftType == "UnsafePointer" %} + char* + {% elif swiftType == "UnsafeRawPointer" %} + void* + {% else %} + objc-h.stencil is missing '{{swiftType}}' + {% endif %} +{% endmacro %} +{% macro emitOneMethod table item %} +{% for string in item.strings %} +{% if not param.noComments %} +{% for line in string.translation|split:"\n" %} +/// {{line}} +{% endfor %} +{% endif %} +{% if string.types %} + {% if string.types.count == 1 %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValue:{% call parametersBlock string.types %}; + {% else %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValues:{% call parametersBlock string.types %}; + {% endif %} +{% else %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}; +{% endif %} +{% endfor %} +{% for child in item.children %} +{% call emitOneMethod table child %} +{% endfor %} +{% endmacro %} +{% for table in tables %} +@interface {{ table.name }} : NSObject + {% call emitOneMethod table.name table.levels %} +@end + +{% endfor %} + +NS_ASSUME_NONNULL_END +{% else %} +// No strings found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-m.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-m.stencil new file mode 100644 index 00000000..1f154b59 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/objc-m.stencil @@ -0,0 +1,90 @@ +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +#import "{{ param.headerName|default:"Localizable.h" }}" +{% if not param.bundle %} + +@interface BundleToken : NSObject +@end + +@implementation BundleToken +@end +{% endif %} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-security" + +static NSString* tr(NSString *tableName, NSString *key, ...) { + NSBundle *bundle = {{param.bundle|default:"[NSBundle bundleForClass:BundleToken.class]"}}; + NSString *format = [bundle localizedStringForKey:key value:nil table:tableName]; + NSLocale *locale = [NSLocale currentLocale]; + + va_list args; + va_start(args, key); + NSString *result = [[NSString alloc] initWithFormat:format locale:locale arguments:args]; + va_end(args); + + return result; +}; +#pragma clang diagnostic pop + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + ({% call paramTranslate type %})p{{ forloop.counter }}{{ " :" if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + p{{forloop.counter}}{{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro paramTranslate swiftType %} + {% if swiftType == "Any" %} + id + {% elif swiftType == "CChar" %} + char + {% elif swiftType == "Float" %} + float + {% elif swiftType == "Int" %} + NSInteger + {% elif swiftType == "String" %} + id + {% elif swiftType == "UnsafePointer" %} + char* + {% elif swiftType == "UnsafeRawPointer" %} + void* + {% else %} + objc-m.stencil is missing '{{swiftType}}' + {% endif %} +{% endmacro %} +{% macro tableContents table item %} + {% for string in item.strings %} + {% if string.types %} + {% if string.types.count == 1 %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValue:{% call parametersBlock string.types %} + {% else %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}}WithValues:{% call parametersBlock string.types %} + {% endif %} +{ + return tr(@"{{table}}", @"{{string.key}}", {% call argumentsBlock string.types %}); +} +{% else %} ++ (NSString*){{string.key|swiftIdentifier:"pretty"|lowerFirstWord}} { + return tr(@"{{table}}", @"{{string.key}}"); +} + {% endif %} + {% endfor %} + {% for child in item.children %} + {% call tableContents table child %} + {% endfor %} +{% endmacro %} +{% for table in tables %} + {% set tableName %}{{table.name|default:"Localized"}}{% endset %} +@implementation {{ tableName }} : NSObject + {% call tableContents table.name table.levels %} +@end + +{% endfor %} +{% else %} +// No strings found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift4.stencil new file mode 100644 index 00000000..f809bc24 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift4.stencil @@ -0,0 +1,104 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + _ p{{forloop.counter}}: Any + {% else %} + _ p{{forloop.counter}}: {{type}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + String(describing: p{{forloop.counter}}) + {% elif type == "UnsafeRawPointer" %} + Int(bitPattern: p{{forloop.counter}}) + {% else %} + p{{forloop.counter}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro recursiveBlock table item %} + {% for string in item.strings %} + {% if not param.noComments %} + {% for line in string.translation|split:"\n" %} + /// {{line}} + {% endfor %} + {% endif %} + {% if string.types %} + {{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { + return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %}) + } + {% elif param.lookupFunction %} + {# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #} + {{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") } + {% else %} + {{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}") + {% endif %} + {% endfor %} + {% for child in item.children %} + + {{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %} + } + {% endfor %} +{% endmacro %} +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} +{{accessModifier}} enum {{enumName}} { + {% if tables.count > 1 or param.forceFileNameEnum %} + {% for table in tables %} + {{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call recursiveBlock tables.first.name tables.first.levels %} + {% endif %} +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension {{enumName}} { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + {% if param.lookupFunction %} + let format = {{ param.lookupFunction }}(key, table) + {% else %} + let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table) + {% endif %} + return String(format: format, locale: Locale.current, arguments: args) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No string found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift5.stencil new file mode 100644 index 00000000..f809bc24 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/strings/structured-swift5.stencil @@ -0,0 +1,104 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if tables.count > 0 %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Strings + +{% macro parametersBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + _ p{{forloop.counter}}: Any + {% else %} + _ p{{forloop.counter}}: {{type}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro argumentsBlock types %}{% filter removeNewlines:"leading" %} + {% for type in types %} + {% if type == "String" %} + String(describing: p{{forloop.counter}}) + {% elif type == "UnsafeRawPointer" %} + Int(bitPattern: p{{forloop.counter}}) + {% else %} + p{{forloop.counter}} + {% endif %} + {{ ", " if not forloop.last }} + {% endfor %} +{% endfilter %}{% endmacro %} +{% macro recursiveBlock table item %} + {% for string in item.strings %} + {% if not param.noComments %} + {% for line in string.translation|split:"\n" %} + /// {{line}} + {% endfor %} + {% endif %} + {% if string.types %} + {{accessModifier}} static func {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}({% call parametersBlock string.types %}) -> String { + return {{enumName}}.tr("{{table}}", "{{string.key}}", {% call argumentsBlock string.types %}) + } + {% elif param.lookupFunction %} + {# custom localization function is mostly used for in-app lang selection, so we want the loc to be recomputed at each call for those (hence the computed var) #} + {{accessModifier}} static var {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}: String { return {{enumName}}.tr("{{table}}", "{{string.key}}") } + {% else %} + {{accessModifier}} static let {{string.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{enumName}}.tr("{{table}}", "{{string.key}}") + {% endif %} + {% endfor %} + {% for child in item.children %} + + {{accessModifier}} enum {{child.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table child %}{% endfilter %} + } + {% endfor %} +{% endmacro %} +// swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:disable nesting type_body_length type_name vertical_whitespace_opening_braces +{% set enumName %}{{param.enumName|default:"L10n"}}{% endset %} +{{accessModifier}} enum {{enumName}} { + {% if tables.count > 1 or param.forceFileNameEnum %} + {% for table in tables %} + {{accessModifier}} enum {{table.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call recursiveBlock table.name table.levels %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call recursiveBlock tables.first.name tables.first.levels %} + {% endif %} +} +// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length +// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces + +// MARK: - Implementation Details + +extension {{enumName}} { + private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { + {% if param.lookupFunction %} + let format = {{ param.lookupFunction }}(key, table) + {% else %} + let format = {{param.bundle|default:"BundleToken.bundle"}}.localizedString(forKey: key, value: nil, table: table) + {% endif %} + return String(format: format, locale: Locale.current, arguments: args) + } +} +{% if not param.bundle and not param.lookupFunction %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No string found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift4.stencil new file mode 100644 index 00000000..c8565931 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift4.stencil @@ -0,0 +1,329 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if catalogs %} +{% set enumName %}{{param.enumName|default:"Asset"}}{% endset %} +{% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %} +{% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %} +{% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %} +{% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %} +{% set symbolType %}{{param.symbolTypeName|default:"SymbolAsset"}}{% endset %} +{% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit +#elseif os(iOS) +{% if resourceCount.arresourcegroup > 0 %} + import ARKit +{% endif %} + import UIKit +#elseif os(tvOS) || os(watchOS) + import UIKit +#endif + +// Deprecated typealiases +{% if resourceCount.color > 0 %} +@available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color +{% endif %} +{% if resourceCount.image > 0 %} +@available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image +{% endif %} + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Asset Catalogs + +{% macro enumBlock assets %} + {% call casesBlock assets %} + {% if param.allValues %} + + // swiftlint:disable trailing_comma + {% if resourceCount.arresourcegroup > 0 %} + {{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.color > 0 %} + {{accessModifier}} static let allColors: [{{colorType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.data > 0 %} + {{accessModifier}} static let allDataAssets: [{{dataType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.image > 0 %} + {{accessModifier}} static let allImages: [{{imageType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.symbol > 0 %} + {{accessModifier}} static let allSymbols: [{{symbolType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "symbol" "" %}{% endfilter %} + ] + {% endif %} + // swiftlint:enable trailing_comma + {% endif %} +{% endmacro %} +{% macro casesBlock assets %} + {% for asset in assets %} + {% if asset.type == "arresourcegroup" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}") + {% elif asset.type == "color" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}") + {% elif asset.type == "data" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}") + {% elif asset.type == "image" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}") + {% elif asset.type == "symbol" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{symbolType}}(name: "{{asset.value}}") + {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} + {{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %} + } + {% elif asset.items %} + {% call casesBlock asset.items %} + {% endif %} + {% endfor %} +{% endmacro %} +{% macro allValuesBlock assets filter prefix %} + {% for asset in assets %} + {% if asset.type == filter %} + {{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}, + {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} + {% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %} + {% call allValuesBlock asset.items filter prefix2 %} + {% elif asset.items %} + {% call allValuesBlock asset.items filter prefix %} + {% endif %} + {% endfor %} +{% endmacro %} +// swiftlint:disable identifier_name line_length nesting type_body_length type_name +{{accessModifier}} enum {{enumName}} { + {% if catalogs.count > 1 or param.forceFileNameEnum %} + {% for catalog in catalogs %} + {{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock catalogs.first.assets %} + {% endif %} +} +// swiftlint:enable identifier_name line_length nesting type_body_length type_name + +// MARK: - Implementation Details +{% if resourceCount.arresourcegroup > 0 %} + +{{accessModifier}} struct {{arResourceGroupType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(iOS) + @available(iOS 11.3, *) + {{accessModifier}} var referenceImages: Set { + return ARReferenceImage.referenceImages(in: self) + } + + @available(iOS 12.0, *) + {{accessModifier}} var referenceObjects: Set { + return ARReferenceObject.referenceObjects(in: self) + } + #endif +} + +#if os(iOS) +@available(iOS 11.3, *) +{{accessModifier}} extension ARReferenceImage { + static func referenceImages(in asset: {{arResourceGroupType}}) -> Set { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set() + } +} + +@available(iOS 12.0, *) +{{accessModifier}} extension ARReferenceObject { + static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set() + } +} +#endif +{% endif %} +{% if resourceCount.color > 0 %} + +{{accessModifier}} final class {{colorType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(macOS) + {{accessModifier}} typealias Color = NSColor + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Color = UIColor + #endif + + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + {{accessModifier}} private(set) lazy var color: Color = Color(asset: self) + + #if os(iOS) || os(tvOS) + @available(iOS 11.0, tvOS 11.0, *) + {{accessModifier}} func color(compatibleWith traitCollection: UITraitCollection) -> Color { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load color asset named \(name).") + } + return color + } + #endif + + fileprivate init(name: String) { + self.name = name + } +} + +{{accessModifier}} extension {{colorType}}.Color { + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + convenience init!(asset: {{colorType}}) { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSColor.Name(asset.name), bundle: bundle) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} +{% endif %} +{% if resourceCount.data > 0 %} + +{{accessModifier}} struct {{dataType}} { + {{accessModifier}} fileprivate(set) var name: String + + @available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) + {{accessModifier}} var data: NSDataAsset { + return NSDataAsset(asset: self) + } +} + +@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) +{{accessModifier}} extension NSDataAsset { + convenience init!(asset: {{dataType}}) { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) || os(watchOS) + self.init(name: asset.name, bundle: bundle) + #elseif os(macOS) + self.init(name: NSDataAsset.Name(asset.name), bundle: bundle) + #endif + } +} +{% endif %} +{% if resourceCount.image > 0 %} + +{{accessModifier}} struct {{imageType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(macOS) + {{accessModifier}} typealias Image = NSImage + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Image = UIImage + #endif + + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) + {{accessModifier}} var image: Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + let name = NSImage.Name(self.name) + let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + + #if os(iOS) || os(tvOS) + @available(iOS 8.0, tvOS 9.0, *) + {{accessModifier}} func image(compatibleWith traitCollection: UITraitCollection) -> Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + #endif +} + +{{accessModifier}} extension {{imageType}}.Image { + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) + @available(macOS, deprecated, + message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property") + convenience init!(asset: {{imageType}}) { + #if os(iOS) || os(tvOS) + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSImage.Name(asset.name)) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} +{% endif %} +{% if resourceCount.symbol > 0 %} + +{{accessModifier}} struct {{symbolType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(iOS) || os(tvOS) || os(watchOS) + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) + {{accessModifier}} typealias Configuration = UIImage.SymbolConfiguration + {{accessModifier}} typealias Image = UIImage + + @available(iOS 12.0, tvOS 12.0, watchOS 5.0, *) + {{accessModifier}} var image: Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load symbol asset named \(name).") + } + return result + } + + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) + {{accessModifier}} func image(with configuration: Configuration) -> Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let result = Image(named: name, in: bundle, with: configuration) else { + fatalError("Unable to load symbol asset named \(name).") + } + return result + } + #endif +} +{% endif %} +{% if not param.bundle %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No assets found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift5.stencil new file mode 100644 index 00000000..42df7be8 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/xcassets/swift5.stencil @@ -0,0 +1,337 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if catalogs %} +{% set enumName %}{{param.enumName|default:"Asset"}}{% endset %} +{% set arResourceGroupType %}{{param.arResourceGroupTypeName|default:"ARResourceGroupAsset"}}{% endset %} +{% set colorType %}{{param.colorTypeName|default:"ColorAsset"}}{% endset %} +{% set dataType %}{{param.dataTypeName|default:"DataAsset"}}{% endset %} +{% set imageType %}{{param.imageTypeName|default:"ImageAsset"}}{% endset %} +{% set symbolType %}{{param.symbolTypeName|default:"SymbolAsset"}}{% endset %} +{% set forceNamespaces %}{{param.forceProvidesNamespaces|default:"false"}}{% endset %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +#if os(macOS) + import AppKit +#elseif os(iOS) +{% if resourceCount.arresourcegroup > 0 %} + import ARKit +{% endif %} + import UIKit +#elseif os(tvOS) || os(watchOS) + import UIKit +#endif + +// Deprecated typealiases +{% if resourceCount.color > 0 %} +@available(*, deprecated, renamed: "{{colorType}}.Color", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.colorAliasName|default:"AssetColorTypeAlias"}} = {{colorType}}.Color +{% endif %} +{% if resourceCount.image > 0 %} +@available(*, deprecated, renamed: "{{imageType}}.Image", message: "This typealias will be removed in SwiftGen 7.0") +{{accessModifier}} typealias {{param.imageAliasName|default:"AssetImageTypeAlias"}} = {{imageType}}.Image +{% endif %} + +// swiftlint:disable superfluous_disable_command file_length implicit_return + +// MARK: - Asset Catalogs + +{% macro enumBlock assets %} + {% call casesBlock assets %} + {% if param.allValues %} + + // swiftlint:disable trailing_comma + {% if resourceCount.arresourcegroup > 0 %} + {{accessModifier}} static let allResourceGroups: [{{arResourceGroupType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "arresourcegroup" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.color > 0 %} + {{accessModifier}} static let allColors: [{{colorType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "color" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.data > 0 %} + {{accessModifier}} static let allDataAssets: [{{dataType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "data" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.image > 0 %} + {{accessModifier}} static let allImages: [{{imageType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "image" "" %}{% endfilter %} + ] + {% endif %} + {% if resourceCount.symbol > 0 %} + {{accessModifier}} static let allSymbols: [{{symbolType}}] = [ + {% filter indent:2 %}{% call allValuesBlock assets "symbol" "" %}{% endfilter %} + ] + {% endif %} + // swiftlint:enable trailing_comma + {% endif %} +{% endmacro %} +{% macro casesBlock assets %} + {% for asset in assets %} + {% if asset.type == "arresourcegroup" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{arResourceGroupType}}(name: "{{asset.value}}") + {% elif asset.type == "color" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{colorType}}(name: "{{asset.value}}") + {% elif asset.type == "data" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{dataType}}(name: "{{asset.value}}") + {% elif asset.type == "image" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{imageType}}(name: "{{asset.value}}") + {% elif asset.type == "symbol" %} + {{accessModifier}} static let {{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}} = {{symbolType}}(name: "{{asset.value}}") + {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} + {{accessModifier}} enum {{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call casesBlock asset.items %}{% endfilter %} + } + {% elif asset.items %} + {% call casesBlock asset.items %} + {% endif %} + {% endfor %} +{% endmacro %} +{% macro allValuesBlock assets filter prefix %} + {% for asset in assets %} + {% if asset.type == filter %} + {{prefix}}{{asset.name|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}, + {% elif asset.items and ( forceNamespaces == "true" or asset.isNamespaced == "true" ) %} + {% set prefix2 %}{{prefix}}{{asset.name|swiftIdentifier:"pretty"|escapeReservedKeywords}}.{% endset %} + {% call allValuesBlock asset.items filter prefix2 %} + {% elif asset.items %} + {% call allValuesBlock asset.items filter prefix %} + {% endif %} + {% endfor %} +{% endmacro %} +// swiftlint:disable identifier_name line_length nesting type_body_length type_name +{{accessModifier}} enum {{enumName}} { + {% if catalogs.count > 1 or param.forceFileNameEnum %} + {% for catalog in catalogs %} + {{accessModifier}} enum {{catalog.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call enumBlock catalog.assets %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call enumBlock catalogs.first.assets %} + {% endif %} +} +// swiftlint:enable identifier_name line_length nesting type_body_length type_name + +// MARK: - Implementation Details +{% if resourceCount.arresourcegroup > 0 %} + +{{accessModifier}} struct {{arResourceGroupType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(iOS) + @available(iOS 11.3, *) + {{accessModifier}} var referenceImages: Set { + return ARReferenceImage.referenceImages(in: self) + } + + @available(iOS 12.0, *) + {{accessModifier}} var referenceObjects: Set { + return ARReferenceObject.referenceObjects(in: self) + } + #endif +} + +#if os(iOS) +@available(iOS 11.3, *) +{{accessModifier}} extension ARReferenceImage { + static func referenceImages(in asset: {{arResourceGroupType}}) -> Set { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + return referenceImages(inGroupNamed: asset.name, bundle: bundle) ?? Set() + } +} + +@available(iOS 12.0, *) +{{accessModifier}} extension ARReferenceObject { + static func referenceObjects(in asset: {{arResourceGroupType}}) -> Set { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + return referenceObjects(inGroupNamed: asset.name, bundle: bundle) ?? Set() + } +} +#endif +{% endif %} +{% if resourceCount.color > 0 %} + +{{accessModifier}} final class {{colorType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(macOS) + {{accessModifier}} typealias Color = NSColor + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Color = UIColor + #endif + + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + {{accessModifier}} private(set) lazy var color: Color = { + guard let color = Color(asset: self) else { + fatalError("Unable to load color asset named \(name).") + } + return color + }() + + #if os(iOS) || os(tvOS) + @available(iOS 11.0, tvOS 11.0, *) + {{accessModifier}} func color(compatibleWith traitCollection: UITraitCollection) -> Color { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let color = Color(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load color asset named \(name).") + } + return color + } + #endif + + fileprivate init(name: String) { + self.name = name + } +} + +{{accessModifier}} extension {{colorType}}.Color { + @available(iOS 11.0, tvOS 11.0, watchOS 4.0, macOS 10.13, *) + convenience init?(asset: {{colorType}}) { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSColor.Name(asset.name), bundle: bundle) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} +{% endif %} +{% if resourceCount.data > 0 %} + +{{accessModifier}} struct {{dataType}} { + {{accessModifier}} fileprivate(set) var name: String + + @available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) + {{accessModifier}} var data: NSDataAsset { + guard let data = NSDataAsset(asset: self) else { + fatalError("Unable to load data asset named \(name).") + } + return data + } +} + +@available(iOS 9.0, tvOS 9.0, watchOS 6.0, macOS 10.11, *) +{{accessModifier}} extension NSDataAsset { + convenience init?(asset: {{dataType}}) { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) || os(watchOS) + self.init(name: asset.name, bundle: bundle) + #elseif os(macOS) + self.init(name: NSDataAsset.Name(asset.name), bundle: bundle) + #endif + } +} +{% endif %} +{% if resourceCount.image > 0 %} + +{{accessModifier}} struct {{imageType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(macOS) + {{accessModifier}} typealias Image = NSImage + #elseif os(iOS) || os(tvOS) || os(watchOS) + {{accessModifier}} typealias Image = UIImage + #endif + + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, macOS 10.7, *) + {{accessModifier}} var image: Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + let name = NSImage.Name(self.name) + let image = (bundle == .main) ? NSImage(named: name) : bundle.image(forResource: name) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + + #if os(iOS) || os(tvOS) + @available(iOS 8.0, tvOS 9.0, *) + {{accessModifier}} func image(compatibleWith traitCollection: UITraitCollection) -> Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let result = Image(named: name, in: bundle, compatibleWith: traitCollection) else { + fatalError("Unable to load image asset named \(name).") + } + return result + } + #endif +} + +{{accessModifier}} extension {{imageType}}.Image { + @available(iOS 8.0, tvOS 9.0, watchOS 2.0, *) + @available(macOS, deprecated, + message: "This initializer is unsafe on macOS, please use the {{imageType}}.image property") + convenience init?(asset: {{imageType}}) { + #if os(iOS) || os(tvOS) + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + self.init(named: asset.name, in: bundle, compatibleWith: nil) + #elseif os(macOS) + self.init(named: NSImage.Name(asset.name)) + #elseif os(watchOS) + self.init(named: asset.name) + #endif + } +} +{% endif %} +{% if resourceCount.symbol > 0 %} + +{{accessModifier}} struct {{symbolType}} { + {{accessModifier}} fileprivate(set) var name: String + + #if os(iOS) || os(tvOS) || os(watchOS) + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) + {{accessModifier}} typealias Configuration = UIImage.SymbolConfiguration + {{accessModifier}} typealias Image = UIImage + + @available(iOS 12.0, tvOS 12.0, watchOS 5.0, *) + {{accessModifier}} var image: Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + #if os(iOS) || os(tvOS) + let image = Image(named: name, in: bundle, compatibleWith: nil) + #elseif os(watchOS) + let image = Image(named: name) + #endif + guard let result = image else { + fatalError("Unable to load symbol asset named \(name).") + } + return result + } + + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) + {{accessModifier}} func image(with configuration: Configuration) -> Image { + let bundle = {{param.bundle|default:"BundleToken.bundle"}} + guard let result = Image(named: name, in: bundle, with: configuration) else { + fatalError("Unable to load symbol asset named \(name).") + } + return result + } + #endif +} +{% endif %} +{% if not param.bundle %} + +// swiftlint:disable convenience_type +private final class BundleToken { + static let bundle: Bundle = { + #if SWIFT_PACKAGE + return Bundle.module + #else + return Bundle(for: BundleToken.self) + #endif + }() +} +// swiftlint:enable convenience_type +{% endif %} +{% else %} +// No assets found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift4.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift4.stencil new file mode 100644 index 00000000..9cc2aa36 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift4.stencil @@ -0,0 +1,92 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set documentPrefix %}{{param.documentName|default:"Document"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - YAML Files +{% macro fileBlock file %} + {% if file.documents.count > 1 %} + {% for document in file.documents %} + {% set documentName %}{{documentPrefix}}{{forloop.counter}}{% endset %} + {{accessModifier}} enum {{documentName|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call documentBlock file document %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call documentBlock file file.documents.first %} + {% endif %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"YAMLFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift5.stencil b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift5.stencil new file mode 100644 index 00000000..9cc2aa36 --- /dev/null +++ b/bin/SwiftGen_SwiftGenCLI.bundle/Contents/Resources/templates/yaml/inline-swift5.stencil @@ -0,0 +1,92 @@ +// swiftlint:disable all +// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen + +{% if files %} +{% set accessModifier %}{% if param.publicAccess %}public{% else %}internal{% endif %}{% endset %} +{% set documentPrefix %}{{param.documentName|default:"Document"}}{% endset %} +import Foundation + +// swiftlint:disable superfluous_disable_command +// swiftlint:disable file_length + +// MARK: - YAML Files +{% macro fileBlock file %} + {% if file.documents.count > 1 %} + {% for document in file.documents %} + {% set documentName %}{{documentPrefix}}{{forloop.counter}}{% endset %} + {{accessModifier}} enum {{documentName|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call documentBlock file document %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call documentBlock file file.documents.first %} + {% endif %} +{% endmacro %} +{% macro documentBlock file document %} + {% set rootType %}{% call typeBlock document.metadata %}{% endset %} + {% if document.metadata.type == "Array" %} + {{accessModifier}} static let items: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% elif document.metadata.type == "Dictionary" %} + {% for key,value in document.metadata.properties %} + {{accessModifier}} {% call propertyBlock key value document.data %} + {% endfor %} + {% else %} + {{accessModifier}} static let value: {{rootType}} = {% call valueBlock document.data document.metadata %} + {% endif %} +{% endmacro %} +{% macro typeBlock metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "Array" %} + [{% call typeBlock metadata.element %}] + {% elif metadata.type == "Dictionary" %} + [String: Any] + {% elif metadata.type == "Optional" %} + Any? + {% else %} + {{metadata.type}} + {% endif %} +{% endfilter %}{% endmacro %} +{% macro propertyBlock key metadata data %}{% filter removeNewlines:"leading" %} + {% set propertyName %}{{key|swiftIdentifier:"pretty"|lowerFirstWord|escapeReservedKeywords}}{% endset %} + {% set propertyType %}{% call typeBlock metadata %}{% endset %} + static let {{propertyName}}: {{propertyType}} = {% call valueBlock data[key] metadata %} +{% endfilter %}{% endmacro %} +{% macro valueBlock value metadata %}{% filter removeNewlines:"leading" %} + {% if metadata.type == "String" %} + "{{ value }}" + {% elif metadata.type == "Optional" %} + nil + {% elif metadata.type == "Array" and value %} + [{% for value in value %} + {% call valueBlock value metadata.element.items[forloop.counter0]|default:metadata.element %} + {{ ", " if not forloop.last }} + {% endfor %}] + {% elif metadata.type == "Dictionary" %} + [{% for key,value in value %} + "{{key}}": {% call valueBlock value metadata.properties[key] %} + {{ ", " if not forloop.last }} + {% empty %} + : + {% endfor %}] + {% elif metadata.type == "Bool" %} + {% if value %}true{% else %}false{% endif %} + {% else %} + {{ value }} + {% endif %} +{% endfilter %}{% endmacro %} + +// swiftlint:disable identifier_name line_length number_separator type_body_length +{{accessModifier}} enum {{param.enumName|default:"YAMLFiles"}} { + {% if files.count > 1 or param.forceFileNameEnum %} + {% for file in files %} + {{accessModifier}} enum {{file.name|swiftIdentifier:"pretty"|escapeReservedKeywords}} { + {% filter indent:2 %}{% call fileBlock file %}{% endfilter %} + } + {% endfor %} + {% else %} + {% call fileBlock files.first %} + {% endif %} +} +// swiftlint:enable identifier_name line_length number_separator type_body_length +{% else %} +// No files found +{% endif %} diff --git a/bin/swiftgen b/bin/swiftgen new file mode 100755 index 00000000..1bc2b874 Binary files /dev/null and b/bin/swiftgen differ diff --git a/swiftgen.yml b/swiftgen.yml new file mode 100644 index 00000000..f6e76e1e --- /dev/null +++ b/swiftgen.yml @@ -0,0 +1,5 @@ +strings: + inputs: Translations/en.lproj + outputs: + - templateName: structured-swift5 + output: Shared/Generated/Strings.swift