This commit is contained in:
KoalaSat 2023-11-28 14:34:12 +01:00
parent 271e6f2875
commit 7ba4d28e86
No known key found for this signature in database
GPG Key ID: 2F7F61C6146AB157
15 changed files with 223 additions and 59 deletions

View File

@ -257,10 +257,10 @@ public class Database {
instance.execSQL("CREATE INDEX nostros_notifications_index ON nostros_notifications(created_at);");
} catch (SQLException e) { }
try {
instance.execSQL("ALTER TABLE nostros_notifications ADD COLUMN zapper_user_id TEXT;");
instance.execSQL("UPDATE nostros_lists SET kind=10003 WHERE kind=10001;"); // FIXME Remove after including kind 10003
} catch (SQLException e) { }
try {
instance.execSQL("UPDATE nostros_lists SET kind=10003 WHERE kind=10001;"); // FIXME Remove after including kind 10003
instance.execSQL("ALTER TABLE nostros_users ADD COLUMN banner TEXT;");
} catch (SQLException e) { }
}

View File

@ -590,6 +590,7 @@ public class Event {
values.put("name", name);
values.put("picture", userContent.optString("picture"));
values.put("about", userContent.optString("about"));
values.put("banner", userContent.optString("banner"));
values.put("lnurl", lnurl);
values.put("ln_address", ln_address);
values.put("nip05", nip05);
@ -615,8 +616,7 @@ public class Event {
return 1;
} else if (created_at == cursor.getInt(0)) {
ContentValues putValues = new ContentValues();
putValues.put("name", name);
putValues.put("tags", tags.toString());
putValues.put("banner", userContent.optString("banner"));
database.update("nostros_users", putValues, whereClause, whereArgs);
}
}

View File

@ -10,7 +10,6 @@ import {
TouchableRipple,
useTheme,
} from 'react-native-paper'
import SInfo from 'react-native-sensitive-info'
import Logo from '../Logo'
import { useTranslation } from 'react-i18next'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
@ -20,7 +19,7 @@ import { navigate } from '../../lib/Navigation'
import { usernamePubKey } from '../../Functions/RelayFunctions/Users'
import ProfileData from '../ProfileData'
import { WalletContext } from '../../Contexts/WalletContext'
import { AppContext, type Config } from '../../Contexts/AppContext'
import { AppContext } from '../../Contexts/AppContext'
export const MenuItems: React.FC = () => {
const [drawerItemIndex, setDrawerItemIndex] = React.useState<number>(-1)

View File

@ -43,6 +43,8 @@ export interface UserContextProps {
validNip05?: boolean
setLnAddress: (value: string) => void
lnAddress?: string
setBanner: (banner: string) => void
banner?: string
}
export interface UserContextProviderProps {
@ -64,6 +66,7 @@ export const initialUserContext: UserContextProps = {
setLnurl: () => {},
setLnAddress: () => {},
setNip05: () => {},
setBanner: () => {},
publicBookmarks: [],
privateBookmarks: [],
mutedEvents: [],
@ -89,6 +92,7 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX
const [privateBookmarks, setPrivateBookmarks] = useState<string[]>([])
const [mutedEvents, setMutedEvents] = useState<string[]>([])
const [mutedUsers, setMutedUsers] = useState<string[]>([])
const [banner, setBanner] = useState<string>()
const reloadUser: () => void = () => {
if (database && publicKey) {
@ -101,6 +105,7 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX
setLnAddress(result.ln_address)
setNip05(result.nip05)
setValidNip05(result.valid_nip05)
setBanner(result.banner)
}
})
}
@ -254,6 +259,8 @@ export const UserContextProvider = ({ children }: UserContextProviderProps): JSX
lnAddress,
setLnAddress,
mutedUsers,
banner,
setBanner
}}
>
{children}

View File

