From 05625f3ee31e9a90941164bb4d96f03c956f17e8 Mon Sep 17 00:00:00 2001 From: Anubhav Mishra Date: Sun, 27 Jul 2025 11:08:51 +0530 Subject: [PATCH 1/4] edited Homview and scaleview and need some fixes --- TrackWeight.xcodeproj/project.pbxproj | 3 +- TrackWeight/HomeView.swift | 220 +++++++++++++------- TrackWeight/ScaleView.swift | 288 +++++++++++++------------- 3 files changed, 289 insertions(+), 222 deletions(-) diff --git a/TrackWeight.xcodeproj/project.pbxproj b/TrackWeight.xcodeproj/project.pbxproj index a343c4f..153c11d 100644 --- a/TrackWeight.xcodeproj/project.pbxproj +++ b/TrackWeight.xcodeproj/project.pbxproj @@ -333,7 +333,7 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 9ZRLG6277G; + DEVELOPMENT_TEAM = BQ65WXL7J3; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; @@ -365,6 +365,7 @@ CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 9ZRLG6277G; + "DEVELOPMENT_TEAM[sdk=macosx*]" = BQ65WXL7J3; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; diff --git a/TrackWeight/HomeView.swift b/TrackWeight/HomeView.swift index 5dadf96..13c813c 100644 --- a/TrackWeight/HomeView.swift +++ b/TrackWeight/HomeView.swift @@ -6,128 +6,192 @@ import SwiftUI struct HomeView: View { + // State variables for the "Begin" button animations + @State private var isHovering: Bool = false + @State private var isPressing: Bool = false + + // Action closure for the "Begin" button let onBegin: () -> Void - + var body: some View { - VStack(spacing: 40) { - Spacer() - + VStack(spacing: 40) { // Overall spacing between major sections + Spacer() // Pushes content towards the center/top if there's space + // Title section VStack(spacing: 15) { - Image(systemName: "scalemass") + Image(systemName: "scalemass.fill") .font(.system(size: 80, weight: .ultraLight)) - .foregroundStyle(Color.blue) - + .foregroundStyle(Color.blue) // Keep the original blue for the icon + Text("TrackWeight") .font(.system(size: 48, weight: .bold, design: .rounded)) .foregroundStyle( LinearGradient( - colors: [.blue, .teal, .cyan], + colors: [.blue, .teal, .cyan], // Original gradient for the title startPoint: .leading, endPoint: .trailing ) ) } - - // Description section - VStack(spacing: 20) { + + // Description and Limitations section (now more responsive and aesthetic) + VStack(spacing: 30) { // Spacing between description and limitations box Text("Transform your MacBook trackpad into a precision scale using Apple's private MultitouchSupport framework to read pressure values with gram-level accuracy.") - .font(.system(size: 18, weight: .medium)) - .foregroundStyle(Color.primary) + .font(.system(size: 19, weight: .medium)) // Slightly larger font for main description + .foregroundStyle(Color.primary) // Adapts to light/dark mode .multilineTextAlignment(.center) - .frame(maxWidth: 550) - - // Limitations section - VStack(spacing: 12) { + .frame(maxWidth: 600) // Increased max width for better readability on larger screens + .padding(.horizontal, 20) // Horizontal padding to prevent text from touching edges + + // Limitations section container (with glassmorphism effect) + VStack(spacing: 15) { // Spacing within the limitations box Text("Important Limitations") - .font(.system(size: 16, weight: .semibold)) - .foregroundStyle(Color.orange) - - VStack(spacing: 8) { + .font(.system(size: 17, weight: .semibold)) // Slightly larger, bolder font for title + .foregroundStyle(Color.orange) // Keep the orange accent for the title + + VStack(alignment: .leading, spacing: 10) { // Align limitation rows to the leading edge LimitationRow( icon: "hand.point.up.left", - text: "Requires finger contact for capacitive detection" + text: "Requires finger contact for capacitive detection. Objects must be non-conductive and have sufficient surface area." ) LimitationRow( icon: "chart.line.downtrend.xyaxis", - text: "May experience pressure drift when placing objects" + text: "May experience slight pressure drift when placing objects due to temperature or contact changes. Calibration is recommended." ) LimitationRow( - icon: "cube.fill", - text: "Metal/magnetic objects may not work" + icon: "cube.box.fill", + text: "Metal or magnetic objects may interfere with the trackpad's sensors and may not register accurately or at all." ) } + .padding(.top, 5) // Small space between "Important Limitations" title and the first row } - .padding(.horizontal, 30) - .padding(.vertical, 20) + .padding(.horizontal, 30) // Horizontal padding for content *inside* the limitations box + .padding(.vertical, 25) // Vertical padding for content *inside* the limitations box .background( - RoundedRectangle(cornerRadius: 15) - .foregroundColor(Color.orange.opacity(0.05)) - .overlay( - RoundedRectangle(cornerRadius: 15) - .stroke(Color.orange.opacity(0.2), lineWidth: 1) - ) + RoundedRectangle(cornerRadius: 20) // More rounded corners for the box + .fill(.ultraThinMaterial) // Frosted glass effect! ) - .frame(maxWidth: 500) - } - - Spacer() - - // Begin button - Button(action: onBegin) { - HStack(spacing: 10) { - Text("Begin") - .font(.system(size: 18, weight: .semibold)) - Image(systemName: "arrow.right") - .font(.system(size: 16, weight: .semibold)) - } - .foregroundStyle(Color.white) - .frame(width: 140, height: 50) - .background( + .overlay( // Subtle white stroke for definition RoundedRectangle(cornerRadius: 25) - .fill( - LinearGradient( - colors: [.blue, .teal], - startPoint: .leading, - endPoint: .trailing - ) - ) - .shadow(color: .blue.opacity(0.3), radius: 10, x: 0, y: 5) + .stroke(Color.white.opacity(0.1), lineWidth: 1) ) + .shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 5) // Softer, more diffuse shadow + .frame(maxWidth: 550) // Max width for the entire limitations box + .padding(.horizontal, 20) // Ensure the entire box has horizontal padding from screen edges } - .buttonStyle(.plain) - .scaleEffect(1.0) - .animation(.spring(response: 0.3, dampingFraction: 0.8), value: true) - .padding(.vertical, 10) - Spacer() + // Begin button (with glassmorphism effect and animations) + // Begin button + Button(action: { + self.isPressing = true + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.isPressing = false + onBegin() + } + }) { + HStack(spacing: 10) { + Text("Begin") + .font(.system(size: 18, weight: .semibold)) + Image(systemName: "scalemass.fill") // SF Symbol for the arrow + .font(.system(size: 16, weight: .semibold)) + // Icon movement on hover + .offset(x: isHovering ? 3 : 0) // Move icon right on hover + } + .foregroundStyle(.white) // Text and icon color + .frame(width: 140, height: 50) // Fixed size for consistency + .background( + RoundedRectangle(cornerRadius: 20) + .fill( + LinearGradient( + // Adjust colors based on hover/press state for dynamic gradient + colors: isPressing ? [.init(red: 0.35, green: 0.13, blue: 0.71), .init(red: 0.49, green: 0.23, blue: 0.98)] : // Darker on press + (isHovering ? [.init(red: 0.49, green: 0.23, blue: 0.98), .init(red: 0.65, green: 0.54, blue: 0.98)] : // Brighter on hover + [.blue, .teal]), // Original gradient + startPoint: .leading, + endPoint: .trailing + ) + ) + ) + // Shadow effect based on hover/press + .shadow(color: Color.black.opacity(isPressing ? 0.15 : (isHovering ? 0.35 : 0.2)), + radius: isPressing ? 8 : (isHovering ? 25 : 15), + x: 0, + y: isPressing ? 2 : (isHovering ? 8 : 4)) + } + .buttonStyle(.plain) + .padding(.vertical, 10) + // Scale and lift effect based on hover/press + .scaleEffect(isPressing ? 0.98 : (isHovering ? 1.02 : 1.0)) + .offset(y: isHovering ? -3 : 0) // Lift button on hover + // Apply animations for smooth transitions + .animation(.spring(response: 0.3, dampingFraction: 0.8), value: isHovering) // Smooth hover animation + .animation(.easeOut(duration: 0.1), value: isPressing) // Quicker press animation + .onHover { hover in // For macOS hover detection + self.isHovering = hover + } + Spacer() // Pushes content towards the center/bottom if there's space } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .padding(.horizontal, 40) + .frame(maxWidth: .infinity, maxHeight: .infinity) // Make the VStack fill the available screen + .padding(.horizontal, 40) // Overall horizontal padding for the entire HomeView content } } +/// A reusable SwiftUI view for displaying a single limitation row with an icon and text. +/// Includes a subtle hover effect for macOS. struct LimitationRow: View { - let icon: String - let text: String - + let icon: String // SF Symbol name (e.g., "hand.point.up.left") + let text: String // The description text for the limitation + + // State to track if the mouse is hovering over this specific row (macOS only) + @State private var isRowHovering: Bool = false + var body: some View { - HStack(spacing: 12) { + HStack(alignment: .top, spacing: 10) { + // Icon for the limitation Image(systemName: icon) - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(Color.orange) - .frame(width: 20) - + .font(.system(size: 18, weight: .medium)) // Consistent icon size + .foregroundStyle(Color.orange) // Keep the orange accent for the icon + .frame(width: 25) // Give the icon a fixed width to align text nicely + + // Text description for the limitation Text(text) - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(Color.secondary) - .multilineTextAlignment(.leading) - - Spacer() + .font(.system(size: 15, weight: .regular)) // Slightly smaller, regular weight + .foregroundStyle(Color.secondary) // Use secondary color for softer text + .lineLimit(nil) // Allow text to wrap to multiple lines + .fixedSize(horizontal: false, vertical: true) // Allow text to take up vertical space as needed + } + .padding(.vertical, 5) // Small vertical padding inside the row + .padding(.horizontal, 10) // Small horizontal padding inside the row + .background( + RoundedRectangle(cornerRadius: 10) // Background for the row itself + // Apply a very subtle white background on hover + .fill(isRowHovering ? Color.white.opacity(0.08) : Color.clear) + ) + .cornerRadius(10) // Clip content to rounded corners + // Subtle scale effect on hover + .scaleEffect(isRowHovering ? 1.01 : 1.0) + // Subtle shadow effect on hover + .shadow(color: Color.black.opacity(isRowHovering ? 0.1 : 0), + radius: isRowHovering ? 5 : 0, + x: 0, + y: isRowHovering ? 2 : 0) + // Smooth animation for hover effects + .animation(.easeOut(duration: 0.2), value: isRowHovering) + .onHover { hover in // macOS specific modifier to detect hover + isRowHovering = hover } } } +// Preview Provider for Xcode Canvas #Preview { - HomeView(onBegin: {}) + // To properly see the glassmorphism effect, provide a background in the preview + ZStack { + LinearGradient(gradient: Gradient(colors: [.purple, .blue, .green]), startPoint: .topLeading, endPoint: .bottomTrailing) + .edgesIgnoringSafeArea(.all) // Make the gradient fill the entire preview area + + HomeView(onBegin: { + print("Begin button tapped from preview!") + }) + } } diff --git a/TrackWeight/ScaleView.swift b/TrackWeight/ScaleView.swift index 97d9fb5..f2f566b 100644 --- a/TrackWeight/ScaleView.swift +++ b/TrackWeight/ScaleView.swift @@ -4,29 +4,31 @@ // import SwiftUI +import AppKit + struct ScaleView: View { @StateObject private var viewModel = ScaleViewModel() - @State private var scaleCompression: CGFloat = 0 - @State private var displayShake = false - @State private var particleOffset: CGFloat = 0 + @State private var scaleCompression: CGFloat = 0 // Controls vertical compression of the scale + @State private var displayShake = false // For subtle display shake animation (not used in this version but kept) + @State private var particleOffset: CGFloat = 0 // For particle effect (not used in this version but kept) @State private var keyMonitor: Any? - + var body: some View { GeometryReader { geometry in ZStack { - // Animated gradient background -// LinearGradient( -// colors: [ -// Color(red: 0.95, green: 0.97, blue: 1.0), -// Color(red: 0.85, green: 0.92, blue: 0.98) -// ], -// startPoint: .topLeading, -// endPoint: .bottomTrailing -// ) -// .ignoresSafeArea() - - VStack(spacing: geometry.size.height * 0.06) { + // Animated gradient background (uncommented and slightly adjusted colors) + LinearGradient( + colors: [ + Color(red: 0.1, green: 0.1, blue: 0.15), // Darker start + Color(red: 0.05, green: 0.05, blue: 0.1) // Darker end + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + .ignoresSafeArea() + + VStack(spacing: geometry.size.height * 0.06) { // Overall vertical spacing // Title with subtitle directly underneath VStack(spacing: 8) { Text("Track Weight") @@ -40,24 +42,24 @@ struct ScaleView: View { ) .minimumScaleFactor(0.7) .lineLimit(1) - + Text("Place your finger on the trackpad to begin") .font(.system(size: min(max(geometry.size.width * 0.022, 14), 18), weight: .medium)) - .foregroundStyle(.gray) + .foregroundStyle(.gray.opacity(0.7)) // Slightly more visible gray .multilineTextAlignment(.center) .frame(maxWidth: geometry.size.width * 0.8) - .opacity(viewModel.hasTouch ? 0 : 1) + .opacity(viewModel.hasTouch ? 0 : 1) // Fades out when touch is detected .animation(.easeInOut(duration: 0.5), value: viewModel.hasTouch) } .frame(height: max(geometry.size.height * 0.15, 80)) // Fixed height for title + subtitle .frame(maxWidth: .infinity) // Ensure full width for centering - - Spacer() - - // Cartoon Digital Scale - responsive size + + Spacer() // Pushes the scale towards the center + + // Modern Digital Scale View - responsive size HStack { Spacer() - CartoonScaleView( + ModernScaleView( // Changed from CartoonScaleView weight: viewModel.currentWeight, hasTouch: viewModel.hasTouch, compression: $scaleCompression, @@ -66,17 +68,17 @@ struct ScaleView: View { ) Spacer() } - - Spacer() - + + Spacer() // Pushes button towards the bottom + // Fixed container for button to prevent jumping VStack(spacing: 10) { if viewModel.hasTouch { Text("Press spacebar or click to zero") .font(.system(size: min(max(geometry.size.width * 0.018, 12), 16), weight: .medium)) - .foregroundStyle(.gray) + .foregroundStyle(.gray.opacity(0.7)) // More visible gray } - + Button(action: { viewModel.zeroScale() }) { @@ -87,29 +89,28 @@ struct ScaleView: View { .font(.system(size: min(max(geometry.size.width * 0.02, 14), 18), weight: .semibold)) } .foregroundStyle(.white) - .frame(width: min(max(geometry.size.width * 0.2, 140), 180), + .frame(width: min(max(geometry.size.width * 0.2, 140), 180), height: min(max(geometry.size.height * 0.08, 40), 55)) .background( + RoundedRectangle(cornerRadius: 25) // Use the same corner radius as the HomeView button + .fill(.ultraThinMaterial) // Glassmorphism for the button + ) + .overlay( // Subtle border for definition RoundedRectangle(cornerRadius: 25) - .fill( - LinearGradient( - colors: [.blue, .teal], - startPoint: .leading, - endPoint: .trailing - ) - ) + .stroke(.white.opacity(0.2), lineWidth: 1) ) + .shadow(color: .black.opacity(0.2), radius: 8, x: 0, y: 4) // Softer shadow } .buttonStyle(.plain) - .opacity(viewModel.hasTouch ? 1 : 0) - .scaleEffect(viewModel.hasTouch ? 1 : 0.8) + .opacity(viewModel.hasTouch ? 1 : 0) // Fades in when touch is detected + .scaleEffect(viewModel.hasTouch ? 1 : 0.8) // Scales in when touch is detected .animation(.spring(response: 0.4, dampingFraction: 0.8), value: viewModel.hasTouch) } .frame(height: min(max(geometry.size.height * 0.15, 80), 100)) // Fixed space for button + instruction .frame(maxWidth: .infinity) // Ensure full width for centering } - .padding(.horizontal, max(geometry.size.width * 0.05, 20)) - .padding(.vertical, max(geometry.size.height * 0.03, 20)) + .padding(.horizontal, max(geometry.size.width * 0.05, 20)) // Overall horizontal padding + .padding(.vertical, max(geometry.size.height * 0.03, 20)) // Overall vertical padding .frame(maxWidth: .infinity, maxHeight: .infinity) // Ensure the VStack takes full available space } } @@ -117,7 +118,8 @@ struct ScaleView: View { .modifier(FocusEffectModifier()) .onChange(of: viewModel.currentWeight) { newWeight in withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) { - scaleCompression = CGFloat(min(newWeight / 100.0, 0.2)) + + scaleCompression = CGFloat(min(newWeight / 100.0, 0.3)) //sensitivity } } .onAppear { @@ -129,7 +131,7 @@ struct ScaleView: View { removeKeyMonitoring() } } - + private func setupKeyMonitoring() { keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in // Space key code is 49 @@ -139,7 +141,7 @@ struct ScaleView: View { return event } } - + private func removeKeyMonitoring() { if let monitor = keyMonitor { NSEvent.removeMonitor(monitor) @@ -148,133 +150,127 @@ struct ScaleView: View { } } -struct CartoonScaleView: View { +// MARK: - ModernScaleView (Replaces CartoonScaleView) +struct ModernScaleView: View { let weight: Float let hasTouch: Bool @Binding var compression: CGFloat - @Binding var displayShake: Bool + @Binding var displayShake: Bool // Kept for potential future use let scaleFactor: CGFloat - + + // Max weight for needle rotation (e.g., 200 grams for full sweep) + private let maxNeedleWeight: Float = 200.0 + // Angle range for the needle (e.g., from -60 degrees to +60 degrees) + private let needleAngleRange: CGFloat = 120.0 + var body: some View { VStack(spacing: 0) { - // Scale platform (top) - responsive to weight - RoundedRectangle(cornerRadius: 8) + // Scale Platform (top part where object is placed) + RoundedRectangle(cornerRadius: 12 * scaleFactor) // Slightly rounded corners .fill( - LinearGradient( - colors: [.gray.opacity(0.3), .gray.opacity(0.6)], - startPoint: .top, - endPoint: .bottom + LinearGradient( // Subtle metallic gradient + colors: [ + Color(red: 0.8, green: 0.8, blue: 0.85), + Color(red: 0.95, green: 0.95, blue: 1.0) + ], + startPoint: .topLeading, + endPoint: .bottomTrailing ) ) - .frame(width: 200 * scaleFactor, height: 12 * scaleFactor) - .offset(y: compression * 15) - - // Scale body + .frame(width: 250 * scaleFactor, height: 25 * scaleFactor) // Thicker platform + .offset(y: compression * 15) // Moves down on compression + .shadow(color: .black.opacity(0.1), radius: 5, x: 0, y: 3) // Soft shadow + + // Scale Body (main part) ZStack { - // Main body - RoundedRectangle(cornerRadius: 20) - .fill( - LinearGradient( - colors: [ - Color(red: 0.95, green: 0.95, blue: 0.97), - Color(red: 0.85, green: 0.85, blue: 0.90) - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) + RoundedRectangle(cornerRadius: 20 * scaleFactor) // Rounded, modern shape + .fill(.ultraThinMaterial) // Frosted glass body + .frame(width: 280 * scaleFactor, height: 180 * scaleFactor) // Wider, more substantial body + .scaleEffect(y: 1 - compression * 1.2) // Squeeze effect: Increased multiplier for more squeeze + .offset(y: compression * 15) // Adjust vertical position during compression + .shadow(color: .black.opacity(0.2), radius: 15, x: 0, y: 10) // Enhanced shadow for depth + .overlay( // Subtle outer border + RoundedRectangle(cornerRadius: 25 * scaleFactor) + .stroke(Color.white.opacity(0.15), lineWidth: 1 * scaleFactor) ) - .frame(width: 250 * scaleFactor, height: 150 * scaleFactor) - .shadow(color: .black.opacity(0.15), radius: 12, x: 0, y: 8) - - // Display screen - RoundedRectangle(cornerRadius: 12) - .fill(.black) - .frame(width: 180 * scaleFactor, height: 60 * scaleFactor) - .offset(y: -10) - .overlay( - RoundedRectangle(cornerRadius: 12) - .fill( - LinearGradient( - colors: [.teal.opacity(0.8), .blue.opacity(0.6)], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 176 * scaleFactor, height: 56 * scaleFactor) - .offset(y: -10) + + // Digital Display Screen + RoundedRectangle(cornerRadius: 15 * scaleFactor) + .fill(Color.black.opacity(0.6)) // Dark, slightly transparent background for the screen + .frame(width: 200 * scaleFactor, height: 80 * scaleFactor) + .overlay( // Inner glow effect for the screen + RoundedRectangle(cornerRadius: 15 * scaleFactor) + .stroke(Color.white.opacity(hasTouch ? 0.3 : 0.1), lineWidth: hasTouch ? 1.5 * scaleFactor : 1 * scaleFactor) + .blur(radius: hasTouch ? 2 * scaleFactor : 0) ) - - // Weight display - VStack(spacing: 2) { + .offset(y: -25 * scaleFactor + compression * 15) // Adjust position with compression + + // Weight display (numbers) + VStack(spacing: 4 * scaleFactor) { Text(String(format: "%.1f", weight)) - .font(.system(size: 32 * scaleFactor, weight: .bold, design: .monospaced)) - .foregroundStyle(.white) - .shadow(color: .teal, radius: hasTouch ? 2 : 0) + .font(.system(size: 48 * scaleFactor, weight: .bold, design: .monospaced)) // Larger, bolder font + .foregroundStyle(.white) // White numbers + .shadow(color: .white.opacity(hasTouch ? 0.6 : 0), radius: hasTouch ? 4 : 0) // Glow effect on numbers when touched .animation(.easeInOut(duration: 0.2), value: weight) - + Text("grams") - .font(.system(size: 12 * scaleFactor, weight: .medium)) - .foregroundStyle(.white.opacity(0.8)) + .font(.system(size: 10 * scaleFactor, weight: .medium)) // Larger unit text + .foregroundStyle(.white.opacity(0.6)) // Softer white for units } - .offset(y: -10) - - // Status indicator - simple and clean + .offset(y: 50 * scaleFactor + compression * 15) // Match display screen position + + // Status Indicator (simple dot) - now inside the scale body if hasTouch { Circle() - .fill(.teal) - .frame(width: 8 * scaleFactor, height: 8 * scaleFactor) - .offset(x: 90 * scaleFactor, y: -50 * scaleFactor) + .fill(.teal) // Bright teal for active state + .frame(width: 20 * scaleFactor, height: 10 * scaleFactor) + .offset(x: 120 * scaleFactor, y: -70 * scaleFactor + compression * 15) // Positioned at top-right of screen + .animation(.easeInOut(duration: 0.3), value: hasTouch) } - - // Fun face on the scale - positioned below the display screen - VStack(spacing: 8 * scaleFactor) { - // Eyes - HStack(spacing: 15 * scaleFactor) { - Circle() - .fill(.black) - .frame(width: 8 * scaleFactor, height: 8 * scaleFactor) - Circle() - .fill(.black) - .frame(width: 8 * scaleFactor, height: 8 * scaleFactor) - } - - // Responsive mouth expression - Group { - if hasTouch && weight > 5 { - // Happy mouth when weighing something substantial - Path { path in - path.move(to: CGPoint(x: 0, y: 0)) - path.addQuadCurve(to: CGPoint(x: 20, y: 0), control: CGPoint(x: 0, y: 15)) - } - .stroke(.black, lineWidth: 2 * scaleFactor) - .frame(width: 20 * scaleFactor, height: 10 * scaleFactor) - } else { - // Neutral mouth - Rectangle() - .fill(.black) - .frame(width: 12 * scaleFactor, height: 2 * scaleFactor) - } - } - .animation(.easeInOut(duration: 0.3), value: weight > 5) + + // NEW: Odometer-like Needle Animation + // Visible only when touch is detected and weight is above a threshold + if hasTouch && weight > 0.5 { // Show needle only when weighing something + // Needle shape + Rectangle() + .fill(Color.white.opacity(0.8)) + .frame(width: 3 * scaleFactor, height: 40 * scaleFactor) // Needle dimensions + .cornerRadius(2 * scaleFactor) + .offset(y: -25 * scaleFactor + compression * 15) // Position at display center, adjusted for compression + .rotationEffect( + // Calculate rotation based on weight, clamped to range + Angle(degrees: Double(min(weight, maxNeedleWeight) / maxNeedleWeight) * needleAngleRange - (needleAngleRange / 2)) + ) + .shadow(color: .white.opacity(0.3), radius: 2 * scaleFactor) // Subtle glow + .animation(.spring(response: 0.5, dampingFraction: 0.7), value: weight) // Smooth animation + + // Small pivot circle at the base of the needle + Circle() + .fill(Color.white.opacity(0.9)) + .frame(width: 10 * scaleFactor, height: 10 * scaleFactor) + .offset(y: -2 * scaleFactor + compression * 15) // Position at display center, adjusted for compression + .shadow(color: .black.opacity(0.2), radius: 2 * scaleFactor) } - .offset(y: 60 * scaleFactor) // Position well below the display screen } - - // Scale legs - HStack(spacing: 140 * scaleFactor) { + + // Scale Legs + HStack(spacing: 160 * scaleFactor) { // Increased spacing between legs ForEach(0..<2, id: \.self) { _ in - RoundedRectangle(cornerRadius: 4) - .fill(.gray.opacity(0.7)) - .frame(width: 12 * scaleFactor, height: 25 * scaleFactor) + Capsule() // More modern capsule shape for legs + .fill(.gray.opacity(0.6)) + .frame(width: 15 * scaleFactor, height: 40 * scaleFactor) // Thicker, taller legs .offset(y: compression * 3) + .shadow(color: .black.opacity(0.1), radius: 3, x: 0, y: 2) // Subtle shadow } } - .offset(y: -5) + .offset(y: -15 * scaleFactor) // Adjust vertical position relative to scale body } - .animation(.spring(response: 0.4, dampingFraction: 0.8), value: compression) + .animation(.spring(response: 0.4, dampingFraction: 0.8), value: compression) // Smooth compression animation } } + +// MARK: - FocusEffectModifier struct FocusEffectModifier: ViewModifier { func body(content: Content) -> some View { if #available(macOS 14.0, *) { @@ -285,6 +281,12 @@ struct FocusEffectModifier: ViewModifier { } } +// MARK: - Preview #Preview { - ScaleView() + // A dark gradient background to highlight the sleek, modern scale design + LinearGradient(gradient: Gradient(colors: [Color(red: 0.05, green: 0.05, blue: 0.1), Color(red: 0.0, green: 0.0, blue: 0.05)]), startPoint: .topLeading, endPoint: .bottomTrailing) + .edgesIgnoringSafeArea(.all) + .overlay( + ScaleView() + ) } From eeb6587b8c1c5bd2a3d0724ca947f6e9f1c3f5e3 Mon Sep 17 00:00:00 2001 From: Anubhav Mishra Date: Sun, 27 Jul 2025 11:55:40 +0530 Subject: [PATCH 2/4] fixed the code --- TrackWeight/HomeView.swift | 130 +++++++++++++++----------------- TrackWeight/ScaleView.swift | 145 +++++++++++++++++------------------- 2 files changed, 130 insertions(+), 145 deletions(-) diff --git a/TrackWeight/HomeView.swift b/TrackWeight/HomeView.swift index 13c813c..a3901c5 100644 --- a/TrackWeight/HomeView.swift +++ b/TrackWeight/HomeView.swift @@ -6,50 +6,49 @@ import SwiftUI struct HomeView: View { - // State variables for the "Begin" button animations + @State private var isHovering: Bool = false @State private var isPressing: Bool = false - // Action closure for the "Begin" button let onBegin: () -> Void var body: some View { - VStack(spacing: 40) { // Overall spacing between major sections - Spacer() // Pushes content towards the center/top if there's space + VStack(spacing: 40) { + Spacer() // Title section VStack(spacing: 15) { Image(systemName: "scalemass.fill") .font(.system(size: 80, weight: .ultraLight)) - .foregroundStyle(Color.blue) // Keep the original blue for the icon + .foregroundStyle(Color.blue) Text("TrackWeight") .font(.system(size: 48, weight: .bold, design: .rounded)) .foregroundStyle( LinearGradient( - colors: [.blue, .teal, .cyan], // Original gradient for the title + colors: [.blue, .teal, .cyan], startPoint: .leading, endPoint: .trailing ) ) } - // Description and Limitations section (now more responsive and aesthetic) - VStack(spacing: 30) { // Spacing between description and limitations box + // Description and Limitations section + VStack(spacing: 30) { Text("Transform your MacBook trackpad into a precision scale using Apple's private MultitouchSupport framework to read pressure values with gram-level accuracy.") - .font(.system(size: 19, weight: .medium)) // Slightly larger font for main description - .foregroundStyle(Color.primary) // Adapts to light/dark mode + .font(.system(size: 19, weight: .medium)) + .foregroundStyle(Color.primary) .multilineTextAlignment(.center) - .frame(maxWidth: 600) // Increased max width for better readability on larger screens - .padding(.horizontal, 20) // Horizontal padding to prevent text from touching edges + .frame(maxWidth: 600) + .padding(.horizontal, 20) - // Limitations section container (with glassmorphism effect) - VStack(spacing: 15) { // Spacing within the limitations box + // Limitations section container but with glassmorphism effect this do not work as i thought + VStack(spacing: 15) { Text("Important Limitations") - .font(.system(size: 17, weight: .semibold)) // Slightly larger, bolder font for title - .foregroundStyle(Color.orange) // Keep the orange accent for the title + .font(.system(size: 17, weight: .semibold)) + .foregroundStyle(Color.orange) - VStack(alignment: .leading, spacing: 10) { // Align limitation rows to the leading edge + VStack(alignment: .leading, spacing: 10) { LimitationRow( icon: "hand.point.up.left", text: "Requires finger contact for capacitive detection. Objects must be non-conductive and have sufficient surface area." @@ -63,25 +62,25 @@ struct HomeView: View { text: "Metal or magnetic objects may interfere with the trackpad's sensors and may not register accurately or at all." ) } - .padding(.top, 5) // Small space between "Important Limitations" title and the first row + .padding(.top, 5) } - .padding(.horizontal, 30) // Horizontal padding for content *inside* the limitations box - .padding(.vertical, 25) // Vertical padding for content *inside* the limitations box + .padding(.horizontal, 30) + .padding(.vertical, 25) .background( - RoundedRectangle(cornerRadius: 20) // More rounded corners for the box - .fill(.ultraThinMaterial) // Frosted glass effect! + RoundedRectangle(cornerRadius: 20) + .fill(.ultraThinMaterial) ) - .overlay( // Subtle white stroke for definition + .overlay( RoundedRectangle(cornerRadius: 25) .stroke(Color.white.opacity(0.1), lineWidth: 1) ) - .shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 5) // Softer, more diffuse shadow - .frame(maxWidth: 550) // Max width for the entire limitations box - .padding(.horizontal, 20) // Ensure the entire box has horizontal padding from screen edges + .shadow(color: Color.black.opacity(0.1), radius: 10, x: 0, y: 5) + .frame(maxWidth: 550) + .padding(.horizontal, 20) } - // Begin button (with glassmorphism effect and animations) - // Begin button + + // Begin button Button(action: { self.isPressing = true DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { @@ -92,27 +91,27 @@ struct HomeView: View { HStack(spacing: 10) { Text("Begin") .font(.system(size: 18, weight: .semibold)) - Image(systemName: "scalemass.fill") // SF Symbol for the arrow + Image(systemName: "scalemass.fill") .font(.system(size: 16, weight: .semibold)) // Icon movement on hover - .offset(x: isHovering ? 3 : 0) // Move icon right on hover + .offset(x: isHovering ? 3 : 0) } - .foregroundStyle(.white) // Text and icon color - .frame(width: 140, height: 50) // Fixed size for consistency + .foregroundStyle(.white) + .frame(width: 140, height: 50) .background( RoundedRectangle(cornerRadius: 20) .fill( LinearGradient( - // Adjust colors based on hover/press state for dynamic gradient + colors: isPressing ? [.init(red: 0.35, green: 0.13, blue: 0.71), .init(red: 0.49, green: 0.23, blue: 0.98)] : // Darker on press (isHovering ? [.init(red: 0.49, green: 0.23, blue: 0.98), .init(red: 0.65, green: 0.54, blue: 0.98)] : // Brighter on hover - [.blue, .teal]), // Original gradient + [.blue, .teal]), startPoint: .leading, endPoint: .trailing ) ) ) - // Shadow effect based on hover/press + .shadow(color: Color.black.opacity(isPressing ? 0.15 : (isHovering ? 0.35 : 0.2)), radius: isPressing ? 8 : (isHovering ? 25 : 15), x: 0, @@ -120,75 +119,66 @@ struct HomeView: View { } .buttonStyle(.plain) .padding(.vertical, 10) - // Scale and lift effect based on hover/press .scaleEffect(isPressing ? 0.98 : (isHovering ? 1.02 : 1.0)) - .offset(y: isHovering ? -3 : 0) // Lift button on hover - // Apply animations for smooth transitions - .animation(.spring(response: 0.3, dampingFraction: 0.8), value: isHovering) // Smooth hover animation - .animation(.easeOut(duration: 0.1), value: isPressing) // Quicker press animation - .onHover { hover in // For macOS hover detection + .offset(y: isHovering ? -3 : 0) + .animation(.spring(response: 0.3, dampingFraction: 0.8), value: isHovering) + .animation(.easeOut(duration: 0.1), value: isPressing) + .onHover { hover in self.isHovering = hover } - Spacer() // Pushes content towards the center/bottom if there's space + Spacer() } - .frame(maxWidth: .infinity, maxHeight: .infinity) // Make the VStack fill the available screen - .padding(.horizontal, 40) // Overall horizontal padding for the entire HomeView content + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(.horizontal, 40) } } -/// A reusable SwiftUI view for displaying a single limitation row with an icon and text. -/// Includes a subtle hover effect for macOS. struct LimitationRow: View { - let icon: String // SF Symbol name (e.g., "hand.point.up.left") - let text: String // The description text for the limitation + let icon: String + let text: String - // State to track if the mouse is hovering over this specific row (macOS only) + @State private var isRowHovering: Bool = false var body: some View { HStack(alignment: .top, spacing: 10) { - // Icon for the limitation + Image(systemName: icon) - .font(.system(size: 18, weight: .medium)) // Consistent icon size - .foregroundStyle(Color.orange) // Keep the orange accent for the icon - .frame(width: 25) // Give the icon a fixed width to align text nicely + .font(.system(size: 18, weight: .medium)) + .foregroundStyle(Color.orange) + .frame(width: 25) - // Text description for the limitation + Text(text) - .font(.system(size: 15, weight: .regular)) // Slightly smaller, regular weight - .foregroundStyle(Color.secondary) // Use secondary color for softer text - .lineLimit(nil) // Allow text to wrap to multiple lines - .fixedSize(horizontal: false, vertical: true) // Allow text to take up vertical space as needed + .font(.system(size: 15, weight: .regular)) + .foregroundStyle(Color.secondary) + .lineLimit(nil) + .fixedSize(horizontal: false, vertical: true) } - .padding(.vertical, 5) // Small vertical padding inside the row - .padding(.horizontal, 10) // Small horizontal padding inside the row + .padding(.vertical, 5) + .padding(.horizontal, 10) .background( - RoundedRectangle(cornerRadius: 10) // Background for the row itself - // Apply a very subtle white background on hover + RoundedRectangle(cornerRadius: 10) .fill(isRowHovering ? Color.white.opacity(0.08) : Color.clear) ) - .cornerRadius(10) // Clip content to rounded corners - // Subtle scale effect on hover + .cornerRadius(10) .scaleEffect(isRowHovering ? 1.01 : 1.0) - // Subtle shadow effect on hover .shadow(color: Color.black.opacity(isRowHovering ? 0.1 : 0), radius: isRowHovering ? 5 : 0, x: 0, y: isRowHovering ? 2 : 0) - // Smooth animation for hover effects + .animation(.easeOut(duration: 0.2), value: isRowHovering) - .onHover { hover in // macOS specific modifier to detect hover + .onHover { hover in isRowHovering = hover } } } -// Preview Provider for Xcode Canvas #Preview { - // To properly see the glassmorphism effect, provide a background in the preview ZStack { LinearGradient(gradient: Gradient(colors: [.purple, .blue, .green]), startPoint: .topLeading, endPoint: .bottomTrailing) - .edgesIgnoringSafeArea(.all) // Make the gradient fill the entire preview area + .edgesIgnoringSafeArea(.all) HomeView(onBegin: { print("Begin button tapped from preview!") diff --git a/TrackWeight/ScaleView.swift b/TrackWeight/ScaleView.swift index f2f566b..ce57d11 100644 --- a/TrackWeight/ScaleView.swift +++ b/TrackWeight/ScaleView.swift @@ -9,15 +9,15 @@ import AppKit struct ScaleView: View { @StateObject private var viewModel = ScaleViewModel() - @State private var scaleCompression: CGFloat = 0 // Controls vertical compression of the scale - @State private var displayShake = false // For subtle display shake animation (not used in this version but kept) - @State private var particleOffset: CGFloat = 0 // For particle effect (not used in this version but kept) + @State private var scaleCompression: CGFloat = 0 + @State private var displayShake = false + @State private var particleOffset: CGFloat = 0 @State private var keyMonitor: Any? var body: some View { GeometryReader { geometry in ZStack { - // Animated gradient background (uncommented and slightly adjusted colors) + // Animated gradient background LinearGradient( colors: [ Color(red: 0.1, green: 0.1, blue: 0.15), // Darker start @@ -28,8 +28,8 @@ struct ScaleView: View { ) .ignoresSafeArea() - VStack(spacing: geometry.size.height * 0.06) { // Overall vertical spacing - // Title with subtitle directly underneath + VStack(spacing: geometry.size.height * 0.06) { + VStack(spacing: 8) { Text("Track Weight") .font(.system(size: min(max(geometry.size.width * 0.05, 24), 42), weight: .bold, design: .rounded)) @@ -45,18 +45,18 @@ struct ScaleView: View { Text("Place your finger on the trackpad to begin") .font(.system(size: min(max(geometry.size.width * 0.022, 14), 18), weight: .medium)) - .foregroundStyle(.gray.opacity(0.7)) // Slightly more visible gray + .foregroundStyle(.gray.opacity(0.7)) .multilineTextAlignment(.center) .frame(maxWidth: geometry.size.width * 0.8) - .opacity(viewModel.hasTouch ? 0 : 1) // Fades out when touch is detected + .opacity(viewModel.hasTouch ? 0 : 1) .animation(.easeInOut(duration: 0.5), value: viewModel.hasTouch) } - .frame(height: max(geometry.size.height * 0.15, 80)) // Fixed height for title + subtitle - .frame(maxWidth: .infinity) // Ensure full width for centering + .frame(height: max(geometry.size.height * 0.15, 80)) + .frame(maxWidth: .infinity) - Spacer() // Pushes the scale towards the center + Spacer() - // Modern Digital Scale View - responsive size + //Scale View - responsive size HStack { Spacer() ModernScaleView( // Changed from CartoonScaleView @@ -69,9 +69,8 @@ struct ScaleView: View { Spacer() } - Spacer() // Pushes button towards the bottom + Spacer() - // Fixed container for button to prevent jumping VStack(spacing: 10) { if viewModel.hasTouch { Text("Press spacebar or click to zero") @@ -92,26 +91,26 @@ struct ScaleView: View { .frame(width: min(max(geometry.size.width * 0.2, 140), 180), height: min(max(geometry.size.height * 0.08, 40), 55)) .background( - RoundedRectangle(cornerRadius: 25) // Use the same corner radius as the HomeView button - .fill(.ultraThinMaterial) // Glassmorphism for the button + RoundedRectangle(cornerRadius: 25) + .fill(.ultraThinMaterial) ) - .overlay( // Subtle border for definition + .overlay( RoundedRectangle(cornerRadius: 25) .stroke(.white.opacity(0.2), lineWidth: 1) ) - .shadow(color: .black.opacity(0.2), radius: 8, x: 0, y: 4) // Softer shadow + .shadow(color: .black.opacity(0.2), radius: 8, x: 0, y: 4) } .buttonStyle(.plain) - .opacity(viewModel.hasTouch ? 1 : 0) // Fades in when touch is detected - .scaleEffect(viewModel.hasTouch ? 1 : 0.8) // Scales in when touch is detected + .opacity(viewModel.hasTouch ? 1 : 0) + .scaleEffect(viewModel.hasTouch ? 1 : 0.8) .animation(.spring(response: 0.4, dampingFraction: 0.8), value: viewModel.hasTouch) } - .frame(height: min(max(geometry.size.height * 0.15, 80), 100)) // Fixed space for button + instruction - .frame(maxWidth: .infinity) // Ensure full width for centering + .frame(height: min(max(geometry.size.height * 0.15, 80), 100)) + .frame(maxWidth: .infinity) } - .padding(.horizontal, max(geometry.size.width * 0.05, 20)) // Overall horizontal padding - .padding(.vertical, max(geometry.size.height * 0.03, 20)) // Overall vertical padding - .frame(maxWidth: .infinity, maxHeight: .infinity) // Ensure the VStack takes full available space + .padding(.horizontal, max(geometry.size.width * 0.05, 20)) + .padding(.vertical, max(geometry.size.height * 0.03, 20)) + .frame(maxWidth: .infinity, maxHeight: .infinity) } } .focusable() @@ -150,25 +149,23 @@ struct ScaleView: View { } } -// MARK: - ModernScaleView (Replaces CartoonScaleView) + struct ModernScaleView: View { let weight: Float let hasTouch: Bool @Binding var compression: CGFloat - @Binding var displayShake: Bool // Kept for potential future use + @Binding var displayShake: Bool // can be used in future let scaleFactor: CGFloat - // Max weight for needle rotation (e.g., 200 grams for full sweep) + private let maxNeedleWeight: Float = 200.0 - // Angle range for the needle (e.g., from -60 degrees to +60 degrees) private let needleAngleRange: CGFloat = 120.0 var body: some View { VStack(spacing: 0) { - // Scale Platform (top part where object is placed) - RoundedRectangle(cornerRadius: 12 * scaleFactor) // Slightly rounded corners + RoundedRectangle(cornerRadius: 12 * scaleFactor) .fill( - LinearGradient( // Subtle metallic gradient + LinearGradient( colors: [ Color(red: 0.8, green: 0.8, blue: 0.85), Color(red: 0.95, green: 0.95, blue: 1.0) @@ -177,100 +174,98 @@ struct ModernScaleView: View { endPoint: .bottomTrailing ) ) - .frame(width: 250 * scaleFactor, height: 25 * scaleFactor) // Thicker platform - .offset(y: compression * 15) // Moves down on compression - .shadow(color: .black.opacity(0.1), radius: 5, x: 0, y: 3) // Soft shadow + .frame(width: 280 * scaleFactor, height: 20 * scaleFactor) + .offset(y: compression * 15) + .shadow(color: .black.opacity(0.1), radius: 5, x: 0, y: 3) // Scale Body (main part) ZStack { - RoundedRectangle(cornerRadius: 20 * scaleFactor) // Rounded, modern shape - .fill(.ultraThinMaterial) // Frosted glass body - .frame(width: 280 * scaleFactor, height: 180 * scaleFactor) // Wider, more substantial body - .scaleEffect(y: 1 - compression * 1.2) // Squeeze effect: Increased multiplier for more squeeze - .offset(y: compression * 15) // Adjust vertical position during compression - .shadow(color: .black.opacity(0.2), radius: 15, x: 0, y: 10) // Enhanced shadow for depth + RoundedRectangle(cornerRadius: 20 * scaleFactor) + .fill(.ultraThinMaterial) + .frame(width: 280 * scaleFactor, height: 180 * scaleFactor) + .scaleEffect(y: 1 - compression * 1.2) + .offset(y: compression * 15) + .shadow(color: .black.opacity(0.2), radius: 15, x: 0, y: 10) .overlay( // Subtle outer border - RoundedRectangle(cornerRadius: 25 * scaleFactor) + RoundedRectangle(cornerRadius: 20 * scaleFactor) .stroke(Color.white.opacity(0.15), lineWidth: 1 * scaleFactor) ) // Digital Display Screen - RoundedRectangle(cornerRadius: 15 * scaleFactor) - .fill(Color.black.opacity(0.6)) // Dark, slightly transparent background for the screen + RoundedRectangle(cornerRadius: 20 * scaleFactor) + .fill(Color.black.opacity(0.6)) .frame(width: 200 * scaleFactor, height: 80 * scaleFactor) .overlay( // Inner glow effect for the screen RoundedRectangle(cornerRadius: 15 * scaleFactor) .stroke(Color.white.opacity(hasTouch ? 0.3 : 0.1), lineWidth: hasTouch ? 1.5 * scaleFactor : 1 * scaleFactor) .blur(radius: hasTouch ? 2 * scaleFactor : 0) ) - .offset(y: -25 * scaleFactor + compression * 15) // Adjust position with compression + .offset(y: -25 * scaleFactor + compression * 15) - // Weight display (numbers) + // Weight display VStack(spacing: 4 * scaleFactor) { Text(String(format: "%.1f", weight)) - .font(.system(size: 48 * scaleFactor, weight: .bold, design: .monospaced)) // Larger, bolder font + .font(.system(size: 42 * scaleFactor, weight: .bold, design: .monospaced)) .foregroundStyle(.white) // White numbers - .shadow(color: .white.opacity(hasTouch ? 0.6 : 0), radius: hasTouch ? 4 : 0) // Glow effect on numbers when touched + .shadow(color: .white.opacity(hasTouch ? 0.6 : 0), radius: hasTouch ? 4 : 0) // Glow effect when touch .animation(.easeInOut(duration: 0.2), value: weight) Text("grams") - .font(.system(size: 10 * scaleFactor, weight: .medium)) // Larger unit text - .foregroundStyle(.white.opacity(0.6)) // Softer white for units + .font(.system(size: 10 * scaleFactor, weight: .medium)) + .foregroundStyle(.white.opacity(0.6)) } - .offset(y: 50 * scaleFactor + compression * 15) // Match display screen position + .offset(y: 50 * scaleFactor + compression * 15) - // Status Indicator (simple dot) - now inside the scale body + // Status Indicator if hasTouch { Circle() - .fill(.teal) // Bright teal for active state + .fill(.teal) .frame(width: 20 * scaleFactor, height: 10 * scaleFactor) - .offset(x: 120 * scaleFactor, y: -70 * scaleFactor + compression * 15) // Positioned at top-right of screen + .offset(x: 120 * scaleFactor, y: -70 * scaleFactor + compression * 15) .animation(.easeInOut(duration: 0.3), value: hasTouch) } - // NEW: Odometer-like Needle Animation - // Visible only when touch is detected and weight is above a threshold - if hasTouch && weight > 0.5 { // Show needle only when weighing something - // Needle shape +// Needle Animation + if weight > 0.5 { Rectangle() .fill(Color.white.opacity(0.8)) - .frame(width: 3 * scaleFactor, height: 40 * scaleFactor) // Needle dimensions + .frame(width: 3 * scaleFactor, height: 40 * scaleFactor) .cornerRadius(2 * scaleFactor) - .offset(y: -25 * scaleFactor + compression * 15) // Position at display center, adjusted for compression + .offset(y: -25 * scaleFactor + compression * 15) .rotationEffect( - // Calculate rotation based on weight, clamped to range Angle(degrees: Double(min(weight, maxNeedleWeight) / maxNeedleWeight) * needleAngleRange - (needleAngleRange / 2)) ) - .shadow(color: .white.opacity(0.3), radius: 2 * scaleFactor) // Subtle glow - .animation(.spring(response: 0.5, dampingFraction: 0.7), value: weight) // Smooth animation + .shadow(color: .white.opacity(0.3), radius: 2 * scaleFactor) + .animation(.spring(response: 0.5, dampingFraction: 0.7), value: weight) - // Small pivot circle at the base of the needle + // Pivot circle Circle() .fill(Color.white.opacity(0.9)) .frame(width: 10 * scaleFactor, height: 10 * scaleFactor) - .offset(y: -2 * scaleFactor + compression * 15) // Position at display center, adjusted for compression + .offset(y: -2 * scaleFactor + compression * 15) .shadow(color: .black.opacity(0.2), radius: 2 * scaleFactor) + + } } // Scale Legs - HStack(spacing: 160 * scaleFactor) { // Increased spacing between legs + HStack(spacing: 160 * scaleFactor) { ForEach(0..<2, id: \.self) { _ in - Capsule() // More modern capsule shape for legs - .fill(.gray.opacity(0.6)) - .frame(width: 15 * scaleFactor, height: 40 * scaleFactor) // Thicker, taller legs - .offset(y: compression * 3) + Capsule() + .fill(.gray.opacity(0.5)) + .frame(width: 20 * scaleFactor, height: 40 * scaleFactor) + .offset(y: -2 * compression * 3) .shadow(color: .black.opacity(0.1), radius: 3, x: 0, y: 2) // Subtle shadow } } - .offset(y: -15 * scaleFactor) // Adjust vertical position relative to scale body + .offset(y: -15 * scaleFactor) } - .animation(.spring(response: 0.4, dampingFraction: 0.8), value: compression) // Smooth compression animation + .animation(.spring(response: 0.4, dampingFraction: 0.8), value: compression) } } -// MARK: - FocusEffectModifier struct FocusEffectModifier: ViewModifier { func body(content: Content) -> some View { if #available(macOS 14.0, *) { @@ -281,9 +276,9 @@ struct FocusEffectModifier: ViewModifier { } } -// MARK: - Preview + #Preview { - // A dark gradient background to highlight the sleek, modern scale design + //dark gradient background LinearGradient(gradient: Gradient(colors: [Color(red: 0.05, green: 0.05, blue: 0.1), Color(red: 0.0, green: 0.0, blue: 0.05)]), startPoint: .topLeading, endPoint: .bottomTrailing) .edgesIgnoringSafeArea(.all) .overlay( From ecd6f381ab36aa1a453d8408916a8551186be936 Mon Sep 17 00:00:00 2001 From: Anubhav Mishra Date: Sun, 27 Jul 2025 14:45:02 +0530 Subject: [PATCH 3/4] fixed scaleview --- TrackWeight/ScaleView.swift | 286 +++++++++++++++++------------------- 1 file changed, 138 insertions(+), 148 deletions(-) diff --git a/TrackWeight/ScaleView.swift b/TrackWeight/ScaleView.swift index ce57d11..75e6fb1 100644 --- a/TrackWeight/ScaleView.swift +++ b/TrackWeight/ScaleView.swift @@ -6,39 +6,27 @@ import SwiftUI import AppKit +// MARK: - ViewModel (Required for the view to work) +// ⚠️ If you have this class defined elsewhere, DELETE this placeholder to avoid errors. + + +// MARK: - Main View struct ScaleView: View { @StateObject private var viewModel = ScaleViewModel() @State private var scaleCompression: CGFloat = 0 - @State private var displayShake = false - @State private var particleOffset: CGFloat = 0 @State private var keyMonitor: Any? var body: some View { GeometryReader { geometry in ZStack { - // Animated gradient background - LinearGradient( - colors: [ - Color(red: 0.1, green: 0.1, blue: 0.15), // Darker start - Color(red: 0.05, green: 0.05, blue: 0.1) // Darker end - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - .ignoresSafeArea() - VStack(spacing: geometry.size.height * 0.06) { - + // Header VStack(spacing: 8) { Text("Track Weight") .font(.system(size: min(max(geometry.size.width * 0.05, 24), 42), weight: .bold, design: .rounded)) .foregroundStyle( - LinearGradient( - colors: [.blue, .teal, .cyan], - startPoint: .leading, - endPoint: .trailing - ) + LinearGradient(colors: [.blue, .teal, .cyan], startPoint: .leading, endPoint: .trailing) ) .minimumScaleFactor(0.7) .lineLimit(1) @@ -56,31 +44,24 @@ struct ScaleView: View { Spacer() - //Scale View - responsive size - HStack { - Spacer() - ModernScaleView( // Changed from CartoonScaleView - weight: viewModel.currentWeight, - hasTouch: viewModel.hasTouch, - compression: $scaleCompression, - displayShake: $displayShake, - scaleFactor: min(geometry.size.width / 700, geometry.size.height / 500) - ) - Spacer() - } + FitnessScaleView( + weight: viewModel.currentWeight, + hasTouch: viewModel.hasTouch, + compression: $scaleCompression, + scaleFactor: min(geometry.size.width / 700, geometry.size.height / 550) // Adjusted scale + ) Spacer() + // Controls VStack(spacing: 10) { if viewModel.hasTouch { Text("Press spacebar or click to zero") .font(.system(size: min(max(geometry.size.width * 0.018, 12), 16), weight: .medium)) - .foregroundStyle(.gray.opacity(0.7)) // More visible gray + .foregroundStyle(.gray.opacity(0.7)) } - Button(action: { - viewModel.zeroScale() - }) { + Button(action: viewModel.zeroScale) { HStack(spacing: 8) { Image(systemName: "arrow.clockwise") .font(.system(size: min(max(geometry.size.width * 0.02, 14), 18), weight: .semibold)) @@ -92,11 +73,7 @@ struct ScaleView: View { height: min(max(geometry.size.height * 0.08, 40), 55)) .background( RoundedRectangle(cornerRadius: 25) - .fill(.ultraThinMaterial) - ) - .overlay( - RoundedRectangle(cornerRadius: 25) - .stroke(.white.opacity(0.2), lineWidth: 1) + .fill(LinearGradient(colors: [.blue, .teal], startPoint: .leading, endPoint: .trailing)) ) .shadow(color: .black.opacity(0.2), radius: 8, x: 0, y: 4) } @@ -116,9 +93,10 @@ struct ScaleView: View { .focusable() .modifier(FocusEffectModifier()) .onChange(of: viewModel.currentWeight) { newWeight in + // The `compression` value drives all the animations. + // It's a value from 0.0 (no weight) to 0.2 (max weight). withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) { - - scaleCompression = CGFloat(min(newWeight / 100.0, 0.3)) //sensitivity + scaleCompression = CGFloat(min(Float(newWeight) / 200.0, 0.2)) } } .onAppear { @@ -149,123 +127,140 @@ struct ScaleView: View { } } - -struct ModernScaleView: View { +// MARK: - Fitness Character View (with Tray) +struct FitnessScaleView: View { let weight: Float let hasTouch: Bool - @Binding var compression: CGFloat - @Binding var displayShake: Bool // can be used in future + @Binding var compression: CGFloat // Value from 0.0 to 0.2 let scaleFactor: CGFloat - - private let maxNeedleWeight: Float = 200.0 - private let needleAngleRange: CGFloat = 120.0 - var body: some View { - VStack(spacing: 0) { - RoundedRectangle(cornerRadius: 12 * scaleFactor) - .fill( - LinearGradient( - colors: [ - Color(red: 0.8, green: 0.8, blue: 0.85), - Color(red: 0.95, green: 0.95, blue: 1.0) - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .frame(width: 280 * scaleFactor, height: 20 * scaleFactor) - .offset(y: compression * 15) - .shadow(color: .black.opacity(0.1), radius: 5, x: 0, y: 3) - - // Scale Body (main part) - ZStack { - RoundedRectangle(cornerRadius: 20 * scaleFactor) - .fill(.ultraThinMaterial) - .frame(width: 280 * scaleFactor, height: 180 * scaleFactor) - .scaleEffect(y: 1 - compression * 1.2) - .offset(y: compression * 15) - .shadow(color: .black.opacity(0.2), radius: 15, x: 0, y: 10) - .overlay( // Subtle outer border - RoundedRectangle(cornerRadius: 20 * scaleFactor) - .stroke(Color.white.opacity(0.15), lineWidth: 1 * scaleFactor) - ) - - // Digital Display Screen - RoundedRectangle(cornerRadius: 20 * scaleFactor) - .fill(Color.black.opacity(0.6)) - .frame(width: 200 * scaleFactor, height: 80 * scaleFactor) - .overlay( // Inner glow effect for the screen - RoundedRectangle(cornerRadius: 15 * scaleFactor) - .stroke(Color.white.opacity(hasTouch ? 0.3 : 0.1), lineWidth: hasTouch ? 1.5 * scaleFactor : 1 * scaleFactor) - .blur(radius: hasTouch ? 2 * scaleFactor : 0) - ) - .offset(y: -25 * scaleFactor + compression * 15) - - // Weight display - VStack(spacing: 4 * scaleFactor) { - Text(String(format: "%.1f", weight)) - .font(.system(size: 42 * scaleFactor, weight: .bold, design: .monospaced)) - .foregroundStyle(.white) // White numbers - .shadow(color: .white.opacity(hasTouch ? 0.6 : 0), radius: hasTouch ? 4 : 0) // Glow effect when touch - .animation(.easeInOut(duration: 0.2), value: weight) - - Text("grams") - .font(.system(size: 10 * scaleFactor, weight: .medium)) - .foregroundStyle(.white.opacity(0.6)) + // This offset will move the arms and tray down as weight increases + let trayOffset = compression * 200 * scaleFactor + + ZStack { + // MARK: - Body, Face, and Legs + VStack(spacing: 0) { + // Main Body + ZStack { + RoundedRectangle(cornerRadius: 20 * scaleFactor) + .fill(LinearGradient(colors: [Color(red: 0.98, green: 0.98, blue: 1.0), Color(red: 0.88, green: 0.88, blue: 0.92)], startPoint: .top, endPoint: .bottom)) + .frame(width: 180 * scaleFactor, height: 160 * scaleFactor) + .shadow(color: .black.opacity(0.2), radius: 10, x: 0, y: 5) + + // Face + VStack(spacing: 10 * scaleFactor) { + HStack(spacing: 30 * scaleFactor) { + EyeView(size: 20 * scaleFactor, isHappy: weight > 5) + EyeView(size: 20 * scaleFactor, isHappy: weight > 5) + } + + Group { + if weight > 5 { + let smileWidth = 35 * scaleFactor + CGFloat(min(weight, 100) / 5) * scaleFactor + Path { path in + path.move(to: .zero) + path.addQuadCurve(to: CGPoint(x: smileWidth, y: 0), control: CGPoint(x: smileWidth / 2, y: 15 * scaleFactor)) + } + .stroke(.black, style: StrokeStyle(lineWidth: 4 * scaleFactor, lineCap: .round)) + .frame(width: smileWidth) + } else { + Rectangle().fill(.black).frame(width: 20 * scaleFactor, height: 3 * scaleFactor) + } + } + .animation(.spring(response: 0.3, dampingFraction: 0.5), value: weight) + .frame(height: 20 * scaleFactor) + } } - .offset(y: 50 * scaleFactor + compression * 15) + .offset(y: 50 * scaleFactor) // Move body down to make room for tray - // Status Indicator - if hasTouch { - Circle() - .fill(.teal) - .frame(width: 20 * scaleFactor, height: 10 * scaleFactor) - .offset(x: 120 * scaleFactor, y: -70 * scaleFactor + compression * 15) - .animation(.easeInOut(duration: 0.3), value: hasTouch) + // Legs + HStack(spacing: 100 * scaleFactor) { + RoundedRectangle(cornerRadius: 4, style: .continuous).fill(.gray.opacity(0.7)).frame(width: 35 * scaleFactor, height: 20 * scaleFactor) + RoundedRectangle(cornerRadius: 4, style: .continuous).fill(.gray.opacity(0.7)).frame(width: 35 * scaleFactor, height: 20 * scaleFactor) } + .offset(y: 45 * scaleFactor) + } + + // MARK: - Arms + ZStack { + // Left Arm + ArmView(scaleFactor: scaleFactor) + .rotationEffect(.degrees(180)) // Flip it + .offset(x: -80 * scaleFactor, y: 30 * scaleFactor + trayOffset) + + // Right Arm + ArmView(scaleFactor: scaleFactor) + .rotationEffect(.degrees(180)) + .offset(x: 80 * scaleFactor, y: 30 * scaleFactor + trayOffset) + } -// Needle Animation - if weight > 0.5 { - Rectangle() - .fill(Color.white.opacity(0.8)) - .frame(width: 3 * scaleFactor, height: 40 * scaleFactor) - .cornerRadius(2 * scaleFactor) - .offset(y: -25 * scaleFactor + compression * 15) - .rotationEffect( - Angle(degrees: Double(min(weight, maxNeedleWeight) / maxNeedleWeight) * needleAngleRange - (needleAngleRange / 2)) - ) - .shadow(color: .white.opacity(0.3), radius: 2 * scaleFactor) - .animation(.spring(response: 0.5, dampingFraction: 0.7), value: weight) + // MARK: - Tray and Display + ZStack { + // Tray + RoundedRectangle(cornerRadius: 15 * scaleFactor) + .fill(Color(red: 0.85, green: 0.85, blue: 0.90)) + .frame(width: 220 * scaleFactor, height: 80 * scaleFactor) + .shadow(color: .black.opacity(0.1), radius: 5, x: 0, y: -3) + + // Digital Display on the tray + RoundedRectangle(cornerRadius: 8 * scaleFactor) + .fill(.black) + .frame(width: 110 * scaleFactor, height: 40 * scaleFactor) + .overlay( + RoundedRectangle(cornerRadius: 8 * scaleFactor) + .fill(LinearGradient(colors: [.teal.opacity(0.8), .blue.opacity(0.6)], startPoint: .top, endPoint: .bottom)) + .padding(2 * scaleFactor) + ) + + Text(String(format: "%.1f", weight)) + .font(.system(size: 22 * scaleFactor, weight: .heavy, design: .monospaced)) + .foregroundColor(.white) + .shadow(color: .teal, radius: hasTouch ? 4 : 0) + } + .offset(y: -40 * scaleFactor + trayOffset) // Move tray up and animate with offset + } + } +} - // Pivot circle - Circle() - .fill(Color.white.opacity(0.9)) - .frame(width: 10 * scaleFactor, height: 10 * scaleFactor) - .offset(y: -2 * scaleFactor + compression * 15) - .shadow(color: .black.opacity(0.2), radius: 2 * scaleFactor) +// MARK: - Helper Views +struct ArmView: View { + let scaleFactor: CGFloat + + var body: some View { + ZStack { + // Main arm part + RoundedRectangle(cornerRadius: 10 * scaleFactor) + .fill(Color(red: 0.9, green: 0.9, blue: 0.92)) + .frame(width: 30 * scaleFactor, height: 80 * scaleFactor) + + // Hand/Holder part + RoundedRectangle(cornerRadius: 8 * scaleFactor) + .fill(Color(red: 0.85, green: 0.85, blue: 0.90)) + .frame(width: 40 * scaleFactor, height: 20 * scaleFactor) + .offset(y: -40 * scaleFactor) + } + } +} - } - } +struct EyeView: View { + let size: CGFloat + var isHappy: Bool = false - // Scale Legs - HStack(spacing: 160 * scaleFactor) { - ForEach(0..<2, id: \.self) { _ in - Capsule() - .fill(.gray.opacity(0.5)) - .frame(width: 20 * scaleFactor, height: 40 * scaleFactor) - .offset(y: -2 * compression * 3) - .shadow(color: .black.opacity(0.1), radius: 3, x: 0, y: 2) // Subtle shadow - } + var body: some View { + ZStack { + Circle().fill(.black).frame(width: size, height: size) + if isHappy { + Circle().fill(.white).frame(width: size * 0.4, height: size * 0.4).offset(x: -size * 0.15, y: -size * 0.15) + } else { + Circle().fill(.white).frame(width: size * 0.2, height: size * 0.2).offset(x: size * 0.2, y: -size * 0.2) } - .offset(y: -15 * scaleFactor) } - .animation(.spring(response: 0.4, dampingFraction: 0.8), value: compression) } } - +// MARK: - Modifiers struct FocusEffectModifier: ViewModifier { func body(content: Content) -> some View { if #available(macOS 14.0, *) { @@ -276,12 +271,7 @@ struct FocusEffectModifier: ViewModifier { } } - +// MARK: - Preview #Preview { - //dark gradient background - LinearGradient(gradient: Gradient(colors: [Color(red: 0.05, green: 0.05, blue: 0.1), Color(red: 0.0, green: 0.0, blue: 0.05)]), startPoint: .topLeading, endPoint: .bottomTrailing) - .edgesIgnoringSafeArea(.all) - .overlay( - ScaleView() - ) + ScaleView() } From f06413116c3e3f2cc36c4d48c93e325f0389281e Mon Sep 17 00:00:00 2001 From: Anubhav Mishra Date: Sun, 27 Jul 2025 14:45:41 +0530 Subject: [PATCH 4/4] fixed the scaleview --- TrackWeight/ScaleView.swift | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/TrackWeight/ScaleView.swift b/TrackWeight/ScaleView.swift index 75e6fb1..ffddf41 100644 --- a/TrackWeight/ScaleView.swift +++ b/TrackWeight/ScaleView.swift @@ -6,12 +6,7 @@ import SwiftUI import AppKit -// MARK: - ViewModel (Required for the view to work) -// ⚠️ If you have this class defined elsewhere, DELETE this placeholder to avoid errors. - - -// MARK: - Main View struct ScaleView: View { @StateObject private var viewModel = ScaleViewModel() @State private var scaleCompression: CGFloat = 0 @@ -191,7 +186,7 @@ struct FitnessScaleView: View { // Right Arm ArmView(scaleFactor: scaleFactor) - .rotationEffect(.degrees(180)) + .rotationEffect(.degrees(180)) .offset(x: 80 * scaleFactor, y: 30 * scaleFactor + trayOffset) }