@appsonair.ir/react-native 1.0.11 → 1.0.12

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/lib/sync.js CHANGED
@@ -812,15 +812,29 @@ async function downloadBundle(downloadUrl, deploymentKey, label, expectedSize, o
812
812
  'X-Deployment-Key': deploymentKey,
813
813
  ...(deviceAbi ? { 'X-Device-Abi': deviceAbi } : {}),
814
814
  });
815
+ let lastReportedProgress = 0;
816
+ let lastReportedReceived = 0;
817
+ let lastReportedTotal = totalBytes > 0 ? totalBytes : 0;
815
818
  const reportDownloadProgress = (received, total) => {
816
819
  if (!onProgress)
817
820
  return;
818
- const resolvedTotal = total > 0 ? total : totalBytes;
819
- if (resolvedTotal <= 0) {
820
- onProgress(0, Math.max(received, 0), 0);
821
+ const safeReceived = Math.max(Number(received) || 0, lastReportedReceived);
822
+ let resolvedTotal = total > 0 ? total : totalBytes;
823
+ if (!(resolvedTotal > 0)) {
824
+ resolvedTotal = lastReportedTotal;
825
+ }
826
+ if (resolvedTotal > lastReportedTotal) {
827
+ lastReportedTotal = resolvedTotal;
828
+ }
829
+ // Keep bytes monotonic even when total is still unknown.
830
+ lastReportedReceived = safeReceived;
831
+ if (!(resolvedTotal > 0)) {
832
+ onProgress(lastReportedProgress, safeReceived, 0);
821
833
  return;
822
834
  }
823
- onProgress(Math.min(Math.max(received / resolvedTotal, 0), 0.99), Math.max(received, 0), resolvedTotal);
835
+ const nextProgress = Math.min(Math.max(safeReceived / resolvedTotal, lastReportedProgress), 0.99);
836
+ lastReportedProgress = nextProgress;
837
+ onProgress(nextProgress, safeReceived, resolvedTotal);
824
838
  };
825
839
  let pollTimer = null;
826
840
  if (onProgress && totalBytes > 0) {
@@ -38,6 +38,25 @@ const react_1 = __importStar(require("react"));
38
38
  const react_native_1 = require("react-native");
39
39
  const sync_1 = require("../sync");
40
40
  const types_1 = require("../types");
41
+ /** Never returns "—" so download size labels don't flicker between ticks. */
42
+ function stableFormatBytes(bytes) {
43
+ if (bytes == null || !Number.isFinite(bytes) || bytes < 0) {
44
+ return null;
45
+ }
46
+ if (bytes < 1024) {
47
+ return `${Math.round(bytes)} B`;
48
+ }
49
+ if (bytes < 1024 * 1024) {
50
+ return `${(bytes / 1024).toFixed(1)} KB`;
51
+ }
52
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
53
+ }
54
+ function joinVersionParts(parts) {
55
+ const filtered = parts
56
+ .map((part) => (part == null ? '' : String(part).trim()))
57
+ .filter(Boolean);
58
+ return filtered.length > 0 ? filtered.join(' ') : null;
59
+ }
41
60
  function OTAUpdateModal({ visible, status, progress, message, description, isMandatory, error, showActions, canSkip, updateType, appVersion, buildNumber, currentAppVersion, currentBuildNumber, currentLabel, label, totalBytes, downloadedBytes, title, fontFamily, messages, onUpdate, onSkip, onDismiss, }) {
42
61
  const copy = (0, react_1.useMemo)(() => ({ ...sync_1.defaultMessages, ...messages }), [messages]);
43
62
  const resolvedTitle = (0, react_1.useMemo)(() => {
@@ -59,13 +78,52 @@ function OTAUpdateModal({ visible, status, progress, message, description, isMan
59
78
  ...(customFontStyle ?? null),
60
79
  writingDirection: 'rtl',
61
80
  }), [customFontStyle]);
81
+ const isDownloading = status === types_1.SyncStatus.DOWNLOADING;
82
+ const stableProgressRef = (0, react_1.useRef)({ progress: 0, downloaded: 0, total: 0 });
83
+ (0, react_1.useEffect)(() => {
84
+ if (!isDownloading) {
85
+ stableProgressRef.current = { progress: 0, downloaded: 0, total: 0 };
86
+ return;
87
+ }
88
+ const nextProgress = Math.min(Math.max(Number(progress) || 0, 0), 1);
89
+ const nextDownloaded = Number(downloadedBytes);
90
+ const nextTotal = Number(totalBytes);
91
+ if (nextProgress >= stableProgressRef.current.progress) {
92
+ stableProgressRef.current.progress = nextProgress;
93
+ }
94
+ if (Number.isFinite(nextTotal) && nextTotal > stableProgressRef.current.total) {
95
+ stableProgressRef.current.total = nextTotal;
96
+ }
97
+ if (Number.isFinite(nextDownloaded) &&
98
+ nextDownloaded >= stableProgressRef.current.downloaded) {
99
+ stableProgressRef.current.downloaded = nextDownloaded;
100
+ }
101
+ }, [isDownloading, progress, downloadedBytes, totalBytes]);
62
102
  if (!visible)
63
103
  return null;
64
- const progressPercent = Math.round(Math.min(Math.max(progress, 0), 1) * 100);
104
+ if (isDownloading) {
105
+ const nextProgress = Math.min(Math.max(Number(progress) || 0, 0), 1);
106
+ const nextDownloaded = Number(downloadedBytes);
107
+ const nextTotal = Number(totalBytes);
108
+ if (nextProgress >= stableProgressRef.current.progress) {
109
+ stableProgressRef.current.progress = nextProgress;
110
+ }
111
+ if (Number.isFinite(nextTotal) && nextTotal > stableProgressRef.current.total) {
112
+ stableProgressRef.current.total = nextTotal;
113
+ }
114
+ if (Number.isFinite(nextDownloaded) &&
115
+ nextDownloaded >= stableProgressRef.current.downloaded) {
116
+ stableProgressRef.current.downloaded = nextDownloaded;
117
+ }
118
+ }
119
+ const stableProgress = isDownloading
120
+ ? stableProgressRef.current.progress
121
+ : Math.min(Math.max(Number(progress) || 0, 0), 1);
122
+ const progressPercent = Math.round(stableProgress * 100);
65
123
  const isPrompt = status === types_1.SyncStatus.UPDATE_AVAILABLE;
66
124
  const isDeferredDone = status === types_1.SyncStatus.UPDATE_INSTALLED;
67
125
  const isError = status === types_1.SyncStatus.ERROR;
68
- const showProgressBar = status === types_1.SyncStatus.DOWNLOADING;
126
+ const showProgressBar = isDownloading;
69
127
  const showSpinner = status === types_1.SyncStatus.RESTARTING ||
70
128
  status === types_1.SyncStatus.INSTALLING ||
71
129
  (!showProgressBar && !isPrompt && !isDeferredDone && !isError);
@@ -74,22 +132,42 @@ function OTAUpdateModal({ visible, status, progress, message, description, isMan
74
132
  : updateType === 'bundle'
75
133
  ? copy.versionBundle
76
134
  : null;
135
+ // Show both version number and release label/name when available.
77
136
  const currentVersionText = updateType === 'apk'
78
- ? [currentAppVersion, currentBuildNumber ? `(${currentBuildNumber})` : null]
79
- .filter(Boolean)
80
- .join(' ')
81
- : currentLabel || currentAppVersion || null;
137
+ ? joinVersionParts([
138
+ currentAppVersion,
139
+ currentBuildNumber ? `(${currentBuildNumber})` : null,
140
+ ])
141
+ : joinVersionParts([
142
+ currentAppVersion,
143
+ currentLabel && currentLabel !== currentAppVersion ? currentLabel : null,
144
+ ]);
82
145
  const nextVersionText = updateType === 'apk'
83
- ? [appVersion, buildNumber != null ? `(${buildNumber})` : null]
84
- .filter(Boolean)
85
- .join(' ')
86
- : label || appVersion || null;
146
+ ? joinVersionParts([
147
+ appVersion,
148
+ buildNumber != null ? `(${buildNumber})` : null,
149
+ label && label !== appVersion ? label : null,
150
+ ])
151
+ : joinVersionParts([
152
+ appVersion,
153
+ label && label !== appVersion ? label : null,
154
+ ]);
87
155
  const handleUpdate = () => (onUpdate ? onUpdate() : (0, sync_1.respondToUpdatePrompt)('update'));
88
156
  const handleSkip = () => (onSkip ? onSkip() : (0, sync_1.respondToUpdatePrompt)('skip'));
89
157
  const handleDismiss = () => onDismiss ? onDismiss() : (0, sync_1.respondToUpdatePrompt)('dismiss');
90
- const downloaded = downloadedBytes ?? 0;
91
- const total = totalBytes ?? 0;
92
- const showSizes = showProgressBar && (total > 0 || downloaded > 0);
158
+ const stableDownloaded = showProgressBar
159
+ ? stableProgressRef.current.downloaded
160
+ : downloadedBytes ?? 0;
161
+ const stableTotal = showProgressBar
162
+ ? stableProgressRef.current.total
163
+ : totalBytes ?? 0;
164
+ const downloadedLabel = stableFormatBytes(stableDownloaded) || '0.0 MB';
165
+ const totalLabel = stableFormatBytes(stableTotal);
166
+ const sizeLabel = totalLabel
167
+ ? `${downloadedLabel} / ${totalLabel}`
168
+ : downloadedLabel;
169
+ const showSizes = showProgressBar;
170
+ const showPromptSize = !showProgressBar && stableTotal > 0 && (isPrompt || isError);
93
171
  return (<react_native_1.Modal visible transparent animationType="fade" statusBarTranslucent onRequestClose={() => {
94
172
  if (canSkip)
95
173
  handleSkip();
@@ -134,20 +212,25 @@ function OTAUpdateModal({ visible, status, progress, message, description, isMan
134
212
  </react_native_1.ScrollView>
135
213
  </react_native_1.View>) : null}
136
214
 
137
- {showProgressBar ? (<>
215
+ {showProgressBar ? (<react_native_1.View style={styles.progressBlock}>
138
216
  <react_native_1.View style={styles.progressTrack}>
139
- <react_native_1.View style={[styles.progressFill, { width: `${progressPercent}%` }]}/>
217
+ <react_native_1.View style={[
218
+ styles.progressFill,
219
+ { width: `${Math.max(progressPercent, 2)}%` },
220
+ ]}/>
140
221
  </react_native_1.View>
141
- <react_native_1.Text style={[styles.percent, !fontFamily && styles.percentBold, textStyle]}>
142
- {progressPercent}٪
143
- </react_native_1.Text>
144
- {showSizes ? (<react_native_1.Text style={[styles.sizeText, textStyle]}>
145
- {(0, sync_1.formatBytes)(downloaded)} / {(0, sync_1.formatBytes)(total > 0 ? total : undefined)}
146
- </react_native_1.Text>) : null}
147
- </>) : null}
222
+ <react_native_1.View style={styles.progressMetaRow}>
223
+ <react_native_1.Text style={[styles.percent, !fontFamily && styles.percentBold]}>
224
+ {progressPercent}٪
225
+ </react_native_1.Text>
226
+ {showSizes ? (<react_native_1.Text style={styles.sizeText} numberOfLines={1}>
227
+ {sizeLabel}
228
+ </react_native_1.Text>) : null}
229
+ </react_native_1.View>
230
+ </react_native_1.View>) : null}
148
231
 
149
- {!showProgressBar && total > 0 && (isPrompt || isError) ? (<react_native_1.Text style={[styles.sizeText, textStyle]}>
150
- حجم دانلود: {(0, sync_1.formatBytes)(total)}
232
+ {showPromptSize ? (<react_native_1.Text style={[styles.sizeTextCentered, textStyle]}>
233
+ حجم دانلود: {stableFormatBytes(stableTotal)}
151
234
  </react_native_1.Text>) : null}
152
235
 
153
236
  {showSpinner ? (<react_native_1.ActivityIndicator size="large" color="#2563eb" style={styles.spinner}/>) : null}
@@ -273,29 +356,48 @@ const styles = react_native_1.StyleSheet.create({
273
356
  textAlign: 'center',
274
357
  lineHeight: 20,
275
358
  },
359
+ progressBlock: {
360
+ width: '100%',
361
+ marginTop: 8,
362
+ },
276
363
  progressTrack: {
277
364
  width: '100%',
278
365
  height: 8,
279
366
  backgroundColor: '#e5e7eb',
280
367
  borderRadius: 999,
281
368
  overflow: 'hidden',
282
- marginTop: 8,
283
369
  },
284
370
  progressFill: {
285
371
  height: '100%',
286
372
  backgroundColor: '#2563eb',
287
373
  borderRadius: 999,
288
374
  },
289
- percent: {
375
+ progressMetaRow: {
290
376
  marginTop: 8,
377
+ width: '100%',
378
+ flexDirection: 'row',
379
+ alignItems: 'center',
380
+ justifyContent: 'space-between',
381
+ direction: 'ltr',
382
+ },
383
+ percent: {
291
384
  fontSize: 13,
292
385
  color: '#6b7280',
293
- textAlign: 'center',
386
+ textAlign: 'left',
387
+ writingDirection: 'ltr',
294
388
  },
295
389
  percentBold: {
296
390
  fontWeight: '600',
297
391
  },
298
392
  sizeText: {
393
+ fontSize: 12,
394
+ color: '#9ca3af',
395
+ textAlign: 'right',
396
+ writingDirection: 'ltr',
397
+ flexShrink: 1,
398
+ marginLeft: 8,
399
+ },
400
+ sizeTextCentered: {
299
401
  marginTop: 4,
300
402
  fontSize: 12,
301
403
  color: '#9ca3af',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appsonair.ir/react-native",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "React Native client SDK for self-hosted OTA updates",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",