diff --git a/Flipcash/Core/Screens/Main/Tips/TipcardView.swift b/Flipcash/Core/Screens/Main/Tips/TipcardView.swift index 53c2a6842..1edf3b608 100644 --- a/Flipcash/Core/Screens/Main/Tips/TipcardView.swift +++ b/Flipcash/Core/Screens/Main/Tips/TipcardView.swift @@ -17,6 +17,13 @@ struct TipcardView: View { /// on the You page (node 9276:4641) and full screen (node 9277:121417). static let aspectRatio: CGFloat = 333.0 / 269.0 + /// The name's size as a fraction of the card's width. Figma draws the name + /// at 17 on the 269-wide card and scales it with the card, so the 302-wide + /// full-screen card gets 19.1 (node 9277:121421) and the 242-wide You-page + /// card 15.3 (node 9276:4645). A fixed size instead left the name looking + /// oversized on the small card and undersized on the big one. + static let nameFraction: CGFloat = 17.0 / 269.0 + /// Explicit because a rendered tree has no container to size against. let size: CGSize let name: String @@ -66,7 +73,7 @@ struct TipcardView: View { .truncationMode(.tail) .multilineTextAlignment(.center) .padding(.horizontal, size.width * 0.08) - .font(.appDisplayXS) + .font(.default(size: nameFontSize, weight: .bold)) .foregroundStyle(Color.textMain) .padding(.top, size.height * 0.06) } @@ -85,6 +92,10 @@ struct TipcardView: View { } } + private var nameFontSize: CGFloat { + size.width * Self.nameFraction + } + private var codeDimension: CGFloat { size.width * 0.68 } diff --git a/Flipcash/Core/Screens/Main/You/YouScreen.swift b/Flipcash/Core/Screens/Main/You/YouScreen.swift index cb66dee8b..5da65e7f8 100644 --- a/Flipcash/Core/Screens/Main/You/YouScreen.swift +++ b/Flipcash/Core/Screens/Main/You/YouScreen.swift @@ -113,12 +113,13 @@ struct YouScreen: View { .frame(width: Self.collapsedCardSize.width, height: Self.collapsedCardSize.height) .overlay { TipcardView( - size: cardSize, + size: Self.drawnCardSize, name: name, avatar: nil, codeData: codeData, tintOpacity: 0.36 ) + .scaleEffect(cardScale) .offset(y: cardOffset) } .onGeometryChange(for: CGRect.self) { $0.frame(in: .global) } action: { cardSlotFrame = $0 } @@ -256,9 +257,21 @@ struct YouScreen: View { height: cardWidth * TipcardView.aspectRatio ) - private var cardSize: CGSize { - let width = isExpanded ? expandedCardWidth : Self.cardWidth - return CGSize(width: width, height: width * TipcardView.aspectRatio) + /// The size the card is always drawn at, whatever it currently measures on + /// screen — the widest it ever gets, so scaling it to size only ever + /// samples the drawing down. + private static let drawnCardSize = CGSize( + width: maxExpandedCardWidth, + height: maxExpandedCardWidth * TipcardView.aspectRatio + ) + + /// The expansion runs as one scale on the drawn card rather than a new size + /// for it to lay itself out at. Laid out, each of the card's metrics is a + /// separate animatable value — and the name's font size is not animatable + /// at all — so the parts arrived at their new sizes at different moments + /// and overlapped mid-flight. Scaled, the card travels as one figure. + private var cardScale: CGFloat { + (isExpanded ? expandedCardWidth : Self.cardWidth) / Self.drawnCardSize.width } /// The expanded width: the design's 302, narrowed only if the screen can't