ADD - share music through messages

This commit is contained in:
smx.pusha
2026-08-12 08:48:51 +02:00
parent 7c697fd993
commit 25ae1b31c1
3 changed files with 104 additions and 13 deletions
+30
View File
@@ -4,6 +4,7 @@ import type { EasySharePayload } from '@/types/easyshare'
import {
easyShareCrewLinkInviteCode,
easyShareDestinationAppIds,
easyShareMusicTarget,
easyShareRoute,
} from '@/utils/easyshare'
@@ -95,6 +96,35 @@ describe('EasyShare deep links', () => {
easyShareCrewLinkInviteCode('link', '', 'invalid-code'),
).toBeNull()
})
it.each([
[
'track',
'skyphone://music/server/city-after-dark',
{ id: 'city-after-dark', kind: 'track', source: 'server' },
],
[
'track',
'skyphone://music/youtube/music-youtube-1',
{ id: 'music-youtube-1', kind: 'track', source: 'youtube' },
],
[
'playlist',
'skyphone://music/playlist/music-playlist-1',
{ id: 'music-playlist-1', kind: 'playlist' },
],
] as const)('extracts the exact shared music target', (kind, link, target) => {
expect(easyShareMusicTarget(kind, link)).toEqual(target)
})
it('rejects mismatched music share kinds', () => {
expect(
easyShareMusicTarget(
'playlist',
'skyphone://music/server/city-after-dark',
),
).toBeNull()
})
})
describe('EasyShare destination suggestions', () => {
+28
View File
@@ -75,6 +75,34 @@ export function easyShareCrewLinkInviteCode(
return null
}
export type EasyShareMusicTarget =
| { id: string; kind: 'playlist' }
| { id: string; kind: 'track'; source: 'server' | 'youtube' }
export function easyShareMusicTarget(
kind: unknown,
link: unknown,
): EasyShareMusicTarget | null {
if (typeof link !== 'string') return null
if (kind === 'track') {
const match = link.match(
/^skyphone:\/\/music\/(server|youtube)\/([^/?#]+)$/,
)
if (match) {
return {
id: match[2],
kind: 'track',
source: match[1] as 'server' | 'youtube',
}
}
}
if (kind === 'playlist') {
const match = link.match(/^skyphone:\/\/music\/playlist\/([^/?#]+)$/)
if (match) return { id: match[1], kind: 'playlist' }
}
return null
}
export async function openEasySharePayload(
router: Router,
payload: EasySharePayload,
+46 -13
View File
@@ -46,11 +46,13 @@ import {
} from 'lucide-vue-next'
import type { CSSProperties } from 'vue'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useMusicStore } from '@/stores/music'
import { useEasyShareStore } from '@/stores/easyshare'
import { usePhoneStore } from '@/stores/phone'
import type { MusicPlaylist, MusicTrack } from '@/types/music'
import { easyShareMusicTarget } from '@/utils/easyshare'
type MusicTab = 'library' | 'playlists' | 'search'
type MusicSheet =
@@ -69,6 +71,7 @@ const MUSIC_SHEET_TRANSITION_MS = 420
const music = useMusicStore()
const easyShare = useEasyShareStore()
const phone = usePhoneStore()
const route = useRoute()
const activeTab = ref<MusicTab>('library')
const activePlaylist = ref<MusicPlaylist | null>(null)
const addMenuOpened = ref(false)
@@ -273,10 +276,11 @@ function openTrackMenu(event: MouseEvent, track: MusicTrack): void {
actionMenuOpened.value = true
}
function shareTrack(): void {
const track = actionTrack.value
function shareTrack(selectedTrack: MusicTrack | null = actionTrack.value): void {
const track = selectedTrack
if (!track) return
closeMenus()
playerOpened.value = false
easyShare.open({
appId: 'music',
copyText: `${track.title}${track.artist}`,
@@ -505,8 +509,24 @@ watch(
},
)
onMounted(() => {
void music.load()
onMounted(async () => {
await music.load()
const target = easyShareMusicTarget(
route.query.easyShareKind,
route.query.easyShareLink,
)
if (!target) return
if (target.kind === 'playlist') {
const playlist = music.playlists.find((entry) => entry.id === target.id)
if (playlist) openPlaylist(playlist)
return
}
const track = music.allTracks.find(
(entry) => entry.id === target.id && entry.source === target.source,
)
if (!track) return
await playTrack(track)
playerOpened.value = true
})
onBeforeUnmount(() => {
@@ -1271,14 +1291,24 @@ onBeforeUnmount(() => {
<X :size="20" />
</k-link>
<span>{{ phone.t('Apps.music.nowPlaying') }}</span>
<k-link
component="button"
icon-only
:aria-label="phone.t('Apps.music.addToPlaylist')"
@click="openCurrentTrackPlaylistPicker"
>
<CirclePlus :size="21" />
</k-link>
<div class="music-player-header-actions">
<k-link
component="button"
icon-only
:aria-label="phone.t('Apps.easyShare.share')"
@click="shareTrack(music.currentTrack)"
>
<Share2 :size="20" />
</k-link>
<k-link
component="button"
icon-only
:aria-label="phone.t('Apps.music.addToPlaylist')"
@click="openCurrentTrackPlaylistPicker"
>
<CirclePlus :size="21" />
</k-link>
</div>
</header>
<div
class="music-player-art"
@@ -1977,8 +2007,11 @@ onBeforeUnmount(() => {
color: #fff;
}
.music-player > header > :last-child {
.music-player-header-actions {
justify-self: end;
display: flex;
align-items: center;
gap: 5px;
}
.music-player-art {