Skip to content

Commit

Permalink
Support video avatars (#3700)
Browse files Browse the repository at this point in the history
Resolves #3688

## What has been done

Added video support for wallet avatars

## Testing
- [x] Test with video avatar (example user
`0xab7375daf14bc661a0c9aff8800d49a34b037a98`)
- [x] Test with image avatar

Latest build:
[extension-builds-3700](https://github.com/tahowallet/extension/suites/19135090954/artifacts/1121162389)
(as of Mon, 18 Dec 2023 11:20:52 GMT).
  • Loading branch information
jagodarybacka authored Dec 18, 2023
2 parents cb10d5e + 58d05ab commit bb4824c
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 46 deletions.
6 changes: 5 additions & 1 deletion background/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1059,11 +1059,15 @@ export default class Main extends BaseService<never> {
this.store.dispatch(updateAccountName({ ...addressOnNetwork, name }))
},
)

this.nameService.emitter.on(
"resolvedAvatar",
async ({ from: { addressOnNetwork }, resolved: { avatar } }) => {
this.store.dispatch(
updateENSAvatar({ ...addressOnNetwork, avatar: avatar.toString() }),
updateENSAvatar({
...addressOnNetwork,
avatar: avatar.toString(),
}),
)
},
)
Expand Down
4 changes: 3 additions & 1 deletion background/redux-slices/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,9 @@ const accountSlice = createSlice({
immerState,
{
payload: { address, network, avatar },
}: { payload: AddressOnNetwork & { avatar: URI } },
}: {
payload: AddressOnNetwork & { avatar: URI }
},
) => {
const normalizedAddress = normalizeEVMAddress(address)

Expand Down
6 changes: 4 additions & 2 deletions background/redux-slices/selectors/accountsSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,8 @@ function getNetworkAccountTotalsByCategory(
}
}

const { name, avatarURL } = accountData.ens

return {
address,
network,
Expand All @@ -466,8 +468,8 @@ function getNetworkAccountTotalsByCategory(
signerId,
path,
accountSigner,
name: accountData.ens.name ?? accountData.defaultName,
avatarURL: accountData.ens.avatarURL ?? accountData.defaultAvatar,
name: name ?? accountData.defaultName,
avatarURL: avatarURL ?? accountData.defaultAvatar,
localizedTotalMainCurrencyAmount: formatCurrencyAmount(
mainCurrencySymbol,
getTotalBalance(accountData.balances, prices, mainCurrencySymbol),
Expand Down
25 changes: 13 additions & 12 deletions ui/components/Shared/SharedAccountItemSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ import { AccountTotal } from "@tallyho/tally-background/redux-slices/selectors"

import { useTranslation } from "react-i18next"
import SharedLoadingSpinner from "./SharedLoadingSpinner"
import SharedAvatar from "./SharedAvatar"

function Avatar({ avatarURL }: { avatarURL?: string }) {
return (
<SharedAvatar
avatarURL={avatarURL}
width="48px"
backupAvatar="./images/avatar@2x.png"
/>
)
}

interface Props {
isSelected?: boolean
Expand Down Expand Up @@ -33,10 +44,10 @@ export default function SharedAccountItemSummary(props: Props): ReactElement {
<div className="left">
{isSelected ? (
<div className="avatar_selected_outline">
<div className="avatar" />
<Avatar avatarURL={avatarURL} />
</div>
) : (
<div className="avatar" />
<Avatar avatarURL={avatarURL} />
)}

<div className="info">
Expand Down Expand Up @@ -91,16 +102,6 @@ export default function SharedAccountItemSummary(props: Props): ReactElement {
padding: 5px 0;
overflow: hidden;
}
.avatar {
background: url("${avatarURL ?? "./images/avatar@2x.png"}") center
no-repeat;
background-color: var(--green-40);
background-size: cover;
width: 48px;
height: 48px;
border-radius: 12px;
flex-shrink: 0;
}
.avatar_selected_outline {
width: 52px;
height: 52px;
Expand Down
33 changes: 14 additions & 19 deletions ui/components/Shared/SharedAddressAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { ReactElement } from "react"
import SharedAvatar from "./SharedAvatar"

type SharedAddressAvatarProps = {
address: string
url?: string
}

/*
@TODO Switch to using our own resolution service, especially
Expand All @@ -7,25 +13,14 @@ once we upgrade our service to support whatever else Effigy can do.
export default function SharedAddressAvatar({
address,
url,
}: {
address: string
url?: string
}): ReactElement {
}: SharedAddressAvatarProps): ReactElement {
return (
<div className="avatar">
<style jsx>{`
.avatar {
width: 40px;
height: 40px;
background-color: var(--castle-black);
background-image: url("${typeof url !== "undefined"
? url
: `https://effigy.im/a/${address}.png`}");
background-size: cover;
border-radius: 999px;
flex-shrink: 0;
}
`}</style>
</div>
<SharedAvatar
width="40px"
background="var(--castle-black)"
avatarURL={url}
backupAvatar={`https://effigy.im/a/${address}.png`}
borderRadius="999px"
/>
)
}
87 changes: 87 additions & 0 deletions ui/components/Shared/SharedAvatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { CSSProperties, useEffect, useState } from "react"

type SharedAvatarProps = {
width: string
background?: string
borderRadius?: string
avatarURL?: string
backupAvatar: string
style?: CSSProperties
}

async function getAvatarType(url?: string) {
if (!url) return null

try {
const fileTypeFetch = await fetch(url, { method: "HEAD" })
const fileType = fileTypeFetch.headers.get("Content-Type")

return fileType
} catch {
return null
}
}

export default function SharedAvatar({
width,
background = "var(--green-40)",
borderRadius = "12px",
avatarURL,
backupAvatar,
style,
}: SharedAvatarProps) {
const [avatarType, setAvatarType] = useState<string | null>(null)

useEffect(() => {
const fetchAvatarType = async () => {
const type = await getAvatarType(avatarURL)
setAvatarType(type)
}

fetchAvatarType()
}, [avatarURL])

return (
<>
<div className="avatar_container" style={style}>
{avatarType === "video/mp4" ? (
<div className="video">
<video src={avatarURL} autoPlay muted loop />
</div>
) : (
<div className="avatar" />
)}
</div>
<style jsx>{`
.avatar_container {
width: ${width};
height: ${width};
border-radius: ${borderRadius};
background-color: ${background};
flex-shrink: 0;
}
.avatar {
width: 100%;
height: 100%;
border-radius: ${borderRadius};
flex-shrink: 0;
background: url("${avatarURL ?? backupAvatar}");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.video {
width: 100%;
height: 100%;
border-radius: ${borderRadius};
overflow: hidden;
}
.video > video {
width: 100%;
height: 100%;
object-fit: cover;
}
`}</style>
</>
)
}
18 changes: 7 additions & 11 deletions ui/components/Shared/SharedCurrentAccountInformation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { ReactElement } from "react"
import classNames from "classnames"
import SharedIcon from "./SharedIcon"
import { useAreInternalSignersUnlocked } from "../../hooks/signing-hooks"
import SharedAvatar from "./SharedAvatar"

type Props = {
shortenedAddress: string
Expand All @@ -25,7 +26,12 @@ export default function SharedCurrentAccountInformation({
<span className="account_info_label ellipsis">
{name ?? shortenedAddress}
</span>
<div className="avatar" />
<SharedAvatar
avatarURL={avatarURL}
backupAvatar="./images/portrait.png"
width="32px"
style={{ marginLeft: 8 }}
/>
{showLockStatus && (
<div data-testid="lock" className="lock_icon_wrap">
<SharedIcon
Expand All @@ -45,16 +51,6 @@ export default function SharedCurrentAccountInformation({
position: relative;
min-width: 0; // Allow the account address/name to collapse to an ellipsis.
}
.avatar {
border-radius: 12px;
width: 32px;
height: 32px;
margin-left: 8px;
background: url("${avatarURL ?? "./images/portrait.png"}");
background-color: var(--green-40);
background-size: cover;
flex-shrink: 0;
}
.hover:hover .account_info_label {
color: var(--trophy-gold);
}
Expand Down

0 comments on commit bb4824c

Please sign in to comment.