import Defaults import SwiftUI struct HomeSettings: View { private var model = FavoritesModel.shared @Default(.favorites) private var favorites @Default(.showHome) private var showHome @Default(.showFavoritesInHome) private var showFavoritesInHome @Default(.showQueueInHome) private var showQueueInHome @Default(.showOpenActionsInHome) private var showOpenActionsInHome @Default(.showOpenActionsToolbarItem) private var showOpenActionsToolbarItem @ObservedObject private var accounts = AccountsModel.shared var body: some View { Group { #if os(tvOS) ScrollView { LazyVStack { homeSettings .padding(.horizontal) editor } } .frame(width: 1000) #else List { homeSettings editor } #endif } .navigationTitle("Home Settings") } var editor: some View { Group { Section(header: SettingsHeader(text: "Favorites")) { if favorites.isEmpty { Text("Favorites is empty") .padding(.vertical) .foregroundColor(.secondary) } ForEach(favorites) { item in FavoriteItemEditor(item: item) } } #if os(tvOS) .padding(.trailing, 40) #endif if !model.addableItems().isEmpty { Section(header: SettingsHeader(text: "Available")) { ForEach(model.addableItems()) { item in HStack { FavoriteItemLabel(item: item) Spacer() Button { model.add(item) } label: { Label("Add to Favorites", systemImage: "heart") #if os(tvOS) .font(.system(size: 30)) #endif } .help("Add to Favorites") #if !os(tvOS) .buttonStyle(.borderless) #endif } } } #if os(tvOS) .padding(.trailing, 40) #endif } } .labelStyle(.iconOnly) } private var homeSettings: some View { Section(header: SettingsHeader(text: "Home".localized())) { #if !os(tvOS) if !accounts.isEmpty { Toggle("Show Home", isOn: $showHome) } #endif Toggle("Show Open Videos quick actions", isOn: $showOpenActionsInHome) Toggle("Show Next in Queue", isOn: $showQueueInHome) if !accounts.isEmpty { Toggle("Show Favorites", isOn: $showFavoritesInHome) } } } } struct FavoriteItemLabel: View { var item: FavoriteItem var body: some View { Text(label) .fontWeight(.bold) } var label: String { switch item.section { case let .playlist(_, id): return PlaylistsModel.shared.find(id: id)?.title ?? "Playlist".localized() default: return item.section.label.localized() } } } struct FavoriteItemEditor: View { var item: FavoriteItem private var model: FavoritesModel { .shared } @State private var listingStyle = WidgetListingStyle.horizontalCells @State private var limit = 3 @State private var presentingRemoveAlert = false @Default(.favorites) private var favorites var body: some View { VStack(alignment: .leading) { HStack { FavoriteItemLabel(item: item) Spacer() HStack(spacing: 10) { FavoriteItemEditorButton(color: model.canMoveUp(item) ? .accentColor : .secondary) { Label("Move Up", systemImage: "arrow.up") } onTapGesture: { model.moveUp(item) } FavoriteItemEditorButton(color: model.canMoveDown(item) ? .accentColor : .secondary) { Label("Move Down", systemImage: "arrow.down") } onTapGesture: { model.moveDown(item) } FavoriteItemEditorButton(color: .init("AppRedColor")) { Label("Remove", systemImage: "trash") } onTapGesture: { presentingRemoveAlert = true } .alert(isPresented: $presentingRemoveAlert) { Alert( title: Text( String( format: "Are you sure you want to remove %@ from Favorites?".localized(), item.section.label.localized() ) ), message: Text("This cannot be reverted"), primaryButton: .destructive(Text("Remove")) { model.remove(item) }, secondaryButton: .cancel() ) } } } listingStylePicker .padding(.vertical, 5) limitInput #if !os(iOS) Divider() #endif } .onAppear(perform: setupEditor) #if !os(tvOS) .buttonStyle(.borderless) #endif } var listingStylePicker: some View { Picker("Listing Style", selection: $listingStyle) { Text("Cells").tag(WidgetListingStyle.horizontalCells) Text("List").tag(WidgetListingStyle.list) } .onChange(of: listingStyle) { newValue in model.setListingStyle(newValue, item) limit = min(limit, WidgetSettings.maxLimit(newValue)) } .labelsHidden() .pickerStyle(.segmented) } var limitInput: some View { HStack { Text("Limit") Spacer() #if !os(tvOS) limitMinusButton .disabled(limit == 1) #endif #if os(tvOS) let textFieldWidth = 100.00 #else let textFieldWidth = 30.00 #endif TextField("Limit", value: $limit, formatter: NumberFormatter()) #if !os(macOS) .keyboardType(.numberPad) #endif .labelsHidden() .frame(width: textFieldWidth, alignment: .trailing) .multilineTextAlignment(.center) .onChange(of: limit) { newValue in let value = min(limit, WidgetSettings.maxLimit(listingStyle)) if newValue <= 0 || newValue != value { limit = value } else { model.setLimit(value, item) } } #if !os(tvOS) limitPlusButton .disabled(limit == WidgetSettings.maxLimit(listingStyle)) #endif } } #if !os(tvOS) var limitMinusButton: some View { FavoriteItemEditorButton { Label("Minus", systemImage: "minus") } onTapGesture: { limit = max(1, limit - 1) } } var limitPlusButton: some View { FavoriteItemEditorButton { Label("Plus", systemImage: "plus") } onTapGesture: { limit = max(1, limit + 1) } } #endif func setupEditor() { listingStyle = model.listingStyle(item) limit = model.limit(item) } } struct FavoriteItemEditorButton: View { var color = Color.accentColor var label: LabelView var onTapGesture: () -> Void = {} init( color: Color = .accentColor, @ViewBuilder label: () -> LabelView, onTapGesture: @escaping () -> Void = {} ) { self.color = color self.label = label() self.onTapGesture = onTapGesture } var body: some View { #if os(tvOS) Button(action: onTapGesture) { label } #else label .imageScale(.medium) .labelStyle(.iconOnly) .padding(7) .frame(minWidth: 40, minHeight: 40) .foregroundColor(color) .accessibilityAddTraits(.isButton) #if os(iOS) .background(RoundedRectangle(cornerRadius: 4).strokeBorder(lineWidth: 1).foregroundColor(color)) #endif .contentShape(Rectangle()) .onTapGesture(perform: onTapGesture) #endif } } struct HomeSettings_Previews: PreviewProvider { static var previews: some View { HomeSettings() .injectFixtureEnvironmentObjects() } }