@ -8,6 +8,7 @@ export interface User {
picture?: string
about?: string
contact?: boolean
banner?: string
follower?: number
lnurl?: string
ln_address?: string

View File

@ -447,7 +447,7 @@
"nip05Description": "Profil mit Domain verbinden.",
"nip05Title": "NIP-05",
"directoryCancell": "Abbrechen",
"directoryContinue": "Weiter",
"continue": "Weiter",
"directoryDescription": "Nostr Directory ist ein Verzeichnis von Nutzern und ihren öffentlichen Schlüsseln, die bereits auf anderen Platformen offiziell Inhalte veröffentlichen. Finde hier Leute, denen du bereits auf anderen Platformen folgst, und hinterlasse deinen öffentlichen Schlüssel, damit andere dich auf Nostr finden können.\n\nBeim Fortfahren muss der Verifikationsprozess auf externen Platformen durchgeführt werden.",
"directoryTitle": "Nostr Directory",
"publishPicture": "Veröffentlichen",
@ -462,7 +462,10 @@
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "Name",
"about": "Über mich"
"about": "Über mich",
"banner": "Banner",
"bannerTitle": "Banner",
"bannerDescription": "Paste here the URL for the image you would like to display as background on your public profile."
},
"textContent": {
"invoice": "Lightning Invoice"

View File

@ -450,7 +450,7 @@
"nip05Description": "Link your identity with a domain.",
"nip05Title": "NIP-05",
"directoryCancell": "Cancel",
"directoryContinue": "Continue",
"continue": "Continue",
"directoryDescription": "Nostr Directory is a database of nostr public keys associated with official user accounts on other platforms. Find the people you already follow on other platforms on nostr and add your public key so that your followers can find you.\n\nIf you continue you will have to perform the verification process outside the application.",
"directoryTitle": "Nostr Directory",
"publishPicture": "Publish",
@ -465,7 +465,10 @@
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "Name",
"about": "Description"
"about": "Description",
"banner": "Banner",
"bannerTitle": "Banner",
"bannerDescription": "Paste here the URL for the image you would like to display as background on your public profile."
},
"textContent": {
"invoice": "Lightning Network invoice"

View File

@ -450,7 +450,7 @@
"nip05Description": "Vincula tu perfil con un dominio.",
"nip05Title": "NIP-05",
"directoryCancell": "Cancelar",
"directoryContinue": "Continuar",
"continue": "Continuar",
"directoryDescription": "Nostr Directory es una base de datos de claves públicas de nostr asociadas a cuentas oficiales de usuarios en otras plataformas. Encuentra a las personas que ya sigues en otras plataformas en nostr y añade tu clave pública para que tus seguidores te encuentren.\n\nSi continuas tendrás que llevar a cabo el proceso de verificación fuera de la aplicación.",
"directoryTitle": "Nostr Directory",
"publishPicture": "Publicar",
@ -465,7 +465,10 @@
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "Nombre",
"about": "Descripción"
"about": "Descripción",
"banner": "Banner",
"bannerTitle": "Banner",
"bannerDescription": "Paste here the URL for the image you would like to display as background on your public profile."
},
"profilePage": {
"bookmarkFeed": {

View File

@ -446,7 +446,7 @@
"nip05Description": "Associez votre profil à un domaine.",
"nip05Title": "NIP-05",
"directoryCancell": "Annuler",
"directoryContinue": "Continuer",
"continue": "Continuer",
"directoryDescription": "Nostr Directory est une base de données des clés publiques de nostr associées à des comptes d'utilisateurs officiels sur d'autres plateformes. Retrouvez sur nostr les personnes que vous suivez déjà sur d'autres plateformes et ajoutez votre clé publique pour que vos followers puissent vous retrouver.\n\nSi vous continuez, vous devrez passer par le processus de vérification en dehors de l'application.",
"directoryTitle": "Nostr Directory",
"publishPicture": "Publier",
@ -461,7 +461,10 @@
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "Nom",
"about": "Description"
"about": "Description",
"banner": "Banner",
"bannerTitle": "Banner",
"bannerDescription": "Paste here the URL for the image you would like to display as background on your public profile."
},
"profilePage": {
"bookmarkFeed": {

View File

@ -444,7 +444,7 @@
"nip05Description": "Link your identity with a domain.",
"nip05Title": "NIP-05",
"directoryCancell": "Отменить",
"directoryContinue": "Продолжить",
"continue": "Продолжить",
"directoryDescription": "Nostr Directory is a database of nostr public keys associated with a Twitter account. Find the people you follow on Twitter on nostr and add your public key so your followers can find you if you want.\n\n\nVerifying your Twitter account will add a badge to your profile.\n\n\nYes you will have to carry out the verification process outside the application.",
"directoryTitle": "Nostr Directory",
"publishPicture": "Publish",
@ -459,7 +459,10 @@
"lud06": "Zaps",
"nip05": "NIP-05",
"name": "Name",
"about": "Описание"
"about": "Описание",
"banner": "Banner",
"bannerTitle": "Banner",
"bannerDescription": "Paste here the URL for the image you would like to display as background on your public profile."
},
"profilePage": {
"bookmarkFeed": {

View File

@ -454,7 +454,7 @@
"nip05Description": "链接接到域名",
"nip05Title": "NIP-05",
"directoryCancell": "取消",
"directoryContinue": "继续",
"continue": "继续",
"directoryDescription": "Nostr 目录是一个与其他平台的官方用户账户相关的 Nostr 公钥数据库。在 Nostr 上找到您已经在其他平台上关注的人,并添加您的公钥,这样您的关注者就能找到您。 \n\n如果继续您将在应用程序之外执行验证过程。",
"directoryTitle": "Nostr 目录",
"publishPicture": "发布",
@ -469,7 +469,10 @@
"lud06": "赞赏",
"nip05": "NIP-05",
"name": "用户名",
"about": "简介"
"about": "简介",
"banner": "Banner",
"bannerTitle": "Banner",
"bannerDescription": "Paste here the URL for the image you would like to display as background on your public profile."
},
"profilePage": {
"bookmarkFeed": {

View File

@ -1,5 +1,5 @@
import React, { useContext, useEffect, useState } from 'react'
import { Linking, ScrollView, StyleSheet, View } from 'react-native'
import { Dimensions, ImageBackground, Linking, ScrollView, StyleSheet, View } from 'react-native'
import Clipboard from '@react-native-clipboard/clipboard'
import { AppContext } from '../../Contexts/AppContext'
import { useTranslation } from 'react-i18next'
@ -24,6 +24,8 @@ import { useFocusEffect } from '@react-navigation/native'
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
import UploadImage from '../../Components/UploadImage'
import { navigate } from '../../lib/Navigation'
import LinearGradient from 'react-native-linear-gradient'
import FastImage from 'react-native-fast-image'
export const ProfileConfigPage: React.FC = () => {
const { t } = useTranslation('common')
@ -32,6 +34,7 @@ export const ProfileConfigPage: React.FC = () => {
const bottomSheetDirectoryRef = React.useRef<RBSheet>(null)
const bottomSheetNip05Ref = React.useRef<RBSheet>(null)
const bottomSheetLud06Ref = React.useRef<RBSheet>(null)
const bottomSheetBannerRef = React.useRef<RBSheet>(null)
const { database, online } = useContext(AppContext)
const { relayPool, lastEventId, lastConfirmationtId, sendEvent } = useContext(RelayPoolContext)
const {
@ -51,6 +54,8 @@ export const ProfileConfigPage: React.FC = () => {
nip05,
setNip05,
reloadUser,
banner,
setBanner
} = useContext(UserContext)
// State
const [showNotification, setShowNotification] = useState<undefined | string>()
@ -82,6 +87,7 @@ export const ProfileConfigPage: React.FC = () => {
bottomSheetPictureRef.current?.close()
bottomSheetNip05Ref.current?.close()
bottomSheetLud06Ref.current?.close()
bottomSheetBannerRef.current?.close()
}
}, [lastEventId, lastConfirmationtId, online])
@ -96,6 +102,7 @@ export const ProfileConfigPage: React.FC = () => {
lud16: lnAddress,
nip05,
picture,
banner
}),
created_at: getUnixTime(new Date()),
kind: Kind.Metadata,
@ -157,10 +164,36 @@ export const ProfileConfigPage: React.FC = () => {
})
}
const pasteBanner: () => void = () => {
Clipboard.getString().then((value) => {
setBanner(value ?? '')
})
}
return (
<View style={styles.container}>
<ScrollView horizontal={false} showsVerticalScrollIndicator={false}>
<Card style={styles.cardContainer}>
<ImageBackground
style={[
styles.banner,
{ width: Dimensions.get('window').width }
]}
source={{ uri: banner }}
resizeMode={FastImage.resizeMode.cover}
>
<LinearGradient
colors={['rgba(0, 0, 0, 0)', theme.colors.elevation.level1]}
style={styles.gradient}
/>
</ImageBackground>
<View style={styles.editBanner}>
<IconButton
icon='pencil-outline'
size={20}
onPress={() => bottomSheetBannerRef.current?.open()}
/>
</View>
<Card.Content>
<View style={styles.cardPicture}>
<TouchableRipple onPress={() => bottomSheetPictureRef.current?.open()}>
@ -353,13 +386,47 @@ export const ProfileConfigPage: React.FC = () => {
onPress={async () => await Linking.openURL('https://www.nostr.directory')}
loading={isPublishingProfile !== undefined}
>
{t('profileConfigPage.directoryContinue')}
{t('profileConfigPage.continue')}
</Button>
<Button mode='outlined' onPress={() => bottomSheetDirectoryRef.current?.close()}>
{t('profileConfigPage.directoryCancell')}
</Button>
</View>
</RBSheet>
<RBSheet
ref={bottomSheetBannerRef}
closeOnDragDown={true}
customStyles={rbSheetCustomStyles}
>
<View>
<Text variant='titleLarge'>{t('profileConfigPage.bannerTitle')}</Text>
<Text style={styles.spacer} variant='bodyMedium'>
{t('profileConfigPage.bannerDescription')}
</Text>
<TextInput
style={styles.spacer}
mode='outlined'
label={t('profileConfigPage.banner') ?? ''}
onChangeText={setBanner}
value={banner}
right={
<TextInput.Icon
icon='content-paste'
onPress={pasteBanner}
forceTextInputFocus={false}
/>
}
/>
<Button
style={styles.spacer}
mode='contained'
onPress={() => onPublishUser('profilePublished')}
loading={isPublishingProfile !== undefined}
>
{t('profileConfigPage.publish')}
</Button>
</View>
</RBSheet>
<RBSheet ref={bottomSheetNip05Ref} closeOnDragDown={true} customStyles={rbSheetCustomStyles}>
<View>
<Text variant='titleLarge'>{t('profileConfigPage.nip05Title')}</Text>
@ -489,7 +556,7 @@ const styles = StyleSheet.create({
cardContainer: {
width: '100%',
justifyContent: 'center',
alignContent: 'center',
alignContent: 'center'
},
cardActions: {
flexDirection: 'row',
@ -517,6 +584,25 @@ const styles = StyleSheet.create({
marginTop: 16,
marginBottom: 16,
},
gradient: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: '30%',
},
banner: {
height: 120,
marginBottom: -80,
borderTopRightRadius: 28
},
editBanner: {
width: '100%',
justifyContent: 'flex-end',
flexDirection: 'row',
paddingRight: 6,
marginTop: -40
}
})
export default ProfileConfigPage

View File

@ -1,6 +1,6 @@
import React, { useContext, useEffect, useMemo, useState } from 'react'
import { Linking, StyleSheet, View } from 'react-native'
import { Surface, Text, Snackbar, Button } from 'react-native-paper'
import { Linking, StyleSheet, View, Image, Dimensions, ImageBackground } from 'react-native'
import { Surface, Text, Snackbar, Button, useTheme } from 'react-native-paper'
import { AppContext } from '../../Contexts/AppContext'
import { UserContext } from '../../Contexts/UserContext'
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
@ -15,12 +15,15 @@ import NotesFeed from './NotesFeed'
import RepliesFeed from './RepliesFeed'
import ZapsFeed from './ZapsFeed'
import BookmarksFeed from './BookmarksFeed'
import FastImage from 'react-native-fast-image'
import LinearGradient from 'react-native-linear-gradient'
interface ProfilePageProps {
route: { params: { pubKey: string } }
}
export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
const theme = useTheme()
const { database, online } = useContext(AppContext)
const { publicKey } = useContext(UserContext)
const { lastEventId, relayPool } = useContext(RelayPoolContext)
@ -193,46 +196,67 @@ export const ProfilePage: React.FC<ProfilePageProps> = ({ route }) => {
return (
<View>
<Surface style={styles.container} elevation={1}>
<View style={styles.profileData}>
<View style={styles.profilePicture}>
<ProfileData
username={user?.name}
publicKey={route.params.pubKey}
validNip05={user?.valid_nip05}
nip05={user?.nip05}
lnurl={user?.lnurl}
lnAddress={user?.ln_address}
picture={user?.picture}
<Surface elevation={1}>
{styles.banner ? (
<ImageBackground
style={[
styles.banner,
{ width: Dimensions.get('window').width }
]}
source={{
uri: user?.banner
}}
resizeMode={FastImage.resizeMode.cover}
>
<LinearGradient
colors={['rgba(0, 0, 0, 0)', theme.colors.elevation.level1]}
style={styles.gradient}
/>
</ImageBackground>
) : <></>}
<View style={styles.container}>
<View style={styles.profileData}>
<View style={styles.profilePicture}>
<ProfileData
username={user?.name}
publicKey={route.params.pubKey}
validNip05={user?.valid_nip05}
nip05={user?.nip05}
lnurl={user?.lnurl}
lnAddress={user?.ln_address}
picture={user?.picture}
/>
</View>
</View>
<View>
<Text>{user?.follower && user.follower > 0 ? t('profilePage.isFollower') : ''}</Text>
<View style={styles.profileDescription}>
<View style={styles.profileAbout}>
<TextContent content={user?.about} showPreview={false} numberOfLines={10} />
</View>
<View style={styles.profileFollow}>
<Text>{user?.follower && user.follower > 0 ? t('profilePage.isFollower') : ''}</Text>
</View>
</View>
{user?.tags && user.tags?.length > 0 && (
<View style={styles.externalEntities}>
{getExternalIdentities().map((extEntity) => {
return (
<View key={extEntity.service}>
<Button
onPress={async () =>
await Linking.openURL(
identitiesIcons[extEntity.service].url(extEntity.identity, extEntity.proof),
)
}
labelStyle={styles.serviceButtonText}
>
{extEntity.service}
</Button>
</View>
)
})}
</View>
)}
</View>
<View>
<TextContent content={user?.about} showPreview={false} numberOfLines={10} />
</View>
{user?.tags && user.tags?.length > 0 && (
<View style={styles.externalEntities}>
{getExternalIdentities().map((extEntity) => {
return (
<View key={extEntity.service}>
<Button
onPress={async () =>
await Linking.openURL(
identitiesIcons[extEntity.service].url(extEntity.identity, extEntity.proof),
)
}
labelStyle={styles.serviceButtonText}
>
{extEntity.service}
</Button>
</View>
)
})}
</View>
)}
</Surface>
<Tabs tabs={['notes', 'replies', 'zaps', 'bookmarks']} setActiveTab={setActiveTab} />
<View style={styles.list}>{renderScene[activeTab]}</View>
@ -255,6 +279,10 @@ const styles = StyleSheet.create({
loading: {
paddingTop: 16,
},
banner: {
height: 120,
marginBottom: -80
},
container: {
padding: 16,
},
@ -285,6 +313,18 @@ const styles = StyleSheet.create({
justifyContent: 'space-between',
paddingBottom: 16,
},
profileDescription: {
flexDirection: 'row',
justifyContent: 'space-between',
},
profileFollow: {
width: '20%',
justifyContent: 'flex-end'
},
profileAbout: {
width: '80%',
paddingRight: 10
},
externalEntities: {
flexDirection: 'row',
justifyContent: 'flex-end',
@ -293,6 +333,13 @@ const styles = StyleSheet.create({
serviceButtonText: {
textTransform: 'capitalize',
},
gradient: {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: '50%', // Adjust the height of the gradient as needed
}
})
export default ProfilePage

View File

@ -45,6 +45,7 @@
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.8.0",
"react-native-image-picker": "^5.1.0",
"react-native-linear-gradient": "^2.8.3",
"react-native-pager-view": "^6.1.4",
"react-native-paper": "^5.5.1",
"react-native-parsed-text": "^0.0.22",

View File

@ -7005,6 +7005,11 @@ react-native-image-picker@^5.1.0:
resolved "https://registry.yarnpkg.com/react-native-image-picker/-/react-native-image-picker-5.3.1.tgz#836ab13c228174728a0bd68293b27dc5e1affa0f"
integrity sha512-zRCjtlE3KOeaWDM8gXzTwXfvo3ZeF2XMkHceU7CVCtKRleKxna/E4XWIPu/lXO2qlMdnSx1WvfPSbqzAX0qxpA==
react-native-linear-gradient@^2.8.3:
version "2.8.3"
resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz#9a116649f86d74747304ee13db325e20b21e564f"
integrity sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==
react-native-pager-view@^6.1.4:
version "6.2.0"
resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.2.0.tgz#51380d93fbe47f6380dc71d613a787bf27a4ca37"