Last active
April 7, 2025 07:39
-
-
Save mireabot/075a3d44f79ea6d41669885892975e4e to your computer and use it in GitHub Desktop.
Revisions
-
mireabot revised this gist
Mar 9, 2025 . 1 changed file with 25 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -75,11 +75,11 @@ struct FinancialHealthSheet: View { let score: Double @State private var currentScore: Double = 0 let scoreRanges: [ClosedRange<Double>: Color] = [ 0...30: .red, // Bad 31...50: .yellow, // Fair 51...80: .blue, // Good 81...100: .green // Great ] let labels = [ @@ -124,7 +124,7 @@ struct FinancialHealthSheet: View { } VStack(alignment: .leading, spacing: 10) { SegmentedProgressBar(score: currentScore, scoreRanges: scoreRanges) .frame(height: 16) HStack(spacing: 0) { @@ -191,7 +191,18 @@ extension FinancialHealthSheet { // Custom segmented progress bar struct SegmentedProgressBar: View { let score: Double let scoreRanges: [ClosedRange<Double>: Color] private var indicatorColor: Color { scoreRanges.first { $0.key.contains(score) }?.value ?? .gray } private var segmentColors: [Color] { scoreRanges.sorted { $0.key.lowerBound < $1.key.lowerBound }.flatMap { range, color in let count = Int((range.upperBound - range.lowerBound) / 10) return Array(repeating: color, count: max(1, count)) } } var body: some View { GeometryReader { geometry in @@ -206,8 +217,8 @@ extension FinancialHealthSheet { // Score indicator GeometryReader { geo in Circle() .fill(indicatorColor) .stroke(.white, lineWidth: 2) .frame(width: 32, height: 32) .position(x: (geo.size.width * score / 100), y: geo.size.height / 2) } @@ -245,4 +256,9 @@ struct RoundedCorner: Shape { ) return Path(path.cgPath) } var animatableData: CGFloat { get { radius } set { radius = newValue } } } -
mireabot revised this gist
Mar 9, 2025 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -206,8 +206,8 @@ extension FinancialHealthSheet { // Score indicator GeometryReader { geo in Circle() .fill(.white) .stroke(.gray.opacity(0.2), lineWidth: 2) .frame(width: 32, height: 32) .position(x: (geo.size.width * score / 100), y: geo.size.height / 2) } -
mireabot created this gist
Mar 8, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,248 @@ import SwiftUI struct FinancialHealthSheetPreview: View { @State private var showFinancialHealthSheet = false var body: some View { VStack { Button(action: { showFinancialHealthSheet.toggle() }, label: { HStack { Text("Show Financial Health Sheet") Spacer() Image(systemName: "arrow.up.right") } }) .buttonStyle(BorderedCapsuledButtonStyle()) } .padding(.horizontal, 16) .floatingSheet(isPresented: $showFinancialHealthSheet, content: { FinancialHealthSheet(score: 79) }) } } // MARK: - Extensions extension FinancialHealthSheetPreview { struct BorderedCapsuledButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .padding(16) .background( Capsule() .stroke(Color(uiColor: .systemGray5), lineWidth: 1) ) } } } // MARK: - Previews #Preview { FinancialHealthSheetPreview() } // MARK: - Financial Health Sheet Base struct FinancialHealthSheetBase { var maxDetent: PresentationDetent var cornerRadius: CGFloat = 20 var interactiveDimiss: Bool = false var hPadding: CGFloat = 20 var bPadding: CGFloat = 20 } extension View { @ViewBuilder func floatingSheet<Content: View>(isPresented: Binding<Bool>, config: FinancialHealthSheetBase = .init(maxDetent: .fraction(0.99)), @ViewBuilder content: @escaping () -> Content) -> some View { self .sheet(isPresented: isPresented) { content() .background(Color(uiColor: .systemBackground)) .clipShape(.rect(cornerRadius: config.cornerRadius)) .padding(.horizontal, config.hPadding) .padding(.bottom, config.bPadding) .frame(maxHeight: .infinity, alignment: .bottom) .presentationDetents([config.maxDetent]) .presentationCornerRadius(0) .presentationBackground(.clear.blendMode(.darken)) .presentationDragIndicator(.hidden) .interactiveDismissDisabled(config.interactiveDimiss) } } } // MARK: - Financial Health Sheet View struct FinancialHealthSheet: View { let score: Double @State private var currentScore: Double = 0 let segmentColors: [Color] = [ .red, .red, .red, // Bad (0-30) .yellow, .yellow, .yellow, // Fair (30-50) .blue, .blue, .blue, // Good (50-80) .green, .green // Great (80-100) ] let labels = [ (text: "Bad", color: Color.red), (text: "Fair", color: Color.yellow), (text: "Good", color: Color.blue), (text: "Great", color: Color.green) ] // Background icons let icons = ["chart.pie.fill", "person.fill", "plus", "heart.fill", "star.fill", "line.3.horizontal"] var body: some View { ZStack(alignment: .top) { LazyHGrid(rows: Array(repeating: GridItem(.fixed(50)), count: 2), spacing: 20) { ForEach(0..<12) { index in Image(systemName: icons[index % icons.count]) .font(.system(size: 24)) .foregroundColor(.gray.opacity(0.7)) .frame(height: 25) .padding(8) .background(.gray.opacity(0.15)) .cornerRadius(10) } } .fixedSize(horizontal: true, vertical: true) .blur(radius: 1.7) VStack(spacing: 32) { VStack(spacing: 6) { Text("Financial health:") .font(.system(.subheadline, weight: .medium)) VStack(spacing: 3) { Text("\(Int(score))") .font(.system(size: 72, weight: .bold)) Text("out of 100") .font(.title3) .foregroundColor(.secondary) } } VStack(alignment: .leading, spacing: 10) { SegmentedProgressBar(score: currentScore, segmentColors: segmentColors) .frame(height: 16) HStack(spacing: 0) { ForEach(labels, id: \.text) { label in Text(label.text) .font(.caption) .foregroundColor(.secondary) .frame(maxWidth: .infinity) } } } .padding(.top, 10) infoView() .padding(.bottom, 16) } .padding(.top, 20) .padding(.horizontal, 16) } .onAppear { withAnimation(.spring(duration: 2)) { currentScore = score } } } @ViewBuilder func infoView() -> some View { VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 6) { Text("Why?") .font(.system(.subheadline, weight: .medium)) .foregroundStyle(.primary) Text("Savings exceed goals and spending is intentional") .font(.system(.subheadline, weight: .regular)) .foregroundStyle(.secondary) .multilineTextAlignment(.leading) } .frame(maxWidth: .infinity, alignment: .leading) Divider().foregroundColor(Color.gray.opacity(0.2)).padding(.vertical, 8) VStack(alignment: .leading, spacing: 6) { Text("Recommendation:") .font(.system(.subheadline, weight: .medium)) .foregroundStyle(.primary) Text("Stay consistent with your budget, spending and saving habits") .font(.system(.subheadline, weight: .regular)) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) } .padding(.horizontal, 12) .padding(.vertical, 14) .overlay { RoundedRectangle(cornerRadius: 10) .stroke(Color.gray.opacity(0.2), lineWidth: 1) } } } // MARK: - FinancialHealthSheet Extenstions extension FinancialHealthSheet { // Custom segmented progress bar struct SegmentedProgressBar: View { let score: Double let segmentColors: [Color] var body: some View { GeometryReader { geometry in HStack(spacing: 4) { ForEach(0..<segmentColors.count, id: \.self) { index in Rectangle() .fill(segmentColors[index]) .cornerRadius(4, corners: getCornerRadius(for: index)) } } .overlay { // Score indicator GeometryReader { geo in Circle() .fill(Color(hex: "#38CA84")) .stroke(.white, lineWidth: 2) .frame(width: 32, height: 32) .position(x: (geo.size.width * score / 100), y: geo.size.height / 2) } } } } private func getCornerRadius(for index: Int) -> UIRectCorner { if index == 0 { return [.topLeft, .bottomLeft] } else if index == segmentColors.count - 1 { return [.topRight, .bottomRight] } return [] } } } // MARK: - View Extensions extension View { func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View { clipShape(RoundedCorner(radius: radius, corners: corners)) } } struct RoundedCorner: Shape { var radius: CGFloat = .infinity var corners: UIRectCorner = .allCorners func path(in rect: CGRect) -> Path { let path = UIBezierPath( roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius) ) return Path(path.cgPath) } }