Skip to content

Commit 6853764

Browse files
committed
fix style in light/dark mode
1 parent 750bc60 commit 6853764

File tree

9 files changed

+149
-60
lines changed

9 files changed

+149
-60
lines changed

firebaseai/ChatExample/Models/ChatMessage.swift

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,28 +72,18 @@ extension ChatMessage {
7272
}
7373

7474
extension ChatMessage {
75-
// Convert ModelContent to ChatMessage
7675
static func from(_ modelContent: ModelContent) -> ChatMessage? {
77-
// Extract text from all parts
76+
// TODO: add non-text parts to message when multi-model support is added
7877
let text = modelContent.parts.compactMap { ($0 as? TextPart)?.text }.joined()
7978
guard !text.isEmpty else {
8079
return nil
8180
}
8281

83-
let participant: Participant
84-
switch modelContent.role {
85-
case "user":
86-
participant = .user
87-
case "model":
88-
participant = .system
89-
default:
90-
return nil
91-
}
82+
let participant: Participant = (modelContent.role == "user") ? .user : .system
9283

9384
return ChatMessage(message: text, participant: participant)
9485
}
9586

96-
// Convert array of ModelContent to array of ChatMessage
9787
static func from(_ modelContents: [ModelContent]) -> [ChatMessage] {
9888
return modelContents.compactMap { from($0) }
9989
}

firebaseai/ChatExample/Screens/ConversationScreen.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ struct ConversationScreen: View {
2323
@State
2424
private var userPrompt = ""
2525

26-
init(firebaseService: FirebaseAI, sampleId: UUID? = nil) {
26+
init(firebaseService: FirebaseAI, sample: Sample? = nil) {
2727
self.firebaseService = firebaseService
2828
_viewModel =
2929
StateObject(wrappedValue: ConversationViewModel(firebaseService: firebaseService,
30-
sampleId: sampleId))
30+
sample: sample))
3131
}
3232

3333
enum FocusedField: Hashable {
@@ -83,18 +83,13 @@ struct ConversationScreen: View {
8383
focusedField = nil
8484
}
8585
.toolbar {
86-
ToolbarItem(placement: .principal) {
87-
Text(viewModel.title)
88-
.font(.system(size: 24, weight: .bold))
89-
.foregroundColor(.primary)
90-
.padding(.top, 10)
91-
}
9286
ToolbarItem(placement: .primaryAction) {
9387
Button(action: newChat) {
9488
Image(systemName: "square.and.pencil")
9589
}
9690
}
9791
}
92+
.navigationTitle(viewModel.title)
9893
.onAppear {
9994
focusedField = .message
10095
// Set initial prompt from viewModel if available
@@ -131,7 +126,7 @@ struct ConversationScreen: View {
131126
struct ConversationScreen_Previews: PreviewProvider {
132127
struct ContainerView: View {
133128
@StateObject var viewModel = ConversationViewModel(firebaseService: FirebaseAI
134-
.firebaseAI()) // Example service init
129+
.firebaseAI(), sample: nil) // Example service init
135130

136131
var body: some View {
137132
ConversationScreen(firebaseService: FirebaseAI.firebaseAI())

firebaseai/ChatExample/ViewModels/ConversationViewModel.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@ class ConversationViewModel: ObservableObject {
4141

4242
private var sample: Sample?
4343

44-
init(firebaseService: FirebaseAI, sampleId: UUID? = nil) {
45-
// retrieve sample from sampleId
46-
sample = Sample.find(by: sampleId)
44+
init(firebaseService: FirebaseAI, sample: Sample? = nil) {
45+
self.sample = sample
4746

4847
// create a generative model with sample data
4948
model = firebaseService.generativeModel(

firebaseai/FirebaseAIExample/ContentView.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,20 @@ struct ContentView: View {
4949
ScrollView {
5050
VStack(alignment: .leading, spacing: 20) {
5151
// Backend Configuration
52-
GroupBox(label: Text("Backend Configuration").font(.system(size: 18, weight: .bold))) {
52+
VStack(alignment: .leading) {
53+
Text("Backend Configuration")
54+
.font(.system(size: 20, weight: .bold))
55+
.padding(.horizontal)
56+
5357
Picker("Backend", selection: $selectedBackend) {
5458
ForEach(BackendOption.allCases) { option in
55-
Text(option.rawValue).tag(option)
59+
Text(option.rawValue)
60+
.tag(option)
5661
}
5762
}
5863
.pickerStyle(SegmentedPickerStyle())
64+
.padding(.horizontal)
5965
}
60-
.padding(.horizontal)
6166

6267
// Use Case Filter
6368
VStack(alignment: .leading) {
@@ -76,6 +81,7 @@ struct ContentView: View {
7681
.padding(.horizontal)
7782
}
7883
}
84+
7985
// Samples
8086
VStack(alignment: .leading) {
8187
Text("Samples")
@@ -95,6 +101,7 @@ struct ContentView: View {
95101
}
96102
.padding(.vertical)
97103
}
104+
.background(Color(.systemGroupedBackground))
98105
.navigationTitle("Firebase AI Logic")
99106
.onChange(of: selectedBackend) { newBackend in
100107
firebaseService = newBackend.backendValue
@@ -106,9 +113,9 @@ struct ContentView: View {
106113
private func destinationView(for sample: Sample) -> some View {
107114
switch sample.navRoute {
108115
case "ConversationScreen":
109-
ConversationScreen(firebaseService: firebaseService, sampleId: sample.id)
116+
ConversationScreen(firebaseService: firebaseService, sample: sample)
110117
case "ImagenScreen":
111-
ImagenScreen(firebaseService: firebaseService, sampleId: sample.id)
118+
ImagenScreen(firebaseService: firebaseService, sample: sample)
112119
case "PhotoReasoningScreen":
113120
PhotoReasoningScreen(firebaseService: firebaseService)
114121
case "FunctionCallingScreen":

firebaseai/FirebaseAIExample/Views/FilterChipView.swift

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,33 @@ struct FilterChipView: View {
2323
var body: some View {
2424
Button(action: action) {
2525
Text(useCase.rawValue)
26-
.padding(.horizontal, 16)
27-
.padding(.vertical, 8)
28-
.background(isSelected ? Color.blue.opacity(0.8) : Color.gray.opacity(0.2))
29-
.foregroundColor(isSelected ? .white : .primary)
30-
.cornerRadius(12)
26+
.padding(.horizontal)
3127
}
28+
.filterChipStyle(isSelected: isSelected)
29+
}
30+
}
31+
32+
private struct FilterChipStyle: ViewModifier {
33+
let isSelected: Bool
34+
35+
func body(content: Content) -> some View {
36+
if isSelected {
37+
content.buttonStyle(.borderedProminent)
38+
} else {
39+
content.buttonStyle(.bordered)
40+
}
41+
}
42+
}
43+
44+
extension View {
45+
func filterChipStyle(isSelected: Bool) -> some View {
46+
modifier(FilterChipStyle(isSelected: isSelected))
47+
}
48+
}
49+
50+
#Preview {
51+
VStack(spacing: 16) {
52+
FilterChipView(useCase: .text, isSelected: true) {}
53+
FilterChipView(useCase: .text, isSelected: false) {}
3254
}
3355
}

firebaseai/FirebaseAIExample/Views/SampleCardView.swift

Lines changed: 96 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,106 @@ struct SampleCardView: View {
1919
let sample: Sample
2020

2121
var body: some View {
22-
VStack(alignment: .leading) {
23-
Text(sample.title)
24-
.font(.system(size: 17, weight: .medium))
22+
GroupBox {
2523
Text(sample.description)
2624
.font(.system(size: 14))
2725
.foregroundColor(.secondary)
28-
.padding(.top, 4)
26+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
27+
} label: {
28+
HStack {
29+
if let useCase = sample.useCases.first {
30+
Image(systemName: systemName(for: useCase))
31+
.foregroundColor(color(for: useCase))
32+
}
33+
Text(sample.title)
34+
.font(.system(size: 17, weight: .medium))
35+
}
2936
}
30-
.padding()
37+
.groupBoxStyle(CardGroupBoxStyle())
3138
.frame(maxWidth: .infinity, minHeight: 150, maxHeight: .infinity, alignment: .top)
32-
.background(Color.white)
33-
.cornerRadius(12)
34-
.shadow(radius: 3)
3539
}
40+
41+
private func systemName(for useCase: UseCase) -> String {
42+
switch useCase {
43+
case .text: "text.bubble.fill"
44+
case .image: "photo.fill"
45+
case .video: "video.fill"
46+
case .audio: "waveform"
47+
case .document: "doc.fill"
48+
case .functionCalling: "gearshape.2.fill"
49+
}
50+
}
51+
52+
private func color(for useCase: UseCase) -> Color {
53+
switch useCase {
54+
case .text:.blue
55+
case .image:.purple
56+
case .video:.red
57+
case .audio:.orange
58+
case .document:.gray
59+
case .functionCalling:.green
60+
}
61+
}
62+
}
63+
64+
public struct CardGroupBoxStyle: GroupBoxStyle {
65+
private var cornerRadius: CGFloat {
66+
if #available(iOS 26.0, *) {
67+
return 28
68+
} else {
69+
return 12
70+
}
71+
}
72+
73+
public func makeBody(configuration: Configuration) -> some View {
74+
VStack(alignment: .leading, spacing: 12) {
75+
configuration.label
76+
configuration.content
77+
}
78+
.padding()
79+
.background(Color(.secondarySystemGroupedBackground))
80+
.clipShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
81+
}
82+
}
83+
84+
#Preview {
85+
let samples = [
86+
Sample(
87+
title: "Sample 1",
88+
description: "This is the first sample card.",
89+
useCases: [.text],
90+
navRoute: "ConversationScreen"
91+
),
92+
Sample(
93+
title: "Sample 2",
94+
description: "This is the second sample card.",
95+
useCases: [.image],
96+
navRoute: "PhotoReasoningScreen"
97+
),
98+
Sample(
99+
title: "Sample 3",
100+
description: "This is the third sample card.",
101+
useCases: [.video],
102+
navRoute: "ConversationScreen"
103+
),
104+
Sample(
105+
title: "Sample 4",
106+
description: "This is the fourth sample card, which is a bit longer to see how the text wraps and if everything still aligns correctly.",
107+
useCases: [.audio],
108+
navRoute: "ConversationScreen"
109+
),
110+
]
111+
112+
ScrollView {
113+
LazyVGrid(columns: [
114+
GridItem(.flexible()),
115+
GridItem(.flexible()),
116+
], spacing: 16) {
117+
ForEach(samples) { sample in
118+
SampleCardView(sample: sample)
119+
}
120+
}
121+
.padding()
122+
}
123+
.background(Color(.systemGroupedBackground))
36124
}

firebaseai/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/Models/Sample.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import Foundation
1616
import FirebaseAI
1717

18-
public struct Sample: Identifiable {
18+
public class Sample: Identifiable {
1919
public let id = UUID()
2020
public let title: String
2121
public let description: String
@@ -43,11 +43,6 @@ public struct Sample: Identifiable {
4343
self.systemInstruction = systemInstruction
4444
self.tools = tools
4545
}
46-
47-
public static func find(by id: UUID?) -> Sample? {
48-
guard let id = id else { return nil }
49-
return samples.first { $0.id == id }
50-
}
5146
}
5247

5348
extension Sample {

firebaseai/ImagenScreen/ImagenScreen.swift

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ struct ImagenScreen: View {
2323
@State
2424
private var userPrompt = ""
2525

26-
init(firebaseService: FirebaseAI, sampleId: UUID? = nil) {
26+
init(firebaseService: FirebaseAI, sample: Sample? = nil) {
2727
self.firebaseService = firebaseService
2828
_viewModel =
2929
StateObject(wrappedValue: ImagenViewModel(firebaseService: firebaseService,
30-
sampleId: sampleId))
30+
sample: sample))
3131
}
3232

3333
enum FocusedField: Hashable {
@@ -73,14 +73,7 @@ struct ImagenScreen: View {
7373
.onTapGesture {
7474
focusedField = nil
7575
}
76-
.toolbar {
77-
ToolbarItem(placement: .principal) {
78-
Text("Imagen example")
79-
.font(.system(size: 24, weight: .bold))
80-
.foregroundColor(.primary)
81-
.padding(.top, 10)
82-
}
83-
}
76+
.navigationTitle("Imagen example")
8477
.onAppear {
8578
focusedField = .message
8679
if userPrompt.isEmpty && !viewModel.initialPrompt.isEmpty {

firebaseai/ImagenScreen/ImagenViewModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ class ImagenViewModel: ObservableObject {
4040

4141
private var sample: Sample?
4242

43-
init(firebaseService: FirebaseAI, sampleId: UUID? = nil) {
44-
sample = Sample.find(by: sampleId)
43+
init(firebaseService: FirebaseAI, sample: Sample? = nil) {
44+
self.sample = sample
4545

4646
let modelName = "imagen-3.0-generate-002"
4747
let safetySettings = ImagenSafetySettings(

0 commit comments

Comments
 (0)