Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions HackerTube/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ struct ContentView: View {
ConferencesView()
}

Tab("Settings", systemImage: "gear") {
SettingsView()
}

Tab("Search", systemImage: "magnifyingglass", role: .search) {
SearchView()
}
Expand Down
15 changes: 14 additions & 1 deletion HackerTube/Domain/UserDefaultsKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,18 @@
import Foundation

enum UserDefaultsKey: String {
case playbackRate = "playbackRate"
case playbackRate
case lowDataModeEnabled
}

extension UserDefaults {
@objc dynamic var playbackRate: Float {
get { float(forKey: UserDefaultsKey.playbackRate.rawValue) }
set { setValue(newValue, forKey: UserDefaultsKey.playbackRate.rawValue) }
}

@objc dynamic var lowDataModeEnabled: Bool {
get { bool(forKey: UserDefaultsKey.lowDataModeEnabled.rawValue) }
set { setValue(newValue, forKey: UserDefaultsKey.lowDataModeEnabled.rawValue) }
}
}
49 changes: 49 additions & 0 deletions HackerTube/Features/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// SettingsView.swift
// HackerTube
//
// Created by Mathijs Bernson on 02/03/2026.
//

import SwiftUI
import AVKit

enum RecordingQuality: String {
case hd, sd
}

struct SettingsView: View {
@AppStorage(UserDefaultsKey.lowDataModeEnabled.rawValue)
var lowDataModeEnabled = false

@AppStorage(UserDefaultsKey.playbackRate.rawValue)
var playbackRate: Double = 1.0
let playbackRates: [Double] = AVPlaybackSpeed.systemDefaultSpeeds.map(\.rate).map(Double.init)

var body: some View {
NavigationStack {
List {
Section("Playback") {
Picker("Playback speed", selection: $playbackRate) {
ForEach(playbackRates, id: \.self) { playbackRate in
Text("\(playbackRate, format: .number) ×", comment: "Playback rate").tag(playbackRate)
}
}
}

Section {
Toggle("Low data mode", isOn: $lowDataModeEnabled)
} header: {
Text("Streaming")
} footer: {
Text("When low data mode is on, HackerTube will play standard definition (SD) video instead of HD when possible.")
}
}
.navigationTitle("Settings")
}
}
}

#Preview {
SettingsView()
}
15 changes: 14 additions & 1 deletion HackerTube/Features/VideoPlayer/VideoPlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import AVKit
import Combine
import SwiftUI
import os.log

Expand Down Expand Up @@ -59,15 +60,27 @@ class VideoPlayerCoordinator: NSObject, AVPlayerViewControllerDelegate {

class VideoPlayerViewController: AVPlayerViewController {
private var selectedSpeedObserver: NSKeyValueObservation?
private var cancelable: AnyCancellable?

override func viewDidLoad() {
super.viewDidLoad()

let userDefaults = UserDefaults.standard

selectedSpeedObserver = self.observe(\.selectedSpeed, options: [.new]) { controller, change in
if let selectedSpeed = change.newValue.flatMap({ $0 }) {
UserDefaults.standard.set(selectedSpeed.rate, forKey: UserDefaultsKey.playbackRate.rawValue)
userDefaults.set(selectedSpeed.rate, forKey: UserDefaultsKey.playbackRate.rawValue)
}
}

cancelable = userDefaults.publisher(for: \.playbackRate)
.sink(receiveValue: { [weak self] newValue in
guard let player = self?.player else { return }

if newValue != player.rate {
player.rate = newValue
}
})
}

override func viewDidAppear(_ animated: Bool) {
Expand Down
1 change: 1 addition & 0 deletions HackerTube/HackerTubeAppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class HackerTubeAppDelegate: NSObject, UIApplicationDelegate {
private func registerDefaultSettings() {
UserDefaults.standard.register(defaults: [
UserDefaultsKey.playbackRate.rawValue : 1.0 as Float,
UserDefaultsKey.lowDataModeEnabled.rawValue : false,
])
}
}
54 changes: 53 additions & 1 deletion HackerTube/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"sourceLanguage" : "en",
"strings" : {
"%@ ×" : {
"comment" : "Playback rate",
"shouldTranslate" : false
},
"%lld views" : {
"comment" : "A label displaying the number of views for a talk.",
"localizations" : {
Expand Down Expand Up @@ -147,6 +151,10 @@
}
}
},
"Low data mode" : {
"comment" : "A toggle switch that allows the user to enable or disable low data mode.",
"isCommentAutoGenerated" : true
},
"No copyright information encoded in video. Please refer to the website of the organizer of %@ at: %@" : {
"comment" : "A label that is shown when no copyright information is available for a talk.",
"localizations" : {
Expand Down Expand Up @@ -199,6 +207,30 @@
}
}
},
"Playback" : {
"comment" : "A section header in the settings view related to playback preferences.",
"isCommentAutoGenerated" : true,
"localizations" : {
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afspelen"
}
}
}
},
"Playback speed" : {
"comment" : "A picker for selecting the playback speed of video in the settings view.",
"isCommentAutoGenerated" : true,
"localizations" : {
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afspeelsnelheid"
}
}
}
},
"Popular" : {
"comment" : "Heading for the popular talks view and tab.",
"localizations" : {
Expand Down Expand Up @@ -285,6 +317,26 @@
}
}
},
"Settings" : {
"comment" : "The title of the settings tab in the main view.",
"isCommentAutoGenerated" : true,
"localizations" : {
"nl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Instellingen"
}
}
}
},
"Streaming" : {
"comment" : "A heading for the streaming settings.",
"isCommentAutoGenerated" : true
},
"When low data mode is on, HackerTube will play standard definition (SD) video instead of HD when possible." : {
"comment" : "A footer text in the settings view that explains the functionality of low data mode.",
"isCommentAutoGenerated" : true
},
"Year" : {
"comment" : "A label that indicates the year to filter talks by.",
"isCommentAutoGenerated" : true,
Expand All @@ -304,5 +356,5 @@
}
}
},
"version" : "1.0"
"version" : "1.1"
}