@unisat/wallet-state 1.0.4 → 1.1.0
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/LICENSE +0 -5
- package/lib/index.d.mts +2012 -200
- package/lib/index.d.ts +2012 -200
- package/lib/index.js +6951 -570
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +6754 -517
- package/lib/index.mjs.map +1 -1
- package/lib/types/index.d.mts +1 -1
- package/lib/types/index.d.ts +1 -1
- package/package.json +19 -15
- package/src/.DS_Store +0 -0
- package/src/context/ApprovalContext.tsx +27 -0
- package/src/context/DeviceContext.tsx +36 -0
- package/src/context/I18nContext.tsx +14 -172
- package/src/context/NavigationContext.tsx +305 -0
- package/src/context/PriceContext.tsx +2 -2
- package/src/context/StorageContext.tsx +393 -0
- package/src/context/ToolsContext.tsx +50 -0
- package/src/context/WalletContext.tsx +119 -108
- package/src/context/index.ts +17 -2
- package/src/hooks/accounts.ts +11 -5
- package/src/hooks/browser.ts +11 -0
- package/src/hooks/global.ts +170 -7
- package/src/hooks/index.ts +1 -2
- package/src/hooks/settings.ts +28 -37
- package/src/hooks/transactions.ts +28 -155
- package/src/hooks/ui.ts +232 -36
- package/src/index.ts +33 -24
- package/src/reducers/accounts.ts +19 -2
- package/src/reducers/browser.ts +223 -0
- package/src/reducers/global.ts +67 -1
- package/src/reducers/index.ts +1 -0
- package/src/reducers/transactions.ts +0 -9
- package/src/reducers/ui.ts +127 -8
- package/src/types/index.ts +1 -1
- package/src/ui-hooks/index.ts +107 -0
- package/src/ui-hooks/useActionOverviewSectionLogic.ts +150 -0
- package/src/ui-hooks/useAddressTypeScreenLogic.ts +160 -0
- package/src/ui-hooks/useAlkanesBalanceCardLogic.ts +41 -0
- package/src/ui-hooks/useAlkanesCollectionListLogic.ts +68 -0
- package/src/ui-hooks/useAlkanesListLogic.ts +69 -0
- package/src/ui-hooks/useAlkanesNFTListLogic.ts +42 -0
- package/src/ui-hooks/useAlkanesNFTScreenLogic.ts +45 -0
- package/src/ui-hooks/useAlkanesTokenScreenLogic.ts +130 -0
- package/src/ui-hooks/useAmountInputLogic.ts +80 -0
- package/src/ui-hooks/useAnnouncementCardLogic.ts +91 -0
- package/src/ui-hooks/useBRC20BalanceCardLogic.ts +115 -0
- package/src/ui-hooks/useBRC20InscribeTransferLogic.ts +398 -0
- package/src/ui-hooks/useBRC20ListLogic.ts +75 -0
- package/src/ui-hooks/useBRC20ProgListLogic.ts +77 -0
- package/src/ui-hooks/useBRC20SendScreenLogic.ts +411 -0
- package/src/ui-hooks/useBRC20SingleStepScreenLogic.ts +208 -0
- package/src/ui-hooks/useBRC20TokenScreenLogic.ts +469 -0
- package/src/ui-hooks/useBalanceCardLogic.ts +164 -0
- package/src/ui-hooks/useBtcDisplayLogic.ts +16 -0
- package/src/ui-hooks/useCAT20BalanceCardLogic.ts +35 -0
- package/src/ui-hooks/useCAT20ListLogic.ts +83 -0
- package/src/ui-hooks/useCAT20TokenScreenLogic.ts +122 -0
- package/src/ui-hooks/useCAT721ListLogic.ts +68 -0
- package/src/ui-hooks/useCAT721NFTScreenLogic.ts +37 -0
- package/src/ui-hooks/useCreatePasswordScreenLogic.ts +92 -0
- package/src/ui-hooks/useCreateWalletLogicImportWordsStep.ts +299 -0
- package/src/ui-hooks/useEditAccountNameScreenLogic.ts +71 -0
- package/src/ui-hooks/useEditContactScreenLogic.ts +162 -0
- package/src/ui-hooks/useEditWalletNameScreenLogic.ts +58 -0
- package/src/ui-hooks/useExportMnemonicsScreenLogic.ts +75 -0
- package/src/ui-hooks/useExportPrivateKeyScreenLogic.ts +64 -0
- package/src/ui-hooks/useFeeRateBarLogic.ts +303 -0
- package/src/ui-hooks/useInfiniteList.ts +85 -0
- package/src/ui-hooks/useInscriptionListLogic.ts +68 -0
- package/src/ui-hooks/useLockTimePageLogic.ts +43 -0
- package/src/ui-hooks/useNotificationsLogic.ts +115 -0
- package/src/ui-hooks/useOrdinalsInscriptionScreenLogic.ts +130 -0
- package/src/ui-hooks/useRunesBalanceCardLogic.ts +44 -0
- package/src/ui-hooks/useRunesListLogic.ts +74 -0
- package/src/ui-hooks/useRunesTokenScreenLogic.ts +149 -0
- package/src/ui-hooks/useSecurityCardLogic.ts +0 -0
- package/src/ui-hooks/useSendAlkanesNFTScreenLogic.ts +138 -0
- package/src/ui-hooks/useSendAlkanesScreenLogic.ts +192 -0
- package/src/ui-hooks/useSendCAT20ScreenLogic.ts +297 -0
- package/src/ui-hooks/useSendCAT721ScreenLogic.ts +205 -0
- package/src/ui-hooks/useSendOrdinalsInscriptionScreenLogic.ts +137 -0
- package/src/ui-hooks/useSendRunesScreenLogic.ts +172 -0
- package/src/ui-hooks/useSettingsTabScreenLogic.ts +211 -0
- package/src/ui-hooks/useSignMessageLogic.ts +302 -0
- package/src/ui-hooks/useSignPsbtLogic.ts +517 -0
- package/src/ui-hooks/useSplitOrdinalsInscriptionScreenLogic.ts +95 -0
- package/src/ui-hooks/useTxConfirmScreenLogic.ts +47 -0
- package/src/ui-hooks/useTxCreateScreenLogic.ts +161 -0
- package/src/ui-hooks/useTxFailScreenLogic.ts +26 -0
- package/src/ui-hooks/useTxSuccessScreenLogic.ts +33 -0
- package/src/updater/accounts.ts +11 -11
- package/src/utils/bitcoin-utils.ts +17 -8
- package/src/utils/eventBus.ts +2 -1
- package/src/utils/password-utils.ts +78 -0
- package/src/utils/ui-utils.ts +28 -0
- package/src/hooks/approval.ts +0 -72
- package/src/hooks/i18n.ts +0 -53
- package/src/utils/i18n.ts +0 -41
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
|
+
import { useI18n, useWallet } from '../context'
|
|
3
|
+
import { useChain, useFeeRateBar, useUpdateFeeRateBar } from '../hooks'
|
|
4
|
+
import { useAsyncEffect } from '../utils/ui-utils'
|
|
5
|
+
|
|
6
|
+
enum FeeRateType {
|
|
7
|
+
SLOW,
|
|
8
|
+
AVG,
|
|
9
|
+
FAST,
|
|
10
|
+
CUSTOM,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface FeeOption {
|
|
14
|
+
type?: FeeRateType
|
|
15
|
+
title: string
|
|
16
|
+
desc?: string
|
|
17
|
+
feeRate: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const translationKeys = {
|
|
21
|
+
[FeeRateType.SLOW]: { type: FeeRateType.SLOW, title: 'slow', desc: 'feerate_slow_desc' },
|
|
22
|
+
[FeeRateType.AVG]: { type: FeeRateType.AVG, title: 'avg', desc: 'feerate_avg_desc' },
|
|
23
|
+
[FeeRateType.FAST]: { type: FeeRateType.FAST, title: 'fast', desc: 'feerate_fast_desc' },
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
const MAX_FEE_RATE = 10000
|
|
27
|
+
const DEFAULT_FEE_RATE = 1
|
|
28
|
+
const AVG_OPTION_INDEX = 1
|
|
29
|
+
|
|
30
|
+
// Fee rate display rules:
|
|
31
|
+
// if (Fast = Avg = Slow) -> show Fast time (30s) for all
|
|
32
|
+
// if (Fast = Avg) -> show Fast time (30s) for both
|
|
33
|
+
// if (Avg = Slow) -> show Avg time (1.5m) for both
|
|
34
|
+
|
|
35
|
+
function getFractalFeeDesc(
|
|
36
|
+
index: FeeRateType,
|
|
37
|
+
fastRate: number,
|
|
38
|
+
avgRate: number,
|
|
39
|
+
slowRate: number,
|
|
40
|
+
t: (key: string) => string
|
|
41
|
+
): string {
|
|
42
|
+
const { FAST, AVG, SLOW } = FeeRateType
|
|
43
|
+
|
|
44
|
+
// All rates are equal
|
|
45
|
+
if (fastRate === avgRate && avgRate === slowRate) {
|
|
46
|
+
return t('feerate_fast_desc_fb')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Fast equals Avg
|
|
50
|
+
if (fastRate === avgRate) {
|
|
51
|
+
return index === SLOW ? t('feerate_slow_desc_fb') : t('feerate_fast_desc_fb')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Avg equals Slow
|
|
55
|
+
if (avgRate === slowRate && (index === AVG || index === SLOW)) {
|
|
56
|
+
return t('feerate_avg_desc_fb')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Default descriptions
|
|
60
|
+
const descriptions = {
|
|
61
|
+
[FAST]: 'feerate_fast_desc_fb',
|
|
62
|
+
[AVG]: 'feerate_avg_desc_fb',
|
|
63
|
+
[SLOW]: 'feerate_slow_desc_fb',
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return t(descriptions[index] || '')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function formatFeeRateTextKey(
|
|
70
|
+
list: FeeOption[],
|
|
71
|
+
isFractal: boolean,
|
|
72
|
+
supportLowFeeMode: boolean,
|
|
73
|
+
t: (key: string) => string
|
|
74
|
+
): FeeOption[] {
|
|
75
|
+
const { FAST, AVG, SLOW } = FeeRateType
|
|
76
|
+
const fastRate = list[FAST]?.feeRate ?? 0
|
|
77
|
+
const avgRate = list[AVG]?.feeRate ?? 0
|
|
78
|
+
const slowRate = list[SLOW]?.feeRate ?? 0
|
|
79
|
+
|
|
80
|
+
return list.map((option, index) => {
|
|
81
|
+
const keys = translationKeys[index as FeeRateType]
|
|
82
|
+
if (!keys) return option
|
|
83
|
+
|
|
84
|
+
let title = t(keys.title)
|
|
85
|
+
let desc = t(keys.desc)
|
|
86
|
+
|
|
87
|
+
if (isFractal) {
|
|
88
|
+
desc = getFractalFeeDesc(index, fastRate, avgRate, slowRate, t)
|
|
89
|
+
} else {
|
|
90
|
+
if (fastRate === slowRate) {
|
|
91
|
+
desc = t('feerate_fast_desc')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (supportLowFeeMode) {
|
|
95
|
+
if (index === SLOW) {
|
|
96
|
+
title = t('feerate_sub1_title')
|
|
97
|
+
desc = ''
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
...option,
|
|
104
|
+
title,
|
|
105
|
+
desc,
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function useFeeRateBarLogic({ readonly }: { readonly?: boolean }) {
|
|
111
|
+
const wallet = useWallet()
|
|
112
|
+
const [feeOptions, setFeeOptions] = useState<FeeOption[]>([])
|
|
113
|
+
const feeRateBarState = useFeeRateBar()
|
|
114
|
+
const updateFeeRateBar = useUpdateFeeRateBar()
|
|
115
|
+
const feeRateInputVal = feeRateBarState.feeRateInputVal
|
|
116
|
+
const feeOptionIndex = feeRateBarState.feeOptionIndex
|
|
117
|
+
const showCustomInput = feeRateBarState.showCustomInput
|
|
118
|
+
const feeRate = feeRateBarState.feeRate
|
|
119
|
+
const { t, isSpecialLocale } = useI18n()
|
|
120
|
+
const chain = useChain()
|
|
121
|
+
const isFractal = chain.isFractal
|
|
122
|
+
const fontSize = useMemo(() => (isSpecialLocale ? 'xxxs' : 'xxs'), [isSpecialLocale])
|
|
123
|
+
|
|
124
|
+
const [showLowFeeModeTipsPopover, setShowLowFeeModeTipsPopover] = useState(false)
|
|
125
|
+
|
|
126
|
+
const supportLowFeeMode = chain.enableLowFeeMode ?? false
|
|
127
|
+
|
|
128
|
+
useAsyncEffect(async () => {
|
|
129
|
+
const feeSummary = await wallet.getFeeSummary()
|
|
130
|
+
|
|
131
|
+
if (supportLowFeeMode) {
|
|
132
|
+
const lowFeeSummary = await wallet.getLowFeeSummary()
|
|
133
|
+
feeSummary.list[0] = lowFeeSummary.list[1]
|
|
134
|
+
|
|
135
|
+
// try use slow fee rate if it's lower than 1 sat/vB
|
|
136
|
+
if (feeSummary.list[0].feeRate > 1) {
|
|
137
|
+
feeSummary.list[0] = lowFeeSummary.list[0]
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ensure slow fee rate is below 1 sat/vB
|
|
141
|
+
if (feeSummary.list[0].feeRate > 1) {
|
|
142
|
+
feeSummary.list[0].feeRate = 0.1
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const translatedList = formatFeeRateTextKey(feeSummary.list, isFractal, supportLowFeeMode, t)
|
|
147
|
+
const options = readonly
|
|
148
|
+
? translatedList
|
|
149
|
+
: [...translatedList, { type: FeeRateType.CUSTOM, title: t('custom'), feeRate: 0 }]
|
|
150
|
+
|
|
151
|
+
setFeeOptions(options)
|
|
152
|
+
}, [wallet, isFractal, t, supportLowFeeMode, readonly])
|
|
153
|
+
|
|
154
|
+
// Memoize default value to avoid repeated calculations
|
|
155
|
+
const defaultFeeRate = useMemo(
|
|
156
|
+
() => feeOptions[AVG_OPTION_INDEX]?.feeRate ?? DEFAULT_FEE_RATE,
|
|
157
|
+
[feeOptions]
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
let val = defaultFeeRate
|
|
162
|
+
if (feeOptionIndex === FeeRateType.CUSTOM) {
|
|
163
|
+
val = parseFloat(feeRateInputVal) || 0
|
|
164
|
+
} else if (feeOptions.length > 0) {
|
|
165
|
+
val = feeOptions[feeOptionIndex]?.feeRate ?? defaultFeeRate
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (val.toString() == feeRate.toString()) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (val === feeRate) {
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
updateFeeRateBar({ feeRate: val })
|
|
177
|
+
}, [feeOptions, feeOptionIndex, feeRateInputVal, defaultFeeRate, feeRate])
|
|
178
|
+
|
|
179
|
+
const adjustFeeRateInput = useCallback(
|
|
180
|
+
(inputVal: string) => {
|
|
181
|
+
// When user manually changes the input, check if we need to switch to CUSTOM
|
|
182
|
+
const shouldSwitchToCustom = feeOptionIndex !== FeeRateType.CUSTOM && feeOptions.length > 0
|
|
183
|
+
const selectedOption = feeOptions[feeOptionIndex]
|
|
184
|
+
|
|
185
|
+
// If currently on SLOW/AVG/FAST and user changes the value, switch to CUSTOM
|
|
186
|
+
if (shouldSwitchToCustom && selectedOption) {
|
|
187
|
+
const currentValue = selectedOption.feeRate.toString()
|
|
188
|
+
if (inputVal !== currentValue) {
|
|
189
|
+
updateFeeRateBar({
|
|
190
|
+
feeRateInputVal: inputVal,
|
|
191
|
+
feeOptionIndex: FeeRateType.CUSTOM,
|
|
192
|
+
})
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Allow empty input
|
|
198
|
+
if (inputVal === '') {
|
|
199
|
+
updateFeeRateBar({ feeRateInputVal: '' })
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const val = parseFloat(inputVal)
|
|
204
|
+
|
|
205
|
+
// Check if input is a valid number
|
|
206
|
+
if (isNaN(val)) {
|
|
207
|
+
updateFeeRateBar({ feeRateInputVal: '' })
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Allow intermediate input like "0" or "0." for typing "0.1"
|
|
212
|
+
if (inputVal === '0' || inputVal.endsWith('.')) {
|
|
213
|
+
updateFeeRateBar({ feeRateInputVal: inputVal })
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Validate and constrain the value
|
|
218
|
+
if (val <= 0) {
|
|
219
|
+
updateFeeRateBar({ feeRateInputVal: defaultFeeRate.toString() })
|
|
220
|
+
} else if (val > MAX_FEE_RATE) {
|
|
221
|
+
updateFeeRateBar({ feeRateInputVal: MAX_FEE_RATE.toString() })
|
|
222
|
+
} else if (val < 1 && supportLowFeeMode == false) {
|
|
223
|
+
updateFeeRateBar({ feeRateInputVal: '1' })
|
|
224
|
+
} else if (val < 0.1) {
|
|
225
|
+
updateFeeRateBar({ feeRateInputVal: '0.1' })
|
|
226
|
+
} else {
|
|
227
|
+
updateFeeRateBar({ feeRateInputVal: inputVal })
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
[defaultFeeRate, updateFeeRateBar, feeOptionIndex, feeOptions, supportLowFeeMode]
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
const isCustomOption = useCallback((option: FeeOption) => option.type === FeeRateType.CUSTOM, [t])
|
|
234
|
+
|
|
235
|
+
const toggleLowFeeRate = useCallback(async () => {
|
|
236
|
+
const selectedOption = feeOptions[FeeRateType.SLOW]
|
|
237
|
+
updateFeeRateBar({
|
|
238
|
+
feeOptionIndex: FeeRateType.SLOW,
|
|
239
|
+
showCustomInput: true,
|
|
240
|
+
feeRateInputVal: selectedOption.feeRate.toString(),
|
|
241
|
+
})
|
|
242
|
+
}, [feeOptions, updateFeeRateBar, feeRateInputVal])
|
|
243
|
+
|
|
244
|
+
const setFeeOptionIndex = useCallback(
|
|
245
|
+
async (index: number) => {
|
|
246
|
+
const selectedOption = feeOptions[index]
|
|
247
|
+
if (supportLowFeeMode && index === FeeRateType.SLOW) {
|
|
248
|
+
const acceptLowFeeMode = await wallet.getAcceptLowFeeMode()
|
|
249
|
+
if (acceptLowFeeMode === false) {
|
|
250
|
+
setShowLowFeeModeTipsPopover(true)
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (index !== FeeRateType.CUSTOM && selectedOption) {
|
|
255
|
+
// When clicking on SLOW/AVG/FAST, show input and fill with the corresponding value
|
|
256
|
+
updateFeeRateBar({
|
|
257
|
+
feeOptionIndex: index,
|
|
258
|
+
showCustomInput: true,
|
|
259
|
+
feeRateInputVal: selectedOption.feeRate.toString(),
|
|
260
|
+
})
|
|
261
|
+
} else if (index === FeeRateType.CUSTOM) {
|
|
262
|
+
// When clicking on CUSTOM, show input with current custom value or empty
|
|
263
|
+
updateFeeRateBar({
|
|
264
|
+
feeOptionIndex: index,
|
|
265
|
+
showCustomInput: true,
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
[feeOptions, updateFeeRateBar]
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
const toggleCustomInput = useCallback(
|
|
273
|
+
(show: boolean) => {
|
|
274
|
+
updateFeeRateBar({ showCustomInput: show })
|
|
275
|
+
},
|
|
276
|
+
[updateFeeRateBar]
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
const isSub1FeeOptionOn = useMemo(() => {
|
|
280
|
+
if (supportLowFeeMode && feeOptionIndex === FeeRateType.SLOW) {
|
|
281
|
+
return true
|
|
282
|
+
}
|
|
283
|
+
return false
|
|
284
|
+
}, [feeOptionIndex, supportLowFeeMode])
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
feeOptions,
|
|
288
|
+
feeOptionIndex,
|
|
289
|
+
setFeeOptionIndex,
|
|
290
|
+
feeRateInputVal,
|
|
291
|
+
adjustFeeRateInput,
|
|
292
|
+
isCustomOption,
|
|
293
|
+
fontSize,
|
|
294
|
+
isSpecialLocale,
|
|
295
|
+
toggleLowFeeRate,
|
|
296
|
+
showCustomInput: readonly ? false : showCustomInput,
|
|
297
|
+
toggleCustomInput,
|
|
298
|
+
supportLowFeeMode,
|
|
299
|
+
showLowFeeModeTipsPopover,
|
|
300
|
+
setShowLowFeeModeTipsPopover,
|
|
301
|
+
isSub1FeeOptionOn,
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
interface UseInfiniteListOptions<T> {
|
|
4
|
+
fetcher: (page: number, pageSize: number) => Promise<{ list: T[]; total: number }>
|
|
5
|
+
pageSize?: number
|
|
6
|
+
dependencies?: any[]
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function useInfiniteList<T>({
|
|
10
|
+
fetcher,
|
|
11
|
+
pageSize = 20,
|
|
12
|
+
dependencies = [],
|
|
13
|
+
}: UseInfiniteListOptions<T>) {
|
|
14
|
+
const [data, setData] = useState<T[]>([])
|
|
15
|
+
const [total, setTotal] = useState(0)
|
|
16
|
+
const [loading, _setLoading] = useState(false)
|
|
17
|
+
const [hasMore, setHasMore] = useState(true)
|
|
18
|
+
const [page, _setPage] = useState(1)
|
|
19
|
+
|
|
20
|
+
const pageRef = useRef(1)
|
|
21
|
+
const loadingRef = useRef(false)
|
|
22
|
+
|
|
23
|
+
const setLoading = (v: boolean) => {
|
|
24
|
+
loadingRef.current = v
|
|
25
|
+
_setLoading(v)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const setPage = (v: number) => {
|
|
29
|
+
pageRef.current = v
|
|
30
|
+
_setPage(v)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const loadData = useCallback(
|
|
34
|
+
async (isRefresh = false) => {
|
|
35
|
+
if (loadingRef.current) return
|
|
36
|
+
|
|
37
|
+
loadingRef.current = true
|
|
38
|
+
setLoading(true)
|
|
39
|
+
try {
|
|
40
|
+
const pageToLoad = isRefresh ? 1 : pageRef.current
|
|
41
|
+
const { list, total } = await fetcher(pageToLoad, pageSize)
|
|
42
|
+
|
|
43
|
+
setTotal(total)
|
|
44
|
+
setData(prev => (isRefresh ? list : [...prev, ...list]))
|
|
45
|
+
|
|
46
|
+
const loadedCount = (pageToLoad - 1) * pageSize + list.length
|
|
47
|
+
setHasMore(loadedCount < total)
|
|
48
|
+
|
|
49
|
+
const nextPage = isRefresh ? 2 : pageToLoad + 1
|
|
50
|
+
pageRef.current = nextPage
|
|
51
|
+
setPage(nextPage)
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error(e)
|
|
54
|
+
} finally {
|
|
55
|
+
loadingRef.current = false
|
|
56
|
+
setLoading(false)
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
[fetcher, pageSize]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const onRefresh = () => loadData(true)
|
|
63
|
+
const onLoadMore = () => {
|
|
64
|
+
if (hasMore && !loadingRef.current) loadData(false)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
// reset
|
|
69
|
+
setData([])
|
|
70
|
+
setPage(1)
|
|
71
|
+
setHasMore(true)
|
|
72
|
+
|
|
73
|
+
loadData(true)
|
|
74
|
+
}, dependencies)
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
data,
|
|
78
|
+
total,
|
|
79
|
+
loading,
|
|
80
|
+
hasMore,
|
|
81
|
+
page,
|
|
82
|
+
onRefresh,
|
|
83
|
+
onLoadMore,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Inscription } from '@unisat/wallet-shared'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
OrdinalsAssetTabKey,
|
|
6
|
+
useChainType,
|
|
7
|
+
useCurrentAccount,
|
|
8
|
+
useNavigation,
|
|
9
|
+
useOrdinalsAssetTabKey,
|
|
10
|
+
useWallet,
|
|
11
|
+
useWallTabFocusRefresh,
|
|
12
|
+
} from '..'
|
|
13
|
+
import { useInfiniteList } from './useInfiniteList'
|
|
14
|
+
|
|
15
|
+
export function useInscriptionListLogic() {
|
|
16
|
+
const nav = useNavigation()
|
|
17
|
+
const wallet = useWallet()
|
|
18
|
+
const currentAccount = useCurrentAccount()
|
|
19
|
+
const chainType = useChainType()
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
data: items,
|
|
23
|
+
total,
|
|
24
|
+
loading,
|
|
25
|
+
hasMore,
|
|
26
|
+
onRefresh,
|
|
27
|
+
onLoadMore,
|
|
28
|
+
} = useInfiniteList<Inscription>({
|
|
29
|
+
fetcher: async (page, pageSize) => {
|
|
30
|
+
if (currentAccount.address === '') {
|
|
31
|
+
return { list: [], total: 0 }
|
|
32
|
+
}
|
|
33
|
+
const { list, total } = await wallet.getAllInscriptionList(
|
|
34
|
+
currentAccount.address,
|
|
35
|
+
page,
|
|
36
|
+
pageSize
|
|
37
|
+
)
|
|
38
|
+
return { list, total }
|
|
39
|
+
},
|
|
40
|
+
dependencies: [currentAccount.address, chainType],
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const tabKey = useOrdinalsAssetTabKey()
|
|
44
|
+
const isFocus = tabKey === OrdinalsAssetTabKey.ALL
|
|
45
|
+
const lastRefreshTimeRef = useRef<number>(0)
|
|
46
|
+
const walletTabFocusRefresh = useWallTabFocusRefresh()
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!isFocus) return
|
|
49
|
+
|
|
50
|
+
// already refreshed → do nothing
|
|
51
|
+
const alreadyRefreshed = lastRefreshTimeRef.current === walletTabFocusRefresh
|
|
52
|
+
if (alreadyRefreshed) return
|
|
53
|
+
|
|
54
|
+
onRefresh()
|
|
55
|
+
|
|
56
|
+
// mark refreshed
|
|
57
|
+
lastRefreshTimeRef.current = walletTabFocusRefresh
|
|
58
|
+
}, [walletTabFocusRefresh, isFocus])
|
|
59
|
+
|
|
60
|
+
const onClickItem = (item: Inscription) => {
|
|
61
|
+
nav.navigate('OrdinalsInscriptionScreen', {
|
|
62
|
+
inscription: item,
|
|
63
|
+
inscriptionId: item.inscriptionId,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { items, total, loading, hasMore, onRefresh, onLoadMore, onClickItem }
|
|
68
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { getAutoLockTimes } from '@unisat/wallet-shared'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { useI18n, useNavigation, useTools, useWallet } from '../context'
|
|
4
|
+
import { useAppDispatch, useAutoLockTimeId } from '../hooks'
|
|
5
|
+
import { settingsActions } from '../reducers'
|
|
6
|
+
|
|
7
|
+
export function useLockTimePageLogic() {
|
|
8
|
+
const { t } = useI18n()
|
|
9
|
+
const autoLockTimeId = useAutoLockTimeId()
|
|
10
|
+
const autoLockTimes = getAutoLockTimes(t)
|
|
11
|
+
const dispatch = useAppDispatch()
|
|
12
|
+
const wallet = useWallet()
|
|
13
|
+
const tools = useTools()
|
|
14
|
+
const [loading, setLoading] = useState(false)
|
|
15
|
+
const nav = useNavigation()
|
|
16
|
+
|
|
17
|
+
const handleSelectOption = async option => {
|
|
18
|
+
if (loading) return
|
|
19
|
+
|
|
20
|
+
setLoading(true)
|
|
21
|
+
try {
|
|
22
|
+
const lockTimeId = option.id
|
|
23
|
+
await wallet.setAutoLockTimeId(lockTimeId)
|
|
24
|
+
// @ts-ignore SAFE
|
|
25
|
+
dispatch(settingsActions.updateSettings({ autoLockTimeId: lockTimeId }))
|
|
26
|
+
tools.toastSuccess(`${t('the_auto_lock_time_has_been_changed_to')} ${option.label}`)
|
|
27
|
+
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
nav.goBack()
|
|
30
|
+
}, 300)
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Failed to set lock time:', error)
|
|
33
|
+
} finally {
|
|
34
|
+
setLoading(false)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
autoLockTimeId,
|
|
39
|
+
autoLockTimes,
|
|
40
|
+
loading,
|
|
41
|
+
handleSelectOption,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { AnnouncementLinkType, StoredNotification } from '@unisat/wallet-shared'
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
3
|
+
import { useI18n, useNavigation, useTools, useWallet } from 'src/context'
|
|
4
|
+
|
|
5
|
+
export function useUnreadNotificationsCount() {
|
|
6
|
+
const wallet = useWallet()
|
|
7
|
+
const [unreadCount, setUnreadCount] = useState(0)
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const updateUnreadCount = async () => {
|
|
11
|
+
const count = await wallet.getNotificationUnreadCount()
|
|
12
|
+
setUnreadCount(count)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
updateUnreadCount()
|
|
16
|
+
|
|
17
|
+
// Poll every 3 seconds for unread count updates.
|
|
18
|
+
const interval = setInterval(updateUnreadCount, 3000)
|
|
19
|
+
return () => clearInterval(interval)
|
|
20
|
+
}, [wallet])
|
|
21
|
+
|
|
22
|
+
return unreadCount
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useNotificationsLogic() {
|
|
26
|
+
const nav = useNavigation()
|
|
27
|
+
const wallet = useWallet()
|
|
28
|
+
const [notifications, setNotifications] = useState<StoredNotification[]>([])
|
|
29
|
+
const [loading, setLoading] = useState(true)
|
|
30
|
+
|
|
31
|
+
const fetchNotifications = useCallback(async () => {
|
|
32
|
+
try {
|
|
33
|
+
setLoading(true)
|
|
34
|
+
const data = await wallet.getNotifications()
|
|
35
|
+
setNotifications(data)
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Failed to fetch notifications:', error)
|
|
38
|
+
setNotifications([])
|
|
39
|
+
} finally {
|
|
40
|
+
setLoading(false)
|
|
41
|
+
}
|
|
42
|
+
}, [wallet])
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
fetchNotifications()
|
|
46
|
+
}, [fetchNotifications])
|
|
47
|
+
|
|
48
|
+
const handleReadNotification = useCallback(
|
|
49
|
+
async (id: string) => {
|
|
50
|
+
await wallet.readNotification(id)
|
|
51
|
+
// Update local state
|
|
52
|
+
setNotifications(prev => prev.map(n => (n.id === id ? { ...n, readAt: Date.now() } : n)))
|
|
53
|
+
},
|
|
54
|
+
[wallet]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
const tools = useTools()
|
|
58
|
+
const { t } = useI18n()
|
|
59
|
+
const handleReadAll = useCallback(async () => {
|
|
60
|
+
await wallet.readAllNotifications()
|
|
61
|
+
// Update local state
|
|
62
|
+
const now = Date.now()
|
|
63
|
+
setNotifications(prev => prev.map(n => ({ ...n, readAt: now })))
|
|
64
|
+
tools.toastSuccess(t('all_marked_as_read'))
|
|
65
|
+
}, [wallet])
|
|
66
|
+
|
|
67
|
+
const handleDeleteNotification = useCallback(
|
|
68
|
+
async (id: string) => {
|
|
69
|
+
await wallet.deleteNotification(id)
|
|
70
|
+
// Update local state
|
|
71
|
+
setNotifications(prev => prev.filter(n => n.id !== id))
|
|
72
|
+
},
|
|
73
|
+
[wallet]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const unreadCount = notifications.filter(n => n.readAt === undefined).length
|
|
77
|
+
|
|
78
|
+
const handleCardClick = async (notification: StoredNotification) => {
|
|
79
|
+
if (notification.readAt === undefined) {
|
|
80
|
+
await handleReadNotification(notification.id)
|
|
81
|
+
}
|
|
82
|
+
if (notification.link) {
|
|
83
|
+
if (notification.linkType === AnnouncementLinkType.EXTERNAL_LINK) {
|
|
84
|
+
nav.navToUrl(notification.link, true)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
nav.navToUrl(notification.link)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const formatTime = (timestamp: number) => {
|
|
91
|
+
const now = Date.now()
|
|
92
|
+
const diff = now - timestamp
|
|
93
|
+
const minutes = Math.floor(diff / 60000)
|
|
94
|
+
const hours = Math.floor(diff / 3600000)
|
|
95
|
+
const days = Math.floor(diff / 86400000)
|
|
96
|
+
|
|
97
|
+
if (minutes < 1) return t('just_now')
|
|
98
|
+
if (minutes < 60) return String(minutes) + ' ' + t('minutes_ago')
|
|
99
|
+
if (hours < 24) return String(hours) + ' ' + t('hours_ago')
|
|
100
|
+
if (days < 7) return String(days) + ' ' + t('days_ago')
|
|
101
|
+
return new Date(timestamp).toLocaleDateString()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
notifications,
|
|
106
|
+
loading,
|
|
107
|
+
unreadCount,
|
|
108
|
+
handleReadNotification,
|
|
109
|
+
handleReadAll,
|
|
110
|
+
handleDeleteNotification,
|
|
111
|
+
fetchNotifications,
|
|
112
|
+
handleCardClick,
|
|
113
|
+
formatTime,
|
|
114
|
+
}
|
|
115
|
+
}
|