add support button, icon view
This commit is contained in:
@@ -357,6 +357,9 @@
|
|||||||
<string name="profile_limited_features">Limited features</string>
|
<string name="profile_limited_features">Limited features</string>
|
||||||
<string name="profile_upgrade_to_pro">Upgrade to Pro</string>
|
<string name="profile_upgrade_to_pro">Upgrade to Pro</string>
|
||||||
<string name="profile_manage_subscription">Manage your subscription in the Google Play Store</string>
|
<string name="profile_manage_subscription">Manage your subscription in the Google Play Store</string>
|
||||||
|
<string name="profile_support">Support</string>
|
||||||
|
<string name="profile_contact_support">Contact Support</string>
|
||||||
|
<string name="profile_contact_support_subtitle">Get help with your account</string>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<string name="settings_title">Settings</string>
|
<string name="settings_title">Settings</string>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ package com.example.casera.network
|
|||||||
*/
|
*/
|
||||||
object ApiConfig {
|
object ApiConfig {
|
||||||
// ⚠️ CHANGE THIS TO TOGGLE ENVIRONMENT ⚠️
|
// ⚠️ CHANGE THIS TO TOGGLE ENVIRONMENT ⚠️
|
||||||
val CURRENT_ENV = Environment.LOCAL
|
val CURRENT_ENV = Environment.DEV
|
||||||
|
|
||||||
enum class Environment {
|
enum class Environment {
|
||||||
LOCAL,
|
LOCAL,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -250,6 +251,48 @@ fun ProfileScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contact Support Section
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
uriHandler.openUri("mailto:caseraSupport@treymail.com?subject=Casera%20Support%20Request")
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(AppRadius.md),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(AppSpacing.lg),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(AppSpacing.xs)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.profile_contact_support),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.SemiBold
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(Res.string.profile_contact_support_subtitle),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Email,
|
||||||
|
contentDescription = stringResource(Res.string.profile_contact_support),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Subscription Section - Only show if limitations are enabled
|
// Subscription Section - Only show if limitations are enabled
|
||||||
if (currentSubscription?.limitationsEnabled == true) {
|
if (currentSubscription?.limitationsEnabled == true) {
|
||||||
Divider(modifier = Modifier.padding(vertical = AppSpacing.sm))
|
Divider(modifier = Modifier.padding(vertical = AppSpacing.sm))
|
||||||
|
|||||||
@@ -545,6 +545,11 @@ enum L10n {
|
|||||||
static var emailNotifications: String { String(localized: "profile_email_notifications") }
|
static var emailNotifications: String { String(localized: "profile_email_notifications") }
|
||||||
static var emailTaskCompleted: String { String(localized: "profile_email_task_completed") }
|
static var emailTaskCompleted: String { String(localized: "profile_email_task_completed") }
|
||||||
static var emailTaskCompletedDescription: String { String(localized: "profile_email_task_completed_description") }
|
static var emailTaskCompletedDescription: String { String(localized: "profile_email_task_completed_description") }
|
||||||
|
|
||||||
|
// Support
|
||||||
|
static var support: String { String(localized: "profile_support") }
|
||||||
|
static var contactSupport: String { String(localized: "profile_contact_support") }
|
||||||
|
static var contactSupportSubtitle: String { String(localized: "profile_contact_support_subtitle") }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Settings
|
// MARK: - Settings
|
||||||
|
|||||||
@@ -19372,6 +19372,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"profile_support" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Support"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile_contact_support" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Contact Support"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile_contact_support_subtitle" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : {
|
||||||
|
"stringUnit" : {
|
||||||
|
"state" : "translated",
|
||||||
|
"value" : "Get help with your account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"profile_subscription" : {
|
"profile_subscription" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@@ -139,6 +139,22 @@ struct ProfileTabView: View {
|
|||||||
}
|
}
|
||||||
.listRowBackground(Color.appBackgroundSecondary)
|
.listRowBackground(Color.appBackgroundSecondary)
|
||||||
|
|
||||||
|
Section(L10n.Profile.support) {
|
||||||
|
Button(action: {
|
||||||
|
sendSupportEmail()
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Label(L10n.Profile.contactSupport, systemImage: "envelope")
|
||||||
|
.foregroundColor(Color.appTextPrimary)
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "arrow.up.right")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(Color.appTextSecondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowBackground(Color.appBackgroundSecondary)
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
showingLogoutAlert = true
|
showingLogoutAlert = true
|
||||||
@@ -193,4 +209,14 @@ struct ProfileTabView: View {
|
|||||||
Text(L10n.Profile.purchasesRestoredMessage)
|
Text(L10n.Profile.purchasesRestoredMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func sendSupportEmail() {
|
||||||
|
let email = "caseraSupport@treymail.com"
|
||||||
|
let subject = "Casera Support Request"
|
||||||
|
let urlString = "mailto:\(email)?subject=\(subject.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? subject)"
|
||||||
|
|
||||||
|
if let url = URL(string: urlString) {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
323
iosApp/iosApp/Subviews/Common/MyCribIconView.swift
Normal file
323
iosApp/iosApp/Subviews/Common/MyCribIconView.swift
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// SVG viewBox dimensions for coordinate conversion
|
||||||
|
private let svgSize: CGFloat = 512
|
||||||
|
|
||||||
|
/// Optical center adjustment (checkmark visual weight is left-heavy)
|
||||||
|
private let opticalCenterOffset: CGFloat = 8
|
||||||
|
|
||||||
|
/// Animatable house roof path (right side)
|
||||||
|
struct HouseRightPath: Shape {
|
||||||
|
var trimEnd: CGFloat = 1.0
|
||||||
|
|
||||||
|
var animatableData: CGFloat {
|
||||||
|
get { trimEnd }
|
||||||
|
set { trimEnd = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
let scale = min(rect.width, rect.height) / svgSize
|
||||||
|
let ox = opticalCenterOffset * scale // optical offset
|
||||||
|
|
||||||
|
var path = Path()
|
||||||
|
// M256 108 L375 200 Q392 212 392 235 L392 345 Q392 375 362 375 L335 375
|
||||||
|
path.move(to: CGPoint(x: 256 * scale + ox, y: 108 * scale))
|
||||||
|
path.addLine(to: CGPoint(x: 375 * scale + ox, y: 200 * scale))
|
||||||
|
path.addQuadCurve(
|
||||||
|
to: CGPoint(x: 392 * scale + ox, y: 235 * scale),
|
||||||
|
control: CGPoint(x: 392 * scale + ox, y: 212 * scale)
|
||||||
|
)
|
||||||
|
path.addLine(to: CGPoint(x: 392 * scale + ox, y: 345 * scale))
|
||||||
|
path.addQuadCurve(
|
||||||
|
to: CGPoint(x: 362 * scale + ox, y: 375 * scale),
|
||||||
|
control: CGPoint(x: 392 * scale + ox, y: 375 * scale)
|
||||||
|
)
|
||||||
|
path.addLine(to: CGPoint(x: 335 * scale + ox, y: 375 * scale))
|
||||||
|
|
||||||
|
return path.trimmedPath(from: 0, to: trimEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animatable house roof path (left side)
|
||||||
|
struct HouseLeftPath: Shape {
|
||||||
|
var trimEnd: CGFloat = 1.0
|
||||||
|
|
||||||
|
var animatableData: CGFloat {
|
||||||
|
get { trimEnd }
|
||||||
|
set { trimEnd = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
let scale = min(rect.width, rect.height) / svgSize
|
||||||
|
let ox = opticalCenterOffset * scale // optical offset
|
||||||
|
|
||||||
|
var path = Path()
|
||||||
|
// M256 108 L137 200 Q120 212 120 235 L120 345 Q120 375 150 375 L177 375
|
||||||
|
path.move(to: CGPoint(x: 256 * scale + ox, y: 108 * scale))
|
||||||
|
path.addLine(to: CGPoint(x: 137 * scale + ox, y: 200 * scale))
|
||||||
|
path.addQuadCurve(
|
||||||
|
to: CGPoint(x: 120 * scale + ox, y: 235 * scale),
|
||||||
|
control: CGPoint(x: 120 * scale + ox, y: 212 * scale)
|
||||||
|
)
|
||||||
|
path.addLine(to: CGPoint(x: 120 * scale + ox, y: 345 * scale))
|
||||||
|
path.addQuadCurve(
|
||||||
|
to: CGPoint(x: 150 * scale + ox, y: 375 * scale),
|
||||||
|
control: CGPoint(x: 120 * scale + ox, y: 375 * scale)
|
||||||
|
)
|
||||||
|
path.addLine(to: CGPoint(x: 177 * scale + ox, y: 375 * scale))
|
||||||
|
|
||||||
|
return path.trimmedPath(from: 0, to: trimEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animatable checkmark path
|
||||||
|
struct CheckmarkPath: Shape {
|
||||||
|
var trimEnd: CGFloat = 1.0
|
||||||
|
|
||||||
|
var animatableData: CGFloat {
|
||||||
|
get { trimEnd }
|
||||||
|
set { trimEnd = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
let scale = min(rect.width, rect.height) / svgSize
|
||||||
|
let ox = opticalCenterOffset * scale // optical offset
|
||||||
|
|
||||||
|
var path = Path()
|
||||||
|
// M175 320 L235 380 L355 250
|
||||||
|
path.move(to: CGPoint(x: 175 * scale + ox, y: 320 * scale))
|
||||||
|
path.addLine(to: CGPoint(x: 235 * scale + ox, y: 380 * scale))
|
||||||
|
path.addLine(to: CGPoint(x: 355 * scale + ox, y: 250 * scale))
|
||||||
|
|
||||||
|
return path.trimmedPath(from: 0, to: trimEnd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animatable window (rounded rectangle)
|
||||||
|
struct WindowShape: Shape {
|
||||||
|
var scale: CGFloat = 1.0
|
||||||
|
|
||||||
|
var animatableData: CGFloat {
|
||||||
|
get { scale }
|
||||||
|
set { scale = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
let viewScale = min(rect.width, rect.height) / svgSize
|
||||||
|
let ox = opticalCenterOffset * viewScale // optical offset
|
||||||
|
|
||||||
|
// Window at (230, 175) with size 52x52 and corner radius 12
|
||||||
|
let windowX: CGFloat = 230
|
||||||
|
let windowY: CGFloat = 175
|
||||||
|
let windowSize: CGFloat = 52
|
||||||
|
let cornerRadius: CGFloat = 12
|
||||||
|
|
||||||
|
// Center point of window (with optical offset)
|
||||||
|
let centerX = (windowX + windowSize / 2) * viewScale + ox
|
||||||
|
let centerY = (windowY + windowSize / 2) * viewScale
|
||||||
|
|
||||||
|
// Scaled dimensions
|
||||||
|
let scaledSize = windowSize * viewScale * scale
|
||||||
|
let scaledRadius = cornerRadius * viewScale * scale
|
||||||
|
|
||||||
|
let windowRect = CGRect(
|
||||||
|
x: centerX - scaledSize / 2,
|
||||||
|
y: centerY - scaledSize / 2,
|
||||||
|
width: scaledSize,
|
||||||
|
height: scaledSize
|
||||||
|
)
|
||||||
|
|
||||||
|
return Path(roundedRect: windowRect, cornerRadius: scaledRadius)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Background rounded rectangle shape
|
||||||
|
struct BackgroundShape: Shape {
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
let scale = min(rect.width, rect.height) / svgSize
|
||||||
|
|
||||||
|
// rect at (24, 24) with 464x464 size and rx/ry=100
|
||||||
|
let bgRect = CGRect(
|
||||||
|
x: 24 * scale,
|
||||||
|
y: 24 * scale,
|
||||||
|
width: 464 * scale,
|
||||||
|
height: 464 * scale
|
||||||
|
)
|
||||||
|
|
||||||
|
return Path(roundedRect: bgRect, cornerRadius: 100 * scale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MyCrib icon view with all paths for animation
|
||||||
|
struct MyCribIconView: View {
|
||||||
|
// Animation progress values (0 to 1)
|
||||||
|
var houseLeftProgress: CGFloat = 1.0
|
||||||
|
var houseRightProgress: CGFloat = 1.0
|
||||||
|
var windowScale: CGFloat = 1.0
|
||||||
|
var checkmarkProgress: CGFloat = 1.0
|
||||||
|
var showBackground: Bool = true
|
||||||
|
var backgroundOpacity: Double = 1.0
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
var backgroundColor: Color = Color(red: 0.96, green: 0.55, blue: 0.24) // #F58D3D
|
||||||
|
var foregroundColor: Color = Color(red: 1.0, green: 0.96, blue: 0.92) // #FFF5EB
|
||||||
|
|
||||||
|
// Stroke widths (scaled from SVG)
|
||||||
|
private let houseStrokeWidth: CGFloat = 28
|
||||||
|
private let checkmarkStrokeWidth: CGFloat = 32
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
let size = min(geometry.size.width, geometry.size.height)
|
||||||
|
let scale = size / svgSize
|
||||||
|
// Center offset to position content in middle of available space
|
||||||
|
let offsetX = (geometry.size.width - size) / 2
|
||||||
|
let offsetY = (geometry.size.height - size) / 2
|
||||||
|
|
||||||
|
ZStack {
|
||||||
|
// Background
|
||||||
|
if showBackground {
|
||||||
|
BackgroundShape()
|
||||||
|
.fill(
|
||||||
|
LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Color(red: 1.0, green: 0.64, blue: 0.28), // #FFA347
|
||||||
|
Color(red: 0.96, green: 0.51, blue: 0.20) // #F58233
|
||||||
|
],
|
||||||
|
startPoint: .top,
|
||||||
|
endPoint: .bottom
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.opacity(backgroundOpacity)
|
||||||
|
.shadow(color: .black.opacity(0.15), radius: 12 * scale, x: 0, y: 8 * scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// House left side
|
||||||
|
HouseLeftPath(trimEnd: houseLeftProgress)
|
||||||
|
.stroke(
|
||||||
|
foregroundColor,
|
||||||
|
style: StrokeStyle(
|
||||||
|
lineWidth: houseStrokeWidth * scale,
|
||||||
|
lineCap: .round,
|
||||||
|
lineJoin: .round
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// House right side
|
||||||
|
HouseRightPath(trimEnd: houseRightProgress)
|
||||||
|
.stroke(
|
||||||
|
foregroundColor,
|
||||||
|
style: StrokeStyle(
|
||||||
|
lineWidth: houseStrokeWidth * scale,
|
||||||
|
lineCap: .round,
|
||||||
|
lineJoin: .round
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Window
|
||||||
|
WindowShape(scale: windowScale)
|
||||||
|
.fill(foregroundColor)
|
||||||
|
|
||||||
|
// Checkmark
|
||||||
|
CheckmarkPath(trimEnd: checkmarkProgress)
|
||||||
|
.stroke(
|
||||||
|
foregroundColor,
|
||||||
|
style: StrokeStyle(
|
||||||
|
lineWidth: checkmarkStrokeWidth * scale,
|
||||||
|
lineCap: .round,
|
||||||
|
lineJoin: .round
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.offset(x: offsetX, y: offsetY)
|
||||||
|
}
|
||||||
|
.aspectRatio(1, contentMode: .fit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Animated version with built-in draw animation
|
||||||
|
struct AnimatedMyCribIconView: View {
|
||||||
|
@State private var houseLeftProgress: CGFloat = 0
|
||||||
|
@State private var houseRightProgress: CGFloat = 0
|
||||||
|
@State private var windowScale: CGFloat = 0
|
||||||
|
@State private var checkmarkProgress: CGFloat = 0
|
||||||
|
@State private var backgroundOpacity: Double = 0
|
||||||
|
|
||||||
|
var animationDuration: Double = 0.5
|
||||||
|
var staggerDelay: Double = 0.15
|
||||||
|
var showBackground: Bool = true
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
MyCribIconView(
|
||||||
|
houseLeftProgress: houseLeftProgress,
|
||||||
|
houseRightProgress: houseRightProgress,
|
||||||
|
windowScale: windowScale,
|
||||||
|
checkmarkProgress: checkmarkProgress,
|
||||||
|
showBackground: showBackground,
|
||||||
|
backgroundOpacity: backgroundOpacity
|
||||||
|
)
|
||||||
|
.onAppear {
|
||||||
|
animateIn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateIn() {
|
||||||
|
// Background fade in
|
||||||
|
withAnimation(.easeOut(duration: animationDuration * 0.5)) {
|
||||||
|
backgroundOpacity = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// House sides draw together
|
||||||
|
withAnimation(.easeOut(duration: animationDuration).delay(staggerDelay)) {
|
||||||
|
houseLeftProgress = 1.0
|
||||||
|
houseRightProgress = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window pops in
|
||||||
|
withAnimation(.spring(response: 0.4, dampingFraction: 0.6).delay(staggerDelay * 2)) {
|
||||||
|
windowScale = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkmark draws
|
||||||
|
withAnimation(.easeOut(duration: animationDuration * 0.8).delay(staggerDelay * 3)) {
|
||||||
|
checkmarkProgress = 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset() {
|
||||||
|
houseLeftProgress = 0
|
||||||
|
houseRightProgress = 0
|
||||||
|
windowScale = 0
|
||||||
|
checkmarkProgress = 0
|
||||||
|
backgroundOpacity = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Static Icon") {
|
||||||
|
MyCribIconView()
|
||||||
|
.frame(width: 200, height: 200)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Animated Icon") {
|
||||||
|
AnimatedMyCribIconView()
|
||||||
|
.frame(width: 200, height: 200)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Icon Without Background") {
|
||||||
|
MyCribIconView(showBackground: false)
|
||||||
|
.frame(width: 200, height: 200)
|
||||||
|
.padding()
|
||||||
|
.background(Color.orange)
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview("Custom Colors") {
|
||||||
|
MyCribIconView(
|
||||||
|
backgroundColor: .blue,
|
||||||
|
foregroundColor: .white
|
||||||
|
)
|
||||||
|
.frame(width: 200, height: 200)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user