@umituz/react-native-subscription 2.14.16 → 2.14.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +9 -1
- package/src/presentation/components/details/PremiumDetailsCard.tsx +2 -2
- package/src/presentation/components/details/PremiumStatusBadge.tsx +31 -27
- package/src/presentation/hooks/useSubscriptionSettingsConfig.ts +10 -14
- package/src/presentation/screens/SubscriptionDetailScreen.tsx +80 -96
- package/src/presentation/screens/components/CreditItem.tsx +81 -86
- package/src/presentation/screens/components/CreditsList.tsx +62 -60
- package/src/presentation/screens/components/DevTestSection.tsx +70 -66
- package/src/presentation/screens/components/SubscriptionActions.tsx +40 -66
- package/src/presentation/screens/components/SubscriptionHeader.tsx +138 -144
- package/src/presentation/types/SubscriptionDetailTypes.ts +113 -0
- package/src/presentation/types/SubscriptionSettingsTypes.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.18",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
package/src/index.ts
CHANGED
|
@@ -172,9 +172,17 @@ export {
|
|
|
172
172
|
type SubscriptionDetailScreenProps,
|
|
173
173
|
type SubscriptionDetailConfig,
|
|
174
174
|
type SubscriptionDetailTranslations,
|
|
175
|
+
type DevTestActions,
|
|
176
|
+
type DevToolsConfig,
|
|
175
177
|
} from "./presentation/screens/SubscriptionDetailScreen";
|
|
176
178
|
|
|
177
|
-
export
|
|
179
|
+
export type {
|
|
180
|
+
SubscriptionHeaderProps,
|
|
181
|
+
CreditsListProps,
|
|
182
|
+
CreditItemProps,
|
|
183
|
+
SubscriptionActionsProps,
|
|
184
|
+
DevTestSectionProps,
|
|
185
|
+
} from "./presentation/types/SubscriptionDetailTypes";
|
|
178
186
|
|
|
179
187
|
|
|
180
188
|
// =============================================================================
|
|
@@ -63,10 +63,10 @@ export const PremiumDetailsCard: React.FC<PremiumDetailsCardProps> = ({
|
|
|
63
63
|
|
|
64
64
|
{isPremium && (
|
|
65
65
|
<View style={styles.detailsSection}>
|
|
66
|
-
{isLifetime ? (
|
|
66
|
+
{isLifetime && translations.lifetimeLabel ? (
|
|
67
67
|
<DetailRow
|
|
68
68
|
label={translations.statusLabel}
|
|
69
|
-
value={translations.lifetimeLabel
|
|
69
|
+
value={translations.lifetimeLabel}
|
|
70
70
|
/>
|
|
71
71
|
) : (
|
|
72
72
|
<>
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Displays subscription status as a colored badge
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React from "react";
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
|
-
import { SubscriptionStatusType } from
|
|
9
|
+
import type { SubscriptionStatusType } from "../../../domain/entities/SubscriptionStatus";
|
|
10
|
+
|
|
10
11
|
export type { SubscriptionStatusType };
|
|
11
12
|
|
|
12
13
|
export interface PremiumStatusBadgeProps {
|
|
@@ -17,9 +18,6 @@ export interface PremiumStatusBadgeProps {
|
|
|
17
18
|
canceledLabel: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
/**
|
|
21
|
-
* Badge component showing subscription status
|
|
22
|
-
*/
|
|
23
21
|
export const PremiumStatusBadge: React.FC<PremiumStatusBadgeProps> = ({
|
|
24
22
|
status,
|
|
25
23
|
activeLabel,
|
|
@@ -36,32 +34,38 @@ export const PremiumStatusBadge: React.FC<PremiumStatusBadgeProps> = ({
|
|
|
36
34
|
canceled: canceledLabel,
|
|
37
35
|
};
|
|
38
36
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
const backgroundColor = useMemo(() => {
|
|
38
|
+
const colors: Record<SubscriptionStatusType, string> = {
|
|
39
|
+
active: tokens.colors.success,
|
|
40
|
+
expired: tokens.colors.error,
|
|
41
|
+
none: tokens.colors.textTertiary,
|
|
42
|
+
canceled: tokens.colors.warning,
|
|
43
|
+
};
|
|
44
|
+
return colors[status];
|
|
45
|
+
}, [status, tokens.colors]);
|
|
45
46
|
|
|
46
|
-
const
|
|
47
|
-
|
|
47
|
+
const styles = useMemo(
|
|
48
|
+
() =>
|
|
49
|
+
StyleSheet.create({
|
|
50
|
+
badge: {
|
|
51
|
+
paddingHorizontal: tokens.spacing.sm,
|
|
52
|
+
paddingVertical: tokens.spacing.xs,
|
|
53
|
+
borderRadius: tokens.borderRadius.xs,
|
|
54
|
+
backgroundColor,
|
|
55
|
+
},
|
|
56
|
+
badgeText: {
|
|
57
|
+
fontWeight: "600",
|
|
58
|
+
color: tokens.colors.onPrimary,
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
[tokens, backgroundColor]
|
|
62
|
+
);
|
|
48
63
|
|
|
49
64
|
return (
|
|
50
|
-
<View style={
|
|
51
|
-
<AtomicText type="labelSmall" style={
|
|
52
|
-
{
|
|
65
|
+
<View style={styles.badge}>
|
|
66
|
+
<AtomicText type="labelSmall" style={styles.badgeText}>
|
|
67
|
+
{labels[status]}
|
|
53
68
|
</AtomicText>
|
|
54
69
|
</View>
|
|
55
70
|
);
|
|
56
71
|
};
|
|
57
|
-
|
|
58
|
-
const styles = StyleSheet.create({
|
|
59
|
-
badge: {
|
|
60
|
-
paddingHorizontal: 8,
|
|
61
|
-
paddingVertical: 4,
|
|
62
|
-
borderRadius: 4,
|
|
63
|
-
},
|
|
64
|
-
badgeText: {
|
|
65
|
-
fontWeight: "600",
|
|
66
|
-
},
|
|
67
|
-
});
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Package-driven: all logic handled internally
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { useMemo
|
|
8
|
-
import { Linking } from "react-native";
|
|
7
|
+
import { useMemo } from "react";
|
|
9
8
|
import { useCredits } from "./useCredits";
|
|
9
|
+
import { useSubscriptionStatus } from "./useSubscriptionStatus";
|
|
10
10
|
import { useCustomerInfo } from "../../revenuecat/presentation/hooks/useCustomerInfo";
|
|
11
11
|
import { usePaywallVisibility } from "./usePaywallVisibility";
|
|
12
12
|
import {
|
|
@@ -45,11 +45,15 @@ export const useSubscriptionSettingsConfig = (
|
|
|
45
45
|
|
|
46
46
|
// Internal hooks
|
|
47
47
|
const { credits } = useCredits({ userId, enabled: !!userId });
|
|
48
|
+
const { isPremium: subscriptionActive } = useSubscriptionStatus({
|
|
49
|
+
userId,
|
|
50
|
+
enabled: !!userId,
|
|
51
|
+
});
|
|
48
52
|
const { customerInfo } = useCustomerInfo();
|
|
49
53
|
const { openPaywall } = usePaywallVisibility();
|
|
50
54
|
|
|
51
|
-
// Premium status from
|
|
52
|
-
const isPremium =
|
|
55
|
+
// Premium status from actual RevenueCat subscription
|
|
56
|
+
const isPremium = subscriptionActive;
|
|
53
57
|
|
|
54
58
|
// RevenueCat entitlement info
|
|
55
59
|
const premiumEntitlement = customerInfo?.entitlements.active["premium"];
|
|
@@ -74,14 +78,8 @@ export const useSubscriptionSettingsConfig = (
|
|
|
74
78
|
[expiresAtIso]
|
|
75
79
|
);
|
|
76
80
|
|
|
77
|
-
// Subscription press handler
|
|
78
|
-
const handleSubscriptionPress =
|
|
79
|
-
if (isPremium) {
|
|
80
|
-
Linking.openURL("https://apps.apple.com/account/subscriptions");
|
|
81
|
-
} else {
|
|
82
|
-
openPaywall();
|
|
83
|
-
}
|
|
84
|
-
}, [isPremium, openPaywall]);
|
|
81
|
+
// Subscription press handler - always opens paywall for upgrade
|
|
82
|
+
const handleSubscriptionPress = openPaywall;
|
|
85
83
|
|
|
86
84
|
// Status type
|
|
87
85
|
const statusType: SubscriptionStatusType = isPremium ? "active" : "none";
|
|
@@ -140,7 +138,6 @@ export const useSubscriptionSettingsConfig = (
|
|
|
140
138
|
manageButton: translations.manageButton,
|
|
141
139
|
upgradeButton: translations.upgradeButton,
|
|
142
140
|
},
|
|
143
|
-
onManageSubscription: handleSubscriptionPress,
|
|
144
141
|
onUpgrade: openPaywall,
|
|
145
142
|
},
|
|
146
143
|
}),
|
|
@@ -154,7 +151,6 @@ export const useSubscriptionSettingsConfig = (
|
|
|
154
151
|
daysRemaining,
|
|
155
152
|
willRenew,
|
|
156
153
|
creditsArray,
|
|
157
|
-
handleSubscriptionPress,
|
|
158
154
|
openPaywall,
|
|
159
155
|
]
|
|
160
156
|
);
|
|
@@ -4,112 +4,96 @@
|
|
|
4
4
|
* No business logic - pure presentation
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React from "react";
|
|
8
|
-
import { StyleSheet } from "react-native";
|
|
7
|
+
import React, { useMemo } from "react";
|
|
8
|
+
import { StyleSheet, View } from "react-native";
|
|
9
9
|
import { useAppDesignTokens, ScreenLayout } from "@umituz/react-native-design-system";
|
|
10
10
|
import { SubscriptionHeader } from "./components/SubscriptionHeader";
|
|
11
11
|
import { CreditsList } from "./components/CreditsList";
|
|
12
12
|
import { SubscriptionActions } from "./components/SubscriptionActions";
|
|
13
|
-
import { DevTestSection
|
|
14
|
-
import type {
|
|
15
|
-
import type { CreditInfo } from "../components/details/PremiumDetailsCard";
|
|
13
|
+
import { DevTestSection } from "./components/DevTestSection";
|
|
14
|
+
import type { SubscriptionDetailScreenProps } from "../types/SubscriptionDetailTypes";
|
|
16
15
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
expiresLabel: string;
|
|
25
|
-
purchasedLabel: string;
|
|
26
|
-
lifetimeLabel: string;
|
|
27
|
-
creditsTitle: string;
|
|
28
|
-
remainingLabel: string;
|
|
29
|
-
usageTitle?: string;
|
|
30
|
-
manageButton: string;
|
|
31
|
-
upgradeButton: string;
|
|
32
|
-
creditsResetInfo?: string;
|
|
33
|
-
}
|
|
16
|
+
export type {
|
|
17
|
+
SubscriptionDetailTranslations,
|
|
18
|
+
SubscriptionDetailConfig,
|
|
19
|
+
SubscriptionDetailScreenProps,
|
|
20
|
+
DevTestActions,
|
|
21
|
+
DevToolsConfig,
|
|
22
|
+
} from "../types/SubscriptionDetailTypes";
|
|
34
23
|
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
daysRemaining?: number | null;
|
|
42
|
-
willRenew?: boolean;
|
|
43
|
-
credits?: CreditInfo[];
|
|
44
|
-
translations: SubscriptionDetailTranslations;
|
|
45
|
-
onManageSubscription?: () => void;
|
|
46
|
-
onUpgrade?: () => void;
|
|
47
|
-
devTools?: {
|
|
48
|
-
actions: DevTestActions;
|
|
49
|
-
title?: string;
|
|
50
|
-
};
|
|
51
|
-
}
|
|
24
|
+
export const SubscriptionDetailScreen: React.FC<SubscriptionDetailScreenProps> = ({
|
|
25
|
+
config,
|
|
26
|
+
}) => {
|
|
27
|
+
const tokens = useAppDesignTokens();
|
|
28
|
+
const showCredits = config.credits && config.credits.length > 0;
|
|
29
|
+
const showUpgradeButton = !config.isPremium && config.onUpgrade;
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
31
|
+
const styles = useMemo(
|
|
32
|
+
() =>
|
|
33
|
+
StyleSheet.create({
|
|
34
|
+
content: {
|
|
35
|
+
flexGrow: 1,
|
|
36
|
+
padding: tokens.spacing.lg,
|
|
37
|
+
gap: tokens.spacing.lg,
|
|
38
|
+
},
|
|
39
|
+
cardsContainer: {
|
|
40
|
+
gap: tokens.spacing.lg,
|
|
41
|
+
},
|
|
42
|
+
spacer: {
|
|
43
|
+
flex: 1,
|
|
44
|
+
minHeight: tokens.spacing.xl,
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
[tokens]
|
|
48
|
+
);
|
|
56
49
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
return (
|
|
51
|
+
<ScreenLayout
|
|
52
|
+
scrollable={true}
|
|
53
|
+
edges={["bottom"]}
|
|
54
|
+
backgroundColor={tokens.colors.backgroundPrimary}
|
|
55
|
+
contentContainerStyle={styles.content}
|
|
56
|
+
footer={
|
|
57
|
+
config.devTools ? (
|
|
58
|
+
<DevTestSection
|
|
59
|
+
actions={config.devTools.actions}
|
|
60
|
+
title={config.devTools.title}
|
|
61
|
+
/>
|
|
62
|
+
) : undefined
|
|
63
|
+
}
|
|
64
|
+
>
|
|
65
|
+
<View style={styles.cardsContainer}>
|
|
66
|
+
<SubscriptionHeader
|
|
67
|
+
statusType={config.statusType}
|
|
68
|
+
isPremium={config.isPremium}
|
|
69
|
+
isLifetime={config.isLifetime}
|
|
70
|
+
expirationDate={config.expirationDate}
|
|
71
|
+
purchaseDate={config.purchaseDate}
|
|
72
|
+
daysRemaining={config.daysRemaining}
|
|
73
|
+
translations={config.translations}
|
|
74
|
+
/>
|
|
62
75
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
contentContainerStyle={styles.content}
|
|
69
|
-
footer={
|
|
70
|
-
config.devTools ? (
|
|
71
|
-
<DevTestSection
|
|
72
|
-
actions={config.devTools.actions}
|
|
73
|
-
title={config.devTools.title}
|
|
74
|
-
/>
|
|
75
|
-
) : undefined
|
|
76
|
+
{showCredits && (
|
|
77
|
+
<CreditsList
|
|
78
|
+
credits={config.credits!}
|
|
79
|
+
title={
|
|
80
|
+
config.translations.usageTitle || config.translations.creditsTitle
|
|
76
81
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
expirationDate={config.expirationDate}
|
|
83
|
-
purchaseDate={config.purchaseDate}
|
|
84
|
-
daysRemaining={config.daysRemaining}
|
|
85
|
-
translations={config.translations}
|
|
86
|
-
/>
|
|
82
|
+
description={config.translations.creditsResetInfo}
|
|
83
|
+
remainingLabel={config.translations.remainingLabel}
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
</View>
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
<CreditsList
|
|
90
|
-
credits={config.credits!}
|
|
91
|
-
title={
|
|
92
|
-
config.translations.usageTitle || config.translations.creditsTitle
|
|
93
|
-
}
|
|
94
|
-
description={config.translations.creditsResetInfo}
|
|
95
|
-
remainingLabel={config.translations.remainingLabel}
|
|
96
|
-
/>
|
|
97
|
-
)}
|
|
88
|
+
<View style={styles.spacer} />
|
|
98
89
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
90
|
+
{showUpgradeButton && (
|
|
91
|
+
<SubscriptionActions
|
|
92
|
+
isPremium={config.isPremium}
|
|
93
|
+
upgradeButtonLabel={config.translations.upgradeButton}
|
|
94
|
+
onUpgrade={config.onUpgrade}
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
</ScreenLayout>
|
|
98
|
+
);
|
|
108
99
|
};
|
|
109
|
-
|
|
110
|
-
const styles = StyleSheet.create({
|
|
111
|
-
content: {
|
|
112
|
-
padding: 16,
|
|
113
|
-
gap: 16,
|
|
114
|
-
},
|
|
115
|
-
});
|
|
@@ -3,98 +3,93 @@
|
|
|
3
3
|
* Displays individual credit usage with progress bar
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React from "react";
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
|
-
|
|
10
|
-
interface CreditItemProps {
|
|
11
|
-
label: string;
|
|
12
|
-
current: number;
|
|
13
|
-
total: number;
|
|
14
|
-
remainingLabel?: string;
|
|
15
|
-
}
|
|
9
|
+
import type { CreditItemProps } from "../../types/SubscriptionDetailTypes";
|
|
16
10
|
|
|
17
11
|
export const CreditItem: React.FC<CreditItemProps> = ({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
label,
|
|
13
|
+
current,
|
|
14
|
+
total,
|
|
15
|
+
remainingLabel,
|
|
22
16
|
}) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
const tokens = useAppDesignTokens();
|
|
18
|
+
const percentage = total > 0 ? (current / total) * 100 : 0;
|
|
19
|
+
const isLow = percentage <= 20;
|
|
20
|
+
const isMedium = percentage > 20 && percentage <= 50;
|
|
21
|
+
|
|
22
|
+
const progressColor = useMemo(() => {
|
|
23
|
+
if (isLow) return tokens.colors.error;
|
|
24
|
+
if (isMedium) return tokens.colors.warning;
|
|
25
|
+
return tokens.colors.success;
|
|
26
|
+
}, [isLow, isMedium, tokens.colors]);
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
const styles = useMemo(
|
|
29
|
+
() =>
|
|
30
|
+
StyleSheet.create({
|
|
31
|
+
container: {
|
|
32
|
+
gap: tokens.spacing.sm,
|
|
33
|
+
},
|
|
34
|
+
header: {
|
|
35
|
+
flexDirection: "row",
|
|
36
|
+
justifyContent: "space-between",
|
|
37
|
+
alignItems: "center",
|
|
38
|
+
},
|
|
39
|
+
label: {
|
|
40
|
+
fontWeight: "500",
|
|
41
|
+
},
|
|
42
|
+
badge: {
|
|
43
|
+
paddingHorizontal: tokens.spacing.md,
|
|
44
|
+
paddingVertical: tokens.spacing.xs,
|
|
45
|
+
borderRadius: tokens.borderRadius.md,
|
|
46
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
47
|
+
},
|
|
48
|
+
count: {
|
|
49
|
+
fontWeight: "600",
|
|
50
|
+
},
|
|
51
|
+
progressBar: {
|
|
52
|
+
height: 8,
|
|
53
|
+
borderRadius: tokens.borderRadius.xs,
|
|
54
|
+
overflow: "hidden",
|
|
55
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
56
|
+
},
|
|
57
|
+
progressFill: {
|
|
58
|
+
height: "100%",
|
|
59
|
+
borderRadius: tokens.borderRadius.xs,
|
|
60
|
+
width: `${percentage}%`,
|
|
61
|
+
backgroundColor: progressColor,
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
[tokens, percentage, progressColor]
|
|
65
|
+
);
|
|
33
66
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
]}
|
|
51
|
-
>
|
|
52
|
-
<View
|
|
53
|
-
style={[
|
|
54
|
-
styles.progressFill,
|
|
55
|
-
{
|
|
56
|
-
width: `${percentage}%`,
|
|
57
|
-
backgroundColor: getColor(),
|
|
58
|
-
},
|
|
59
|
-
]}
|
|
60
|
-
/>
|
|
61
|
-
</View>
|
|
62
|
-
<AtomicText type="bodySmall" style={[styles.remaining, { color: tokens.colors.textSecondary }]}>
|
|
63
|
-
{current} {remainingLabel}
|
|
64
|
-
</AtomicText>
|
|
67
|
+
return (
|
|
68
|
+
<View style={styles.container}>
|
|
69
|
+
<View style={styles.header}>
|
|
70
|
+
<AtomicText
|
|
71
|
+
type="bodyMedium"
|
|
72
|
+
style={[styles.label, { color: tokens.colors.textPrimary }]}
|
|
73
|
+
>
|
|
74
|
+
{label}
|
|
75
|
+
</AtomicText>
|
|
76
|
+
<View style={styles.badge}>
|
|
77
|
+
<AtomicText
|
|
78
|
+
type="labelSmall"
|
|
79
|
+
style={[styles.count, { color: progressColor }]}
|
|
80
|
+
>
|
|
81
|
+
{current} / {total}
|
|
82
|
+
</AtomicText>
|
|
65
83
|
</View>
|
|
66
|
-
|
|
84
|
+
</View>
|
|
85
|
+
<View style={styles.progressBar}>
|
|
86
|
+
<View style={styles.progressFill} />
|
|
87
|
+
</View>
|
|
88
|
+
{remainingLabel && (
|
|
89
|
+
<AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary }}>
|
|
90
|
+
{current} {remainingLabel}
|
|
91
|
+
</AtomicText>
|
|
92
|
+
)}
|
|
93
|
+
</View>
|
|
94
|
+
);
|
|
67
95
|
};
|
|
68
|
-
|
|
69
|
-
const styles = StyleSheet.create({
|
|
70
|
-
container: {
|
|
71
|
-
gap: 8,
|
|
72
|
-
},
|
|
73
|
-
header: {
|
|
74
|
-
flexDirection: "row",
|
|
75
|
-
justifyContent: "space-between",
|
|
76
|
-
alignItems: "center",
|
|
77
|
-
},
|
|
78
|
-
label: {
|
|
79
|
-
fontWeight: "500",
|
|
80
|
-
},
|
|
81
|
-
badge: {
|
|
82
|
-
paddingHorizontal: 12,
|
|
83
|
-
paddingVertical: 4,
|
|
84
|
-
borderRadius: 12,
|
|
85
|
-
},
|
|
86
|
-
count: {
|
|
87
|
-
fontWeight: "600",
|
|
88
|
-
},
|
|
89
|
-
progressBar: {
|
|
90
|
-
height: 8,
|
|
91
|
-
borderRadius: 4,
|
|
92
|
-
overflow: "hidden",
|
|
93
|
-
},
|
|
94
|
-
progressFill: {
|
|
95
|
-
height: "100%",
|
|
96
|
-
borderRadius: 4,
|
|
97
|
-
},
|
|
98
|
-
remaining: {
|
|
99
|
-
},
|
|
100
|
-
});
|