listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

PullToClear.swift (1799B)


      1 import SwiftUI
      2 
      3 /// Pull distance at which the indicator signals readiness and completed item clearing triggers.
      4 let pullClearThreshold: CGFloat = 50
      5 
      6 struct PullToClearIndicator: View {
      7     let pullOffset: CGFloat
      8 
      9     private var progress: CGFloat { min(1, pullOffset / pullClearThreshold) }
     10     private var isReady: Bool { pullOffset >= pullClearThreshold }
     11     private let textSlideDistance: CGFloat = 22
     12 
     13     var body: some View {
     14         HStack(spacing: 6) {
     15             ZStack {
     16                 Image(systemName: "checkmark")
     17                     .offset(y: isReady ? 0 : textSlideDistance)
     18                 Image(systemName: "tray")
     19                     .offset(y: isReady ? -textSlideDistance : 0)
     20             }
     21             .frame(width: 26, height: textSlideDistance, alignment: .leading)
     22             .clipped()
     23             .foregroundStyle(.secondary)
     24             .font(.system(size: 17))
     25             .fontWeight(.semibold)
     26             .animation(.easeInOut(duration: 0.15), value: isReady)
     27             ZStack(alignment: .leading) {
     28                 Text("Release to clear")
     29                     .offset(y: isReady ? 0 : textSlideDistance)
     30                 Text("Clear completed")
     31                     .offset(y: isReady ? -textSlideDistance : 0)
     32             }
     33             .foregroundStyle(.secondary)
     34             .font(ItemRowMetrics.hintSUI)
     35             .frame(height: textSlideDistance, alignment: .topLeading)
     36             .clipped()
     37             .animation(.easeInOut(duration: 0.15), value: isReady)
     38         }
     39         .frame(maxWidth: .infinity)
     40         .frame(height: 56)
     41         // Reveal from the bottom upward as the user pulls
     42         .frame(height: min(pullOffset, 56), alignment: .bottom)
     43         .clipped()
     44         .opacity(Double(progress))
     45         .allowsHitTesting(false)
     46     }
     47 }