Add Signal support for social links, including URL handling, placeholder text, and icon rendering. Update version to 1.30.0.

This commit is contained in:
MartinBraquet
2026-07-20 23:36:55 +02:00
parent 5788e6d7aa
commit 31c241bb95
8 changed files with 44 additions and 5 deletions

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.compassconnections.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 125
versionName "1.29.1"
versionCode 126
versionName "1.30.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

View File

@@ -1067,6 +1067,7 @@
"profile.optional.subtitle": "Optionale Informationen",
"profile.optional.title": "Mehr Informationen über mich",
"profile.optional.university": "Universität",
"profile.optional.signal_placeholder": "Telefonnummer oder signal.me-Link",
"profile.optional.username_or_url": "Benutzername oder URL",
"profile.optional.want_kids": "Ich möchte Kinder haben",
"profile.optional.work": "Arbeit",

View File

@@ -1066,6 +1066,7 @@
"profile.optional.subtitle": "Informations optionnelles",
"profile.optional.title": "Plus d'informations à mon sujet",
"profile.optional.university": "Université",
"profile.optional.signal_placeholder": "Numéro de téléphone ou lien signal.me",
"profile.optional.username_or_url": "Nom d'utilisateur ou URL",
"profile.optional.want_kids": "Je souhaite avoir des enfants",
"profile.optional.work": "Domaine de travail",

View File

@@ -5,6 +5,7 @@ export const SITE_ORDER = [
'site', // personal site
'x', // twitter
'discord',
'signal',
'bluesky',
'mastodon',
'substack',
@@ -55,6 +56,8 @@ const stripper: {[key in Site]: (input: string) => string} = {
.replace(/^@/, '')
.replace(/\/$/, ''),
discord: (s) => s,
// strip a signal.me deep link down to the phone number / handle for display
signal: (s) => s.replace(/^(https?:\/\/)?(www\.)?signal\.me\/#[a-z]+\//i, '').replace(/\/$/, ''),
paypal: (s) =>
s.replace(/^(https?:\/\/)?(www\.)?(\w+\.)?paypal\.com\/paypalme\//, '').replace(/\/$/, ''),
patreon: (s) => s.replace(/^(https?:\/\/)?(www\.)?(\w+\.)?patreon\.com\//, '').replace(/\/$/, ''),
@@ -100,6 +103,16 @@ const urler: {[key in Site]: (handle: string) => string} = {
(s.length === 17 || s.length === 18) && !isNaN(parseInt(s, 10))
? `https://discord.com/users/${s}` // discord user id
: discordLink, // our server
// signal.me deep links open the Signal app directly. Phone numbers use #p/,
// a full pasted signal.me link (e.g. a username link, #eu/) is kept as-is.
signal: (s) => {
if (s.startsWith('http')) return s
const cleaned = s.replace(/[\s()\-.]/g, '')
if (/^\+?\d{7,15}$/.test(cleaned)) {
return `https://signal.me/#p/${cleaned.startsWith('+') ? cleaned : `+${cleaned}`}`
}
return `https://signal.me/#p/${s}`
},
bluesky: (s) => `https://bsky.app/profile/${s}`,
mastodon: (s) => (s.includes('@') ? `https://${s.split('@')[1]}/@${s.split('@')[0]}` : s),
substack: (s) => (s.startsWith('http') ? s : `https://${s}.substack.com`),
@@ -121,6 +134,7 @@ export const PLATFORM_LABELS: {[key in Site]: string} = {
site: 'Website',
x: 'Twitter/X',
discord: 'Discord',
signal: 'Signal',
bluesky: 'Bluesky',
mastodon: 'Mastodon',
substack: 'Substack',

View File

@@ -55,6 +55,15 @@ describe('strip', () => {
expect(strip('linkedin', 'username')).toBe('username')
})
})
describe('signal', () => {
it('should strip signal.me deep links', () => {
expect(strip('signal', 'https://signal.me/#p/+15551234567')).toBe('+15551234567')
expect(strip('signal', 'signal.me/#p/+15551234567')).toBe('+15551234567')
expect(strip('signal', 'https://signal.me/#eu/abc123')).toBe('abc123')
expect(strip('signal', '+15551234567')).toBe('+15551234567')
})
})
})
describe('getSocialUrl', () => {
@@ -69,6 +78,15 @@ describe('getSocialUrl', () => {
expect(getSocialUrl('spotify', 'username')).toBe('https://open.spotify.com/user/username')
})
it('should build signal.me deep links from phone numbers', () => {
expect(getSocialUrl('signal', '+15551234567')).toBe('https://signal.me/#p/+15551234567')
expect(getSocialUrl('signal', '15551234567')).toBe('https://signal.me/#p/+15551234567')
expect(getSocialUrl('signal', '+1 (555) 123-4567')).toBe('https://signal.me/#p/+15551234567')
expect(getSocialUrl('signal', 'https://signal.me/#eu/abc123')).toBe(
'https://signal.me/#eu/abc123',
)
})
it('should handle custom website URLs', () => {
expect(getSocialUrl('site', 'example.com')).toBe('https://example.com')
expect(getSocialUrl('site', 'https://example.com')).toBe('https://example.com')

View File

@@ -107,9 +107,11 @@ export function SocialLinksSection({profile, setProfile}: SocialLinksSectionProp
<Input
type="text"
placeholder={
SITE_ORDER.includes(newLinkPlatform as any) && newLinkPlatform != 'site'
? t('profile.optional.username_or_url', 'Username or URL')
: t('profile.optional.site_url', 'Site URL')
newLinkPlatform === 'signal'
? t('profile.optional.signal_placeholder', 'Phone (+32777777777)')
: SITE_ORDER.includes(newLinkPlatform as any) && newLinkPlatform != 'site'
? t('profile.optional.username_or_url', 'Username or URL')
: t('profile.optional.site_url', 'Site URL')
}
value={newLinkValue}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setNewLinkValue(e.target.value)}

View File

@@ -2,6 +2,7 @@ import {DocumentTextIcon, LinkIcon} from '@heroicons/react/24/solid'
import {Site} from 'common/socials'
import {ReactNode} from 'react'
import {LuBookmark, LuHandshake, LuHeart, LuUsers} from 'react-icons/lu'
import {SiSignal} from 'react-icons/si'
import {
TbBrandBluesky,
TbBrandDiscord,
@@ -23,6 +24,7 @@ export const PLATFORM_ICONS: {
site: LinkIcon,
x: TbBrandX,
discord: TbBrandDiscord,
signal: SiSignal,
bluesky: TbBrandBluesky,
mastodon: TbBrandMastodon,
substack: LuBookmark,

View File

@@ -13,6 +13,7 @@ import {Row} from '../layout/row'
import {SocialIcon} from './social'
const LABELS_TO_RENDER = [
'signal',
'okcupid',
'calendly',
'datingdoc',