Skip to content
Merged
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
3 changes: 3 additions & 0 deletions Tiny/App/tinyApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import FirebaseCore
@main
struct TinyApp: App {
@StateObject var heartbeatSoundManager = HeartbeatSoundManager()
@StateObject private var themeManager = ThemeManager()
@StateObject var authService = AuthenticationService()
@StateObject var syncManager = HeartbeatSyncManager()

Expand Down Expand Up @@ -39,12 +40,14 @@ struct TinyApp: App {
WindowGroup {
if isShowingSplashScreen {
SplashScreenView(isShowingSplashScreen: $isShowingSplashScreen)
.environmentObject(themeManager)
.preferredColorScheme(.dark)
} else {
ContentView()
.environmentObject(heartbeatSoundManager)
.environmentObject(authService)
.environmentObject(syncManager)
.environmentObject(themeManager)
.preferredColorScheme(.dark)
}
}
Expand Down
9 changes: 7 additions & 2 deletions Tiny/Core/Components/BokehEffectView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SwiftUI
internal import Combine

struct BokehEffectView: View {
@EnvironmentObject var themeManager: ThemeManager
@Binding var amplitude: Float

private var pulseOpacity: Double {
Expand All @@ -19,13 +20,17 @@ struct BokehEffectView: View {
private var pulseScale: CGFloat {
return 1.0 + CGFloat(amplitude) * 0.3
}

private var bokehColor: Color {
themeManager.selectedOrbStyle.bokehColor
}

var body: some View {
ZStack {
// Layer 1: The Base Glow (Shifted Left)
// This is the bottom layer, slightly less bright.
Circle()
.fill(Color.orbLightYellow)
.fill(bokehColor)
.frame(width: 30)
.opacity(pulseOpacity * 0.5)
.scaleEffect(pulseScale * 1)
Expand All @@ -34,7 +39,7 @@ struct BokehEffectView: View {
// Layer 2: The Core/Highlight (Shifted Right)
// *** We apply .blendMode(.screen) here to brighten the overlap ***
Circle()
.fill(Color.orbLightYellow)
.fill(bokehColor)
.frame(width: 30)
.opacity(pulseOpacity * 0.5)
.scaleEffect(pulseScale * 1.1)
Expand Down
71 changes: 50 additions & 21 deletions Tiny/Core/Components/Orb/Models/OrbStyles.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,72 @@
import SwiftUI

enum OrbStyles: String, CaseIterable, Identifiable {
case ocean = "Ocean"
case defaultStyle = "Default"
case forest = "Forest"
case yellow = "Yellow"
case pink = "Pink"
case purple = "Purple"
case blue = "Blue"
case green = "Green"

var id: String { rawValue }
var displayName: String { rawValue }

var backgorundColors: [Color] {
switch self {
case .ocean:
return [.blue, .cyan, .clear]
case .forest:
return [.green, .mint, .clear]
case .defaultStyle:
return [.orange, .orbOrange, .clear]
case .yellow:
return [Color("orbYellow"), Color("orbYellow"), .clear]
case .pink:
return [Color("orbPink"), Color("orbPink"), .clear]
case .purple:
return [Color("orbPurple"), Color("orbPurple"), .clear]
case .blue:
return [Color("orbBlue"), Color("orbBlue"), .clear]
case .green:
return [Color("orbGreen"), Color("orbGreen"), .clear]
}
}

var glowColor: Color {
switch self {
case .ocean:
return .cyan.opacity(1)
case .forest:
return .green.opacity(1)
case .defaultStyle:
return .orbOrange.opacity(1)
case .yellow:
return Color("orbYellow")
case .pink:
return Color("orbPink")
case .purple:
return Color("orbPurple")
case .blue:
return Color("orbBlue")
case .green:
return Color("orbGreen")
}
}

var particleColor: Color {
switch self {
case .ocean:
return .cyan
case .forest:
return .green
case .defaultStyle:
return .white
case .yellow:
return Color("orbYellow")
case .pink:
return Color("orbPink")
case .purple:
return Color("orbPurple")
case .blue:
return Color("orbBlue")
case .green:
return Color("orbGreen")
}
}

var bokehColor: Color {
switch self {
case .yellow:
return Color("bokehYellow")
case .pink:
return Color("bokehPink")
case .purple:
return Color("bokehPurple")
case .blue:
return Color("bokehBlue")
case .green:
return Color("bokehGreen")
}
}

Expand Down
12 changes: 7 additions & 5 deletions Tiny/Core/Components/Orb/Views/AnimatedOrbView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import SwiftUI
import SpriteKit

struct AnimatedOrbView: View {
@EnvironmentObject var themeManager: ThemeManager
@StateObject private var physicsController = OrbPhysicsController()
var size: CGFloat = 200
var style: OrbStyles = .defaultStyle
var style: OrbStyles?
private var effectiveStyle: OrbStyles { style ?? themeManager.selectedOrbStyle }

private var configuration: OrbConfiguration {
OrbConfiguration(style: style)
OrbConfiguration(style: effectiveStyle)
}

var body: some View {
Expand All @@ -29,9 +31,9 @@ struct AnimatedOrbView: View {
.stroke(
AngularGradient(
colors: [
style.glowColor.opacity(0.8),
style.glowColor.opacity(2),
style.glowColor.opacity(0.6)
effectiveStyle.glowColor.opacity(0.8),
effectiveStyle.glowColor.opacity(2),
effectiveStyle.glowColor.opacity(0.6)
],
center: .center,
startAngle: .degrees(0),
Expand Down
2 changes: 1 addition & 1 deletion Tiny/Core/Components/Orb/Views/OrbStylePicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ struct OrbStylePicker: View {
}

#Preview {
@Previewable @State var selectedStyle: OrbStyles = .ocean
@Previewable @State var selectedStyle: OrbStyles = .yellow

VStack {
OrbStylePicker(selectedStyle: $selectedStyle)
Expand Down
9 changes: 7 additions & 2 deletions Tiny/Core/Components/SplashScreenView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,23 @@
import SwiftUI

struct SplashScreenView: View {
@EnvironmentObject var themeManager: ThemeManager
@Binding var isShowingSplashScreen: Bool
@State private var animate: Bool = false

var body: some View {
ZStack {
Image("bgSplashScreen")
// Use customizable background from ThemeManager
Color.black.ignoresSafeArea()
Image(themeManager.selectedBackground.imageName)
.resizable()
.scaledToFill()
.ignoresSafeArea()

Image("titleSplashScreen")
.resizable()
.scaledToFill()
.frame(width: animate ? 100 :80, height: animate ? 100 : 80) // Animate size
.frame(width: animate ? 100 : 80, height: animate ? 100 : 80) // Animate size
.opacity(animate ? 1 : 0.5) // Animate opacity
}
.ignoresSafeArea()
Expand All @@ -39,5 +43,6 @@ struct SplashScreenView: View {

#Preview {
SplashScreenView(isShowingSplashScreen: .constant(true))
.environmentObject(ThemeManager())
.preferredColorScheme(.dark)
}
32 changes: 32 additions & 0 deletions Tiny/Core/Components/Theme/Models/BackgroundTheme.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// BackgroundTheme.swift
// Tiny
//
// Created by Benedictus Yogatama Favian Satyajati on 28/11/25.
//

import Foundation
import SwiftUI

enum BackgroundTheme: String, CaseIterable, Identifiable {
case purple = "Purple"
case pink = "Pink"
case blue = "Blue"
case black = "Black"

var id: String { rawValue }
var displayName: String { rawValue }

var imageName: String {
switch self {
case .purple:
return "bgPurple"
case .pink:
return "bgPink"
case .blue:
return "bgBlue"
case .black:
return "bgBlack"
}
}
}
51 changes: 51 additions & 0 deletions Tiny/Core/Services/Theme/ThemeManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// ThemeManager.swift
// Tiny
//
// Created by Benedictus Yogatama Favian Satyajati on 28/11/25.
//

import Foundation
import SwiftUI
internal import Combine

class ThemeManager: ObservableObject {
@Published var selectedOrbStyle: OrbStyles {
didSet {
saveOrbStyle()
}
}

@Published var selectedBackground: BackgroundTheme {
didSet {
saveBackground()
}
}

private let orbStyleKey = "selectedOrbStyle"
private let backgroundKey = "selectedBackground"

init() {
if let savedOrbStyle = UserDefaults.standard.string(forKey: orbStyleKey),
let orbStyle = OrbStyles(rawValue: savedOrbStyle) {
self.selectedOrbStyle = orbStyle
} else {
self.selectedOrbStyle = .yellow
}

if let savedBackground = UserDefaults.standard.string(forKey: backgroundKey),
let background = BackgroundTheme(rawValue: savedBackground) {
self.selectedBackground = background
} else {
self.selectedBackground = .purple
}
}

private func saveOrbStyle() {
UserDefaults.standard.set(selectedOrbStyle.rawValue, forKey: orbStyleKey)
}

private func saveBackground() {
UserDefaults.standard.set(selectedBackground.rawValue, forKey: backgroundKey)
}
}
4 changes: 3 additions & 1 deletion Tiny/Features/Authentication/Views/NameInputView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import SwiftUI

struct NameInputView: View {
@EnvironmentObject var themeManager: ThemeManager
@EnvironmentObject var authService: AuthenticationService
let selectedRole: UserRole
let onContinue: () -> Void
Expand All @@ -28,7 +29,8 @@ struct NameInputView: View {

var body: some View {
ZStack {
Image("backgroundPurple")
Color.black.ignoresSafeArea()
Image(themeManager.selectedBackground.imageName)
.resizable()
.scaledToFill()
.clipped()
Expand Down
4 changes: 3 additions & 1 deletion Tiny/Features/Authentication/Views/RoleSelectionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import SwiftUI

struct RoleSelectionView: View {
@EnvironmentObject var themeManager: ThemeManager
@EnvironmentObject var authService: AuthenticationService
@Binding var selectedRole: UserRole?
let onContinue: () -> Void

var body: some View {
ZStack {
Image("backgroundPurple")
Color.black.ignoresSafeArea()
Image(themeManager.selectedBackground.imageName)
.resizable()
.scaledToFill()
.clipped()
Expand Down
4 changes: 3 additions & 1 deletion Tiny/Features/Authentication/Views/RoomCodeInputView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import SwiftUI

struct RoomCodeInputView: View {
@EnvironmentObject var themeManager: ThemeManager
@EnvironmentObject var authService: AuthenticationService

@State private var roomCode: String = ""
Expand All @@ -26,7 +27,8 @@ struct RoomCodeInputView: View {

var body: some View {
ZStack {
Image("backgroundPurple")
Color.black.ignoresSafeArea()
Image(themeManager.selectedBackground.imageName)
.resizable()
.scaledToFill()
.clipped()
Expand Down
4 changes: 3 additions & 1 deletion Tiny/Features/Authentication/Views/SignInView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import SwiftUI
import AuthenticationServices

struct SignInView: View {
@EnvironmentObject var themeManager: ThemeManager
@EnvironmentObject var authService: AuthenticationService
@State private var errorMessage: String?

Expand All @@ -24,7 +25,8 @@ struct SignInView: View {

var body: some View {
ZStack(alignment: .center) {
Image("backgroundPurple")
Color.black.ignoresSafeArea()
Image(themeManager.selectedBackground.imageName)
.resizable()
.scaledToFill()
.clipped()
Expand Down
Loading