/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
shared/tracker/assertion.tsx
497 строк
15 KB
chrisnojima
refactor(styles): theme via hooks, no android remount (#29515)
08 авг 2026, 02:24
Не верифицирован
08 авг 2026, 02:24
a41d38e
Код
Авторство
О чём код?
import type * as React from 'react' import * as C from '@/constants' import {useCurrentUserState} from '@/stores/current-user' import * as T from '@/constants/types' import {openURL as openUrl} from '@/util/misc' import * as Kb from '@/common-adapters' import {SiteIcon} from '@/profile/generic/site-icon' import {formatTimeForAssertionPopup} from '@/util/timestamp' import {useColorScheme} from 'react-native' import {navToProfile} from '@/constants/router' import {copyToClipboard} from '@/util/storeless-actions' import {assertionColorToColor, assertionColorToTextColor, stateToIcon} from './model' type OwnProps = { assertion: T.Tracker.Assertion isSuggestion?: boolean notAUser?: boolean onRefresh: () => void stellarHidden?: boolean username: string } const Assertion = (ownProps: OwnProps) => { const styles = useStyles() const theme = Kb.Styles.useTheme() const isYours = useCurrentUserState(s => ownProps.username === s.username) const {assertion, isSuggestion = false, notAUser = false, onRefresh, stellarHidden = false} = ownProps const {color, metas: _metas, proofURL, sigID, siteIcon} = assertion const {siteIconDarkmode, siteIconFull, siteIconFullDarkmode, siteURL, state, timestamp, type, value} = assertion const hideStellar = C.useRPC(T.RPCGen.apiserverPostRpcPromise) const recheckProof = C.useRPC(T.RPCGen.proveCheckProofRpcPromise) const navigateAppend = C.Router2.navigateAppend const _onCreateProof = () => navigateAppend({name: 'profileProofsList', params: {platform: type}}) const onHideStellar = (hidden: boolean) => { hideStellar( [{args: [{key: 'hidden', value: hidden ? '1' : '0'}], endpoint: 'stellar/hidden'}, C.waitingKeyTracker], () => {}, () => {} ) } const onRecheck = () => { recheckProof( [{sigID}, C.waitingKeyProfile], () => { navToProfile(ownProps.username) onRefresh() }, () => {} ) } const _onRevoke = () => { navigateAppend({ name: 'profileRevoke', params: { icon: siteIconFull, kid: assertion.kid || undefined, platform: type as T.More.PlatformsExpandedType, platformHandle: value, proofId: sigID, }, }) } const metas = _metas.map(({color, label}) => ({color, label})) const onCreateProof = notAUser ? undefined : isSuggestion ? _onCreateProof : undefined const openProof = () => { void openUrl(proofURL) } const openSite = () => { void openUrl(siteURL) } const onShowProof = notAUser || !proofURL ? undefined : openProof const onShowSite = notAUser || !siteURL ? undefined : openSite const {header, items} = (() => { if (!isYours || isSuggestion) { return {} } const onRevoke = type === 'stellar' ? { danger: true, onClick: () => onHideStellar(!stellarHidden), title: `${stellarHidden ? 'Show' : 'Hide'} Stellar address on profile`, } : { danger: true, onClick: _onRevoke, title: type === 'pgp' ? 'Drop' : 'Revoke', } if (metas.find(m => m.label === 'unreachable')) { return { header: ( <Kb.Text center={true} type="BodySmallSemibold" style={styles.popupHeaderTextRed}> Your proof could not be found, and Keybase has stopped checking. How would you like to proceed? </Kb.Text> ), items: [ {onClick: onShowProof, title: 'View proof'}, {onClick: onRecheck, title: 'I fixed it - recheck'}, onRevoke, ], } } if (metas.find(m => m.label === 'pending')) { let pendingMessage: undefined | string switch (type) { case 'hackernews': pendingMessage = 'Your proof is pending. Hacker News caches its bios, so it might take a few hours before your proof gets verified.' break case 'dns': pendingMessage = 'Your proof is pending. DNS proofs can take a few hours to recognize.' break } return { header: pendingMessage ? ( <Kb.Text center={true} type="BodySmallSemibold" style={styles.popupHeaderTextBlue}> {pendingMessage} </Kb.Text> ) : null, items: [onRevoke], } } return { header: ( <Kb.Box2 direction="vertical" gap="tiny" centerChildren={true} style={styles.menuHeader} fullWidth={true} > <Kb.Box2 direction="vertical" relative={true}> <AssertionSiteIcon full={true} siteIconFullDarkmode={siteIconFullDarkmode} siteIconFull={siteIconFull} siteIconDarkmode={siteIconDarkmode} siteIcon={siteIcon} onCreateProof={onCreateProof} onShowProof={onShowProof} isSuggestion={isSuggestion} /> <Kb.ImageIcon type={stateToDecorationIcon(state)} style={styles.siteIconFullDecoration} /> </Kb.Box2> {!!timestamp && ( <> <Kb.Text type="BodySmall">Posted on</Kb.Text> <Kb.Text center={true} type="BodySmall"> {formatTimeForAssertionPopup(timestamp)} </Kb.Text> </> )} </Kb.Box2> ), items: [{onClick: onShowProof, title: `View ${proofTypeToDesc(type)}`}, onRevoke], } })() const makePopup = (p: Kb.Popup2Parms) => { const {attachTo, hidePopup} = p return items ? ( <Kb.FloatingMenu closeOnSelect={true} visible={true} onHidden={hidePopup} attachTo={attachTo} position="bottom right" containerStyle={styles.floatingMenu} header={header} items={items} /> ) : null } const {showPopup, popup, popupAnchor} = Kb.usePopup2(makePopup) const tooltip = state === 'valid' || state === 'revoked' ? 'View proof' : undefined return ( <Kb.Box2 className={notAUser ? undefined : 'hover-container'} ref={popupAnchor} direction="vertical" noShrink={true} style={styles.container} fullWidth={true} > <Kb.Box2 alignItems="flex-start" direction="horizontal" gap="tiny" fullWidth={true} gapStart={true} gapEnd={true} > <AssertionSiteIcon full={false} siteIconFullDarkmode={siteIconFullDarkmode} siteIconFull={siteIconFull} siteIconDarkmode={siteIconDarkmode} siteIcon={siteIcon} onCreateProof={onCreateProof} onShowProof={onShowProof} isSuggestion={isSuggestion} /> <Kb.Text type="Body" style={styles.textAssertion}> <Value isSuggestion={isSuggestion} type={type} value={value} notAUser={notAUser} onShowSite={onShowSite} onCreateProof={onCreateProof} state={state} color={color} /> {!isSuggestion && ( <Kb.Text type="Body" style={styles.site}> @{type} </Kb.Text> )} </Kb.Text> <Kb.ClickableBox onClick={items ? showPopup : onShowProof} style={styles.statusAssertion} direction="horizontal" alignItems="center" gap="tiny" tooltip={tooltip} > <Kb.Icon type={stateToIcon(state)} fontSize={20} hoverColor={assertionColorToColor(color, theme)} color={isSuggestion ? theme.black_20 : assertionColorToColor(color, theme)} /> {items ? ( <> {!isMobile && <Kb.Icon className="hover-visible" type="iconfont-caret-down" sizeType="Tiny" />} {popup} </> ) : null} </Kb.ClickableBox> </Kb.Box2> {!!metas.length && ( <Kb.Box2 direction="horizontal" fullWidth={true} style={styles.metaAssertion}> {metas.map(m => ( <Kb.Meta key={m.label} backgroundColor={assertionColorToColor(m.color, theme)} title={m.label} /> ))} </Kb.Box2> )} </Kb.Box2> ) } const proofTypeToDesc = (proofType: string) => { switch (proofType) { case 'btc': case 'zcash': return 'signature' default: return 'proof' } } // alternate versions of the ones from `stateToIcon` for the popup menu header const stateToDecorationIcon = (state: T.Tracker.AssertionState) => { switch (state) { case 'checking': return 'icon-proof-pending' case 'valid': return 'icon-proof-success' case 'error': case 'warning': case 'revoked': return 'icon-proof-broken' case 'suggestion': return 'icon-proof-unfinished' default: return 'icon-proof-pending' } } const stateToValueTextStyle = (state: T.Tracker.AssertionState, styles: ReturnType<typeof useStyles>) => { switch (state) { case 'revoked': return styles.strikeThrough case 'checking': case 'valid': case 'error': case 'warning': case 'suggestion': default: return null } } const StellarValue = (p: {value: string; color: T.Tracker.AssertionColor}) => { const styles = useStyles() const theme = Kb.Styles.useTheme() const {value, color} = p const onCopyAddress = () => { copyToClipboard(value) } const menuItems: Kb.MenuItems = [{onClick: onCopyAddress, title: 'Copy address'}] const makePopup = (p: Kb.Popup2Parms) => { const {attachTo, hidePopup} = p return ( <Kb.FloatingMenu attachTo={attachTo} closeOnSelect={true} items={menuItems} onHidden={hidePopup} visible={true} position="bottom center" /> ) } const {showPopup, popup, popupAnchor} = Kb.usePopup2(makePopup) const label = ( <Kb.Text type="BodyPrimaryLink" onClick={isMobile ? undefined : showPopup} tooltip={popup ? undefined : 'Stellar Federation Address'} style={Kb.Styles.collapseStyles([styles.username, {color: assertionColorToTextColor(color, theme)}])} > {value} </Kb.Text> ) return isMobile ? ( label ) : ( <Kb.Box2 direction="vertical" ref={popupAnchor} style={styles.tooltip}> {label} {popup} </Kb.Box2> ) } const Value = (p: { isSuggestion?: boolean type: string value: string notAUser: boolean onShowSite?: () => void onCreateProof?: () => void state: T.Tracker.AssertionState color: T.Tracker.AssertionColor }) => { const styles = useStyles() const theme = Kb.Styles.useTheme() let content: React.JSX.Element | null if (p.type === 'stellar' && !p.isSuggestion) { content = <StellarValue value={p.value} color={p.color} /> } else { let str = p.value let style: Kb.Styles.StylesCrossPlatform = styles.username if (!p.isSuggestion) { switch (p.type) { case 'pgp': { const last = p.value.substring(p.value.length - 16).toUpperCase() str = `${last.substring(0, 4)} ${last.substring(4, 8)} ${last.substring(8, 12)} ${last.substring( 12, 16 )}` break } case 'btc': // fallthrough case 'zcash': style = styles.crypto break } } content = ( <Kb.Text type={p.notAUser ? 'Body' : 'BodyPrimaryLink'} onClick={p.onCreateProof || p.onShowSite} style={Kb.Styles.collapseStyles([ style, stateToValueTextStyle(p.state, styles), {color: assertionColorToTextColor(p.color, theme)}, ])} > {str} </Kb.Text> ) } return content } const HoverOpacity = (p: {children: React.ReactNode}) => ( <Kb.Box2 direction="vertical" className="hover-opacity inverted"> {p.children} </Kb.Box2> ) type SIProps = { full: boolean siteIconFullDarkmode?: T.Tracker.SiteIconSet siteIconFull?: T.Tracker.SiteIconSet siteIconDarkmode?: T.Tracker.SiteIconSet siteIcon?: T.Tracker.SiteIconSet onCreateProof?: () => void onShowProof?: () => void isSuggestion: boolean } const AssertionSiteIcon = (p: SIProps) => { const styles = useStyles() const {full, siteIconFullDarkmode, siteIconFull, siteIconDarkmode, siteIcon} = p const {onCreateProof, onShowProof, isSuggestion} = p const isDarkMode = useColorScheme() === 'dark' const set = full ? isDarkMode ? siteIconFullDarkmode : siteIconFull : isDarkMode ? siteIconDarkmode : siteIcon if (!set) return null let child = <SiteIcon full={full} set={set} /> if (full) { return child } if (!isMobile && isSuggestion) { child = <HoverOpacity>{child}</HoverOpacity> } return ( <Kb.ClickableBox onClick={onCreateProof || onShowProof} style={isSuggestion ? styles.halfOpacity : null} direction="vertical"> {child} </Kb.ClickableBox> ) } const popupHeaderTextBase = (theme: Kb.Styles.Theme) => ({ color: theme.white, ...Kb.Styles.padding(Kb.Styles.globalMargins.tiny, Kb.Styles.globalMargins.small), }) as const const useStyles = Kb.Styles.createStyleHook( theme => ({ container: {...Kb.Styles.paddingV(4)}, crypto: Kb.Styles.platformStyles({ isElectron: {display: 'inline-block', fontSize: 11, wordBreak: 'break-all'}, }), floatingMenu: { maxWidth: 240, minWidth: 196, }, // desktop is handled by css halfOpacity: Kb.Styles.platformStyles({isMobile: {opacity: 0.5}}), menuHeader: { ...Kb.Styles.bottomDivider(theme), padding: Kb.Styles.globalMargins.small, }, metaAssertion: {flexShrink: 0, paddingLeft: 20 + Kb.Styles.globalMargins.tiny * 2 - 4}, // icon spacing plus meta has 2 padding for some reason popupHeaderTextBlue: { ...popupHeaderTextBase(theme), backgroundColor: theme.blue, }, popupHeaderTextRed: { ...popupHeaderTextBase(theme), backgroundColor: theme.red, }, site: {color: theme.black_20}, siteIconFullDecoration: {bottom: -8, position: 'absolute', right: -10}, statusAssertion: Kb.Styles.platformStyles({ isMobile: {position: 'relative', top: -2}, }), strikeThrough: {textDecorationLine: 'line-through'}, textAssertion: Kb.Styles.platformStyles({ common: {flexGrow: 1, flexShrink: 1, marginTop: -1}, }), tooltip: Kb.Styles.platformStyles({isElectron: {display: 'inline-flex'}}), username: Kb.Styles.platformStyles({ common: {letterSpacing: 0.2}, isElectron: {wordBreak: 'break-all'}, }), }) as const ) export default Assertion