|
@@ -6,35 +6,77 @@ struct AppTileView: View {
|
|
|
let onTap: () -> Void
|
|
let onTap: () -> Void
|
|
|
|
|
|
|
|
@State private var isHovering = false
|
|
@State private var isHovering = false
|
|
|
|
|
+ @State private var isPressing = false
|
|
|
|
|
|
|
|
var body: some View {
|
|
var body: some View {
|
|
|
Button(action: onTap) {
|
|
Button(action: onTap) {
|
|
|
VStack(spacing: 11) {
|
|
VStack(spacing: 11) {
|
|
|
AppIconView(app: app, size: 64)
|
|
AppIconView(app: app, size: 64)
|
|
|
- .shadow(color: .black.opacity(0.2), radius: 8, x: 0, y: 5)
|
|
|
|
|
|
|
+ .shadow(
|
|
|
|
|
+ color: .black.opacity(isHovering ? 0.34 : 0.2),
|
|
|
|
|
+ radius: isHovering ? 14 : 8,
|
|
|
|
|
+ x: 0,
|
|
|
|
|
+ y: isHovering ? 10 : 5
|
|
|
|
|
+ )
|
|
|
|
|
+ .scaleEffect(isPressing ? 0.96 : 1.0)
|
|
|
|
|
+ .offset(y: isHovering ? -1 : 0)
|
|
|
|
|
|
|
|
Text(app.name)
|
|
Text(app.name)
|
|
|
.font(.system(size: 13, weight: .medium))
|
|
.font(.system(size: 13, weight: .medium))
|
|
|
- .foregroundStyle(.white.opacity(0.95))
|
|
|
|
|
|
|
+ .foregroundStyle(.white.opacity(isHovering ? 1.0 : 0.95))
|
|
|
.lineLimit(1)
|
|
.lineLimit(1)
|
|
|
.truncationMode(.tail)
|
|
.truncationMode(.tail)
|
|
|
.frame(maxWidth: .infinity)
|
|
.frame(maxWidth: .infinity)
|
|
|
|
|
+ .offset(y: isHovering ? -0.5 : 0)
|
|
|
}
|
|
}
|
|
|
.padding(.horizontal, 7)
|
|
.padding(.horizontal, 7)
|
|
|
.padding(.vertical, 10)
|
|
.padding(.vertical, 10)
|
|
|
.background(
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|
|
- .fill(isHovering ? Color.white.opacity(0.08) : Color.clear)
|
|
|
|
|
|
|
+ .fill(
|
|
|
|
|
+ LinearGradient(
|
|
|
|
|
+ colors: isHovering
|
|
|
|
|
+ ? [Color.white.opacity(0.14), Color.white.opacity(0.06)]
|
|
|
|
|
+ : [Color.clear, Color.clear],
|
|
|
|
|
+ startPoint: .topLeading,
|
|
|
|
|
+ endPoint: .bottomTrailing
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
)
|
|
)
|
|
|
.overlay(
|
|
.overlay(
|
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
|
|
- .stroke(isHovering ? Color.white.opacity(0.16) : Color.clear, lineWidth: 1)
|
|
|
|
|
|
|
+ .stroke(
|
|
|
|
|
+ isHovering ? Color.white.opacity(0.24) : Color.clear,
|
|
|
|
|
+ lineWidth: isHovering ? 1.2 : 1
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+ .scaleEffect(isPressing ? 0.98 : (isHovering ? 1.025 : 1.0))
|
|
|
|
|
+ .offset(y: isPressing ? 0 : (isHovering ? -2 : 0))
|
|
|
|
|
+ .shadow(
|
|
|
|
|
+ color: .black.opacity(isHovering ? 0.22 : 0),
|
|
|
|
|
+ radius: isHovering ? 16 : 0,
|
|
|
|
|
+ x: 0,
|
|
|
|
|
+ y: isHovering ? 10 : 0
|
|
|
)
|
|
)
|
|
|
- .animation(.easeOut(duration: 0.15), value: isHovering)
|
|
|
|
|
|
|
+ .contentShape(RoundedRectangle(cornerRadius: 14, style: .continuous))
|
|
|
|
|
+ .animation(.spring(response: 0.24, dampingFraction: 0.88), value: isHovering)
|
|
|
|
|
+ .animation(.easeOut(duration: 0.12), value: isPressing)
|
|
|
}
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
.buttonStyle(.plain)
|
|
|
|
|
+ .simultaneousGesture(
|
|
|
|
|
+ DragGesture(minimumDistance: 0)
|
|
|
|
|
+ .onChanged { _ in
|
|
|
|
|
+ isPressing = true
|
|
|
|
|
+ }
|
|
|
|
|
+ .onEnded { _ in
|
|
|
|
|
+ isPressing = false
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
.onHover { hovering in
|
|
.onHover { hovering in
|
|
|
isHovering = hovering
|
|
isHovering = hovering
|
|
|
|
|
+ if !hovering {
|
|
|
|
|
+ isPressing = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|