diff --git a/Tiny/App/tinyApp.swift b/Tiny/App/tinyApp.swift index 2e41636..550ddd8 100644 --- a/Tiny/App/tinyApp.swift +++ b/Tiny/App/tinyApp.swift @@ -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() @@ -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) } } diff --git a/Tiny/Core/Components/BokehEffectView.swift b/Tiny/Core/Components/BokehEffectView.swift index 2142cfe..241457b 100644 --- a/Tiny/Core/Components/BokehEffectView.swift +++ b/Tiny/Core/Components/BokehEffectView.swift @@ -9,6 +9,7 @@ import SwiftUI internal import Combine struct BokehEffectView: View { + @EnvironmentObject var themeManager: ThemeManager @Binding var amplitude: Float private var pulseOpacity: Double { @@ -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) @@ -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) diff --git a/Tiny/Core/Components/Orb/Models/OrbStyles.swift b/Tiny/Core/Components/Orb/Models/OrbStyles.swift index 043548a..dde91d7 100644 --- a/Tiny/Core/Components/Orb/Models/OrbStyles.swift +++ b/Tiny/Core/Components/Orb/Models/OrbStyles.swift @@ -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") } } diff --git a/Tiny/Core/Components/Orb/Views/AnimatedOrbView.swift b/Tiny/Core/Components/Orb/Views/AnimatedOrbView.swift index 6883520..10de624 100644 --- a/Tiny/Core/Components/Orb/Views/AnimatedOrbView.swift +++ b/Tiny/Core/Components/Orb/Views/AnimatedOrbView.swift @@ -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 { @@ -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), diff --git a/Tiny/Core/Components/Orb/Views/OrbStylePicker.swift b/Tiny/Core/Components/Orb/Views/OrbStylePicker.swift index 7e8bfce..01d245a 100644 --- a/Tiny/Core/Components/Orb/Views/OrbStylePicker.swift +++ b/Tiny/Core/Components/Orb/Views/OrbStylePicker.swift @@ -56,7 +56,7 @@ struct OrbStylePicker: View { } #Preview { - @Previewable @State var selectedStyle: OrbStyles = .ocean + @Previewable @State var selectedStyle: OrbStyles = .yellow VStack { OrbStylePicker(selectedStyle: $selectedStyle) diff --git a/Tiny/Core/Components/SplashScreenView.swift b/Tiny/Core/Components/SplashScreenView.swift index 781c59b..8033d5b 100644 --- a/Tiny/Core/Components/SplashScreenView.swift +++ b/Tiny/Core/Components/SplashScreenView.swift @@ -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() @@ -39,5 +43,6 @@ struct SplashScreenView: View { #Preview { SplashScreenView(isShowingSplashScreen: .constant(true)) + .environmentObject(ThemeManager()) .preferredColorScheme(.dark) } diff --git a/Tiny/Core/Components/Theme/Models/BackgroundTheme.swift b/Tiny/Core/Components/Theme/Models/BackgroundTheme.swift new file mode 100644 index 0000000..9445c06 --- /dev/null +++ b/Tiny/Core/Components/Theme/Models/BackgroundTheme.swift @@ -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" + } + } +} diff --git a/Tiny/Core/Services/Theme/ThemeManager.swift b/Tiny/Core/Services/Theme/ThemeManager.swift new file mode 100644 index 0000000..fa3a233 --- /dev/null +++ b/Tiny/Core/Services/Theme/ThemeManager.swift @@ -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) + } +} diff --git a/Tiny/Features/Authentication/Views/NameInputView.swift b/Tiny/Features/Authentication/Views/NameInputView.swift index b5e95b1..f70b0f8 100644 --- a/Tiny/Features/Authentication/Views/NameInputView.swift +++ b/Tiny/Features/Authentication/Views/NameInputView.swift @@ -8,6 +8,7 @@ import SwiftUI struct NameInputView: View { + @EnvironmentObject var themeManager: ThemeManager @EnvironmentObject var authService: AuthenticationService let selectedRole: UserRole let onContinue: () -> Void @@ -28,7 +29,8 @@ struct NameInputView: View { var body: some View { ZStack { - Image("backgroundPurple") + Color.black.ignoresSafeArea() + Image(themeManager.selectedBackground.imageName) .resizable() .scaledToFill() .clipped() diff --git a/Tiny/Features/Authentication/Views/RoleSelectionView.swift b/Tiny/Features/Authentication/Views/RoleSelectionView.swift index f00146c..571917d 100644 --- a/Tiny/Features/Authentication/Views/RoleSelectionView.swift +++ b/Tiny/Features/Authentication/Views/RoleSelectionView.swift @@ -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() diff --git a/Tiny/Features/Authentication/Views/RoomCodeInputView.swift b/Tiny/Features/Authentication/Views/RoomCodeInputView.swift index 2f738c3..7336905 100644 --- a/Tiny/Features/Authentication/Views/RoomCodeInputView.swift +++ b/Tiny/Features/Authentication/Views/RoomCodeInputView.swift @@ -8,6 +8,7 @@ import SwiftUI struct RoomCodeInputView: View { + @EnvironmentObject var themeManager: ThemeManager @EnvironmentObject var authService: AuthenticationService @State private var roomCode: String = "" @@ -26,7 +27,8 @@ struct RoomCodeInputView: View { var body: some View { ZStack { - Image("backgroundPurple") + Color.black.ignoresSafeArea() + Image(themeManager.selectedBackground.imageName) .resizable() .scaledToFill() .clipped() diff --git a/Tiny/Features/Authentication/Views/SignInView.swift b/Tiny/Features/Authentication/Views/SignInView.swift index 93c0da2..1f8ba0e 100644 --- a/Tiny/Features/Authentication/Views/SignInView.swift +++ b/Tiny/Features/Authentication/Views/SignInView.swift @@ -9,6 +9,7 @@ import SwiftUI import AuthenticationServices struct SignInView: View { + @EnvironmentObject var themeManager: ThemeManager @EnvironmentObject var authService: AuthenticationService @State private var errorMessage: String? @@ -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() diff --git a/Tiny/Features/Customization/Views/ThemeCustomizationView.swift b/Tiny/Features/Customization/Views/ThemeCustomizationView.swift new file mode 100644 index 0000000..8efc821 --- /dev/null +++ b/Tiny/Features/Customization/Views/ThemeCustomizationView.swift @@ -0,0 +1,213 @@ +// +// ThemeCustomizationView.swift +// Tiny +// +// Created by Benedictus Yogatama Favian Satyajati on 28/11/25. +// + +import SwiftUI + +struct ThemeCustomizationView: View { + @EnvironmentObject var themeManager: ThemeManager + + @State private var selectedTab: CustomizationTab = .sphere + + enum CustomizationTab: String, CaseIterable { + case sphere = "Sphere" + case background = "Background" + } + + var body: some View { + ZStack { + // Background + Color.black.ignoresSafeArea() + Image(themeManager.selectedBackground.imageName) + .resizable() + .ignoresSafeArea() + .opacity(1) + + VStack(spacing: 0) { + // Preview Orb - Centered in remaining space + Spacer() + + ZStack { + AnimatedOrbView(size: 240) + .environmentObject(themeManager) + + BokehEffectView(amplitude: .constant(0.6)) + .environmentObject(themeManager) + } + .frame(width: 240, height: 240) + + Spacer() + + // Bottom Sheet + VStack(spacing: 0) { + // Segmented Control + HStack(spacing: 0) { + ForEach(CustomizationTab.allCases, id: \.self) { tab in + Button(action: { + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + selectedTab = tab + } + }, label: { + Text(tab.rawValue) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(selectedTab == tab ? .white : .tinyViolet) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background( + RoundedRectangle(cornerRadius: 20) + .fill(selectedTab == tab ? Color.tinyViolet : Color.clear) + ) + }) + .buttonStyle(.plain) + } + } + .padding(6) + .background( + RoundedRectangle(cornerRadius: 24) + .fill(Color.white.opacity(0.1)) + ) + .padding(.horizontal, 24) + .padding(.top, 24) + + // Options - Horizontal scroll with selected item prominent + ScrollViewReader { proxy in + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 20) { + if selectedTab == .sphere { + ForEach(OrbStyles.allCases) { style in + OrbOptionButton( + style: style, + isSelected: themeManager.selectedOrbStyle == style, + action: { + withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) { + themeManager.selectedOrbStyle = style + } + } + ) + .id(style.id) + } + } else { + ForEach(BackgroundTheme.allCases) { background in + BackgroundOptionButton( + background: background, + isSelected: themeManager.selectedBackground == background, + action: { + withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) { + themeManager.selectedBackground = background + } + } + ) + .id(background.id) + } + } + } + .padding(.horizontal, 24) + .padding(.vertical, 30) + } + .onChange(of: themeManager.selectedOrbStyle) { _, newValue in + withAnimation { + proxy.scrollTo(newValue.id, anchor: .center) + } + } + .onChange(of: themeManager.selectedBackground) { _, newValue in + withAnimation { + proxy.scrollTo(newValue.id, anchor: .center) + } + } + } + .frame(height: 240) + } + .background( + RoundedRectangle(cornerRadius: 32) + .fill(Color.white.opacity(0.05)) + .background( + RoundedRectangle(cornerRadius: 32) + .fill(.ultraThinMaterial.opacity(0.07)) + ) + .ignoresSafeArea(.all) + ) + .padding(.bottom, 0) + } + } + .navigationTitle("Theme") + .navigationBarTitleDisplayMode(.inline) + .preferredColorScheme(.dark) + } +} + +// Orb Option Button - Selected is MUCH larger +struct OrbOptionButton: View { + let style: OrbStyles + let isSelected: Bool + let action: () -> Void + + var body: some View { + Button(action: action) { + ZStack { + // The orb itself + AnimatedOrbView(size: isSelected ? 120 : 90, style: style) + + // Subtle glow for selected + if isSelected { + Circle() + .fill(style.glowColor.opacity(0.2)) + .frame(width: 160, height: 160) + .blur(radius: 20) + } + } + .frame(width: isSelected ? 160 : 100, height: isSelected ? 160 : 100) + .opacity(isSelected ? 1 : 0.3) + } + .buttonStyle(.plain) + .animation(.spring(response: 0.4, dampingFraction: 0.7), value: isSelected) + } +} + +// Background Option Button - Selected is MUCH larger +struct BackgroundOptionButton: View { + let background: BackgroundTheme + let isSelected: Bool + let action: () -> Void + + var body: some View { + Button(action: action) { + ZStack { + // Background preview circle + Circle() + .fill( + ImagePaint( + image: Image(background.imageName), + scale: isSelected ? 0.25 : 0.35 + ) + ) + .frame(width: isSelected ? 120 : 90, height: isSelected ? 120 : 90) + + // Border for selected + if isSelected { + Circle() + .stroke(Color.white.opacity(0.6), lineWidth: 3) + .frame(width: isSelected ? 120 : 95, height: isSelected ? 120 : 95) + } + + // Subtle outer glow + if isSelected { + Circle() + .stroke(Color.white.opacity(0.2), lineWidth: 1) + .frame(width: 120, height: 120) + .blur(radius: 10) + } + } + .frame(width: isSelected ? 120 : 100, height: isSelected ? 120 : 100) + } + .buttonStyle(.plain) + .animation(.spring(response: 0.4, dampingFraction: 0.7), value: isSelected) + } +} + +#Preview { + ThemeCustomizationView() + .environmentObject(ThemeManager()) +} diff --git a/Tiny/Features/LiveListen/Views/OrbLiveListenView.swift b/Tiny/Features/LiveListen/Views/OrbLiveListenView.swift index b779532..4eb2b7c 100644 --- a/Tiny/Features/LiveListen/Views/OrbLiveListenView.swift +++ b/Tiny/Features/LiveListen/Views/OrbLiveListenView.swift @@ -2,11 +2,13 @@ import SwiftUI import SwiftData struct OrbLiveListenView: View { + @State private var showThemeCustomization = false + @EnvironmentObject var themeManager: ThemeManager @Environment(\.modelContext) private var modelContext - + @ObservedObject var heartbeatSoundManager: HeartbeatSoundManager @Binding var showTimeline: Bool - + @StateObject private var viewModel = OrbLiveListenViewModel() @StateObject private var tutorialViewModel = TutorialViewModel() @@ -39,6 +41,10 @@ struct OrbLiveListenView: View { ShareSheet(activityItems: [lastRecordingURL]) } } + .sheet(isPresented: $showThemeCustomization) { + ThemeCustomizationView() + .environmentObject(themeManager) + } .preferredColorScheme(.dark) .onAppear { tutorialViewModel.showInitialTutorialIfNeeded() @@ -50,7 +56,7 @@ struct OrbLiveListenView: View { private var backgroundView: some View { ZStack { Color.black.ignoresSafeArea() - Image("backgroundPurple") + Image(themeManager.selectedBackground.imageName) .resizable() .scaleEffect(viewModel.isListening ? 1.2 : 1.0) .animation(.easeInOut(duration: 1.2), value: viewModel.isListening) @@ -83,7 +89,7 @@ struct OrbLiveListenView: View { VStack { HStack { Spacer() - + if viewModel.isPlaybackMode { Button { viewModel.showShareSheet = true @@ -151,8 +157,8 @@ struct OrbLiveListenView: View { VStack(spacing: 8) { Text(viewModel.audioPostProcessingManager.isPlaying ? "Playing..." : (viewModel.isDraggingToSave ? "Drag to save" : "Tap orb to play")) - .font(.title2) - .fontWeight(.medium) + .font(.title2) + .fontWeight(.medium) if viewModel.audioPostProcessingManager.duration > 0 && !viewModel.isDraggingToSave { Text("\(Int(viewModel.currentTime))s / \(Int(viewModel.audioPostProcessingManager.duration))s") @@ -247,6 +253,31 @@ struct OrbLiveListenView: View { } } } + + private var themeButton: some View { + VStack { + HStack { + if !viewModel.isListening && !viewModel.isPlaybackMode { + Button(action: { + showThemeCustomization = true + }, label: { + Image(systemName: "paintbrush.fill") + .font(.system(size: 20, weight: .semibold)) + .foregroundColor(.white) + .frame(width: 50, height: 50) + .background(Circle().fill(Color.white.opacity(0.1))) + .clipShape(Circle()) + }) + .padding(.leading, 16) + .padding(.top, 50) + .transition(.opacity.animation(.easeInOut)) + } + Spacer() + } + Spacer() + } + .allowsHitTesting(true) // Ensure button is tappable + } struct GestureModifier: ViewModifier { let isPlaybackMode: Bool @@ -255,9 +286,9 @@ struct OrbLiveListenView: View { let handleDragEnd: (SequenceGesture.Value) -> Void let handleLongPressChange: (Bool) -> Void let handleLongPressComplete: () -> Void - + @GestureState private var isDetectingLongPress = false - + func body(content: Content) -> some View { if isPlaybackMode { content.gesture( diff --git a/Tiny/Features/Profile/Views/ProfileView.swift b/Tiny/Features/Profile/Views/ProfileView.swift index 446f451..4dd91a8 100644 --- a/Tiny/Features/Profile/Views/ProfileView.swift +++ b/Tiny/Features/Profile/Views/ProfileView.swift @@ -14,6 +14,7 @@ struct ProfileView: View { @EnvironmentObject var authService: AuthenticationService @EnvironmentObject var syncManager: HeartbeatSyncManager @StateObject private var heartbeatMainViewModel = HeartbeatMainViewModel() + @EnvironmentObject var themeManager: ThemeManager @State private var showRoomCode = false @State private var isInitialized = false @@ -25,7 +26,7 @@ struct ProfileView: View { var body: some View { ZStack { - Image("backgroundPurple") + Image(themeManager.selectedBackground.imageName) .resizable() .scaledToFill() .ignoresSafeArea() @@ -133,7 +134,7 @@ struct ProfileView: View { private var settingsSection: some View { Section { - NavigationLink(destination: ThemeDummy()) { + NavigationLink(destination: ThemeCustomizationView()) { Label("Theme", systemImage: "paintpalette.fill") .foregroundStyle(.white) } diff --git a/Tiny/Resources/Assets.xcassets/backgrounds/Contents.json b/Tiny/Resources/Assets.xcassets/backgrounds/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/backgrounds/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/backgrounds/bgBlack.imageset/Contents.json b/Tiny/Resources/Assets.xcassets/backgrounds/bgBlack.imageset/Contents.json new file mode 100644 index 0000000..cba26f5 --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/backgrounds/bgBlack.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bgBlack.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/backgrounds/bgBlack.imageset/bgBlack.png b/Tiny/Resources/Assets.xcassets/backgrounds/bgBlack.imageset/bgBlack.png new file mode 100644 index 0000000..f86b6ab Binary files /dev/null and b/Tiny/Resources/Assets.xcassets/backgrounds/bgBlack.imageset/bgBlack.png differ diff --git a/Tiny/Resources/Assets.xcassets/backgrounds/bgBlue.imageset/Contents.json b/Tiny/Resources/Assets.xcassets/backgrounds/bgBlue.imageset/Contents.json new file mode 100644 index 0000000..1c0f20e --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/backgrounds/bgBlue.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bgBlue.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/backgrounds/bgBlue.imageset/bgBlue.png b/Tiny/Resources/Assets.xcassets/backgrounds/bgBlue.imageset/bgBlue.png new file mode 100644 index 0000000..0f75987 Binary files /dev/null and b/Tiny/Resources/Assets.xcassets/backgrounds/bgBlue.imageset/bgBlue.png differ diff --git a/Tiny/Resources/Assets.xcassets/backgrounds/bgPink.imageset/Contents.json b/Tiny/Resources/Assets.xcassets/backgrounds/bgPink.imageset/Contents.json new file mode 100644 index 0000000..5c0df9f --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/backgrounds/bgPink.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bgPink.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/backgrounds/bgPink.imageset/bgPink.png b/Tiny/Resources/Assets.xcassets/backgrounds/bgPink.imageset/bgPink.png new file mode 100644 index 0000000..7622d5c Binary files /dev/null and b/Tiny/Resources/Assets.xcassets/backgrounds/bgPink.imageset/bgPink.png differ diff --git a/Tiny/Resources/Assets.xcassets/backgrounds/bgPurple.imageset/Contents.json b/Tiny/Resources/Assets.xcassets/backgrounds/bgPurple.imageset/Contents.json new file mode 100644 index 0000000..fa2aa62 --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/backgrounds/bgPurple.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bgPurple.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/backgrounds/bgPurple.imageset/bgPurple.png b/Tiny/Resources/Assets.xcassets/backgrounds/bgPurple.imageset/bgPurple.png new file mode 100644 index 0000000..e4267d8 Binary files /dev/null and b/Tiny/Resources/Assets.xcassets/backgrounds/bgPurple.imageset/bgPurple.png differ diff --git a/Tiny/Resources/Assets.xcassets/bokehColor/Contents.json b/Tiny/Resources/Assets.xcassets/bokehColor/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/bokehColor/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/bokehColor/bokehBlue.colorset/Contents.json b/Tiny/Resources/Assets.xcassets/bokehColor/bokehBlue.colorset/Contents.json new file mode 100644 index 0000000..a7d5111 --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/bokehColor/bokehBlue.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xD2", + "red" : "0x8E" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xD2", + "red" : "0x8E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/bokehColor/bokehGreen.colorset/Contents.json b/Tiny/Resources/Assets.xcassets/bokehColor/bokehGreen.colorset/Contents.json new file mode 100644 index 0000000..6151025 --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/bokehColor/bokehGreen.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x8E", + "green" : "0xFF", + "red" : "0xC1" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x8E", + "green" : "0xFF", + "red" : "0xC1" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/bokehColor/bokehPink.colorset/Contents.json b/Tiny/Resources/Assets.xcassets/bokehColor/bokehPink.colorset/Contents.json new file mode 100644 index 0000000..0b9413f --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/bokehColor/bokehPink.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x97", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x97", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/bokehColor/bokehPurple.colorset/Contents.json b/Tiny/Resources/Assets.xcassets/bokehColor/bokehPurple.colorset/Contents.json new file mode 100644 index 0000000..1c0c17d --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/bokehColor/bokehPurple.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x9C", + "red" : "0xBF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0x9C", + "red" : "0xBF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/bokehColor/bokehYellow.colorset/Contents.json b/Tiny/Resources/Assets.xcassets/bokehColor/bokehYellow.colorset/Contents.json new file mode 100644 index 0000000..23e2a6b --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/bokehColor/bokehYellow.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x6F", + "green" : "0xCF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x6F", + "green" : "0xCF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/orbColor/Contents.json b/Tiny/Resources/Assets.xcassets/orbColor/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/orbColor/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/orbColor/orbBlue.colorset/Contents.json b/Tiny/Resources/Assets.xcassets/orbColor/orbBlue.colorset/Contents.json new file mode 100644 index 0000000..e0a85ca --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/orbColor/orbBlue.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF5", + "green" : "0x77", + "red" : "0x4D" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF5", + "green" : "0x77", + "red" : "0x4D" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/orbColor/orbGreen.colorset/Contents.json b/Tiny/Resources/Assets.xcassets/orbColor/orbGreen.colorset/Contents.json new file mode 100644 index 0000000..b63dbdb --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/orbColor/orbGreen.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x28", + "green" : "0x93", + "red" : "0x47" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x28", + "green" : "0x93", + "red" : "0x47" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/orbColor/orbPink.colorset/Contents.json b/Tiny/Resources/Assets.xcassets/orbColor/orbPink.colorset/Contents.json new file mode 100644 index 0000000..84eca51 --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/orbColor/orbPink.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x91", + "green" : "0x33", + "red" : "0x9C" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x91", + "green" : "0x33", + "red" : "0x9C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/orbColor/orbPurple.colorset/Contents.json b/Tiny/Resources/Assets.xcassets/orbColor/orbPurple.colorset/Contents.json new file mode 100644 index 0000000..009c50e --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/orbColor/orbPurple.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x98", + "green" : "0x31", + "red" : "0x6C" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x98", + "green" : "0x31", + "red" : "0x6C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Assets.xcassets/orbColor/orbYellow.colorset/Contents.json b/Tiny/Resources/Assets.xcassets/orbColor/orbYellow.colorset/Contents.json new file mode 100644 index 0000000..4d9b43e --- /dev/null +++ b/Tiny/Resources/Assets.xcassets/orbColor/orbYellow.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2D", + "green" : "0x71", + "red" : "0x9A" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2D", + "green" : "0x71", + "red" : "0x9A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Tiny/Resources/Localizable.xcstrings b/Tiny/Resources/Localizable.xcstrings index 39f4a6e..90c3b07 100644 --- a/Tiny/Resources/Localizable.xcstrings +++ b/Tiny/Resources/Localizable.xcstrings @@ -73,7 +73,7 @@ "isCommentAutoGenerated" : true }, "20" : { - "comment" : "The number of weeks in a pregnancy.", + "comment" : "The number \"20\" displayed in the user interface.", "isCommentAutoGenerated" : true }, "20Hz" : { @@ -155,7 +155,7 @@ "isCommentAutoGenerated" : true }, "Change Profile Photo" : { - "comment" : "A title for the bottom sheet that appears when a user wants to change their profile photo.", + "comment" : "A title for the bottom sheet that appears when the user taps on their profile photo.", "isCommentAutoGenerated" : true }, "Confidence" : { @@ -167,7 +167,7 @@ "isCommentAutoGenerated" : true }, "Connect with Your Partner" : { - "comment" : "A call-to-action text displayed below the feature card.", + "comment" : "A button label that invites the user to connect with their partner.", "isCommentAutoGenerated" : true }, "Connect your AirPods and let Tiny access your microphone to hear every little beat." : { @@ -223,7 +223,7 @@ "isCommentAutoGenerated" : true }, "Edit Profile" : { - "comment" : "The title of the view that allows users to edit their profile.", + "comment" : "The title of the view.", "isCommentAutoGenerated" : true }, "Enhanced proximity audio" : { @@ -374,7 +374,7 @@ "isCommentAutoGenerated" : true }, "Name" : { - "comment" : "A label describing the user's name.", + "comment" : "A label displayed above the user's name field in the profile view.", "isCommentAutoGenerated" : true }, "No heartbeat detected yet" : { @@ -438,7 +438,7 @@ "isCommentAutoGenerated" : true }, "Privacy Policy" : { - "comment" : "A label for the privacy policy option in the profile settings.", + "comment" : "A link to the privacy policy.", "isCommentAutoGenerated" : true }, "Proximity Gain" : { @@ -497,7 +497,7 @@ "isCommentAutoGenerated" : true }, "Save" : { - "comment" : "The text of a button that saves changes made in the profile.", + "comment" : "The text of a button that saves changes made to a user's profile.", "isCommentAutoGenerated" : true }, "Save or Delete" : { @@ -529,7 +529,7 @@ "isCommentAutoGenerated" : true }, "Sign Out" : { - "comment" : "A button that signs the user out of their account.", + "comment" : "A button that triggers a confirmation dialog to sign out the user.", "isCommentAutoGenerated" : true }, "Signal Amplitude" : { @@ -632,11 +632,11 @@ "isCommentAutoGenerated" : true }, "Terms and Conditions" : { - "comment" : "A link to the app's terms and conditions.", + "comment" : "A link to the terms and conditions of the app.", "isCommentAutoGenerated" : true }, "Theme" : { - "comment" : "A button that allows the user to change the app's theme.", + "comment" : "The title of the view where users can customize the theme of the app.", "isCommentAutoGenerated" : true }, "Time" : { @@ -652,7 +652,7 @@ "isCommentAutoGenerated" : true }, "Tutorial" : { - "comment" : "A link to a view that displays a tutorial.", + "comment" : "A link to a view that shows a tutorial.", "isCommentAutoGenerated" : true }, "Variability" : { @@ -676,7 +676,7 @@ "isCommentAutoGenerated" : true }, "You'll need to sign in again to sync your data and access personalized features." : { - "comment" : "A message displayed in the confirmation dialog when signing out.", + "comment" : "A message displayed in a confirmation dialog when a user signs out.", "isCommentAutoGenerated" : true }, "You're connected to this room" : {