mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 17:23:25 +00:00
ENH - complete and secure EasyShare integration
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
Plus,
|
||||
Rows3,
|
||||
Search,
|
||||
Share2,
|
||||
Trash2,
|
||||
UserRound,
|
||||
X,
|
||||
@@ -22,6 +23,7 @@ import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { useAccountStore } from '@/stores/account'
|
||||
import { useCalendarStore } from '@/stores/calendar'
|
||||
import { useEasyShareStore } from '@/stores/easyshare'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
import type { CalendarEvent, CalendarEventDraft } from '@/types/calendar'
|
||||
|
||||
@@ -42,6 +44,7 @@ type YearOverview = {
|
||||
const phone = usePhoneStore()
|
||||
const account = useAccountStore()
|
||||
const calendar = useCalendarStore()
|
||||
const easyShare = useEasyShareStore()
|
||||
const today = new Date()
|
||||
const visibleYear = ref(today.getFullYear())
|
||||
const selectedDate = ref(dateKey(today))
|
||||
@@ -358,6 +361,19 @@ function openEdit(): void {
|
||||
screen.value = 'form'
|
||||
}
|
||||
|
||||
function shareEvent(): void {
|
||||
if (!selectedEvent.value) return
|
||||
easyShare.open({
|
||||
appId: 'calendar',
|
||||
copyText: `${selectedEvent.value.title}\n${selectedEvent.value.note}`,
|
||||
id: selectedEvent.value.id,
|
||||
kind: 'document',
|
||||
link: `skyphone://calendar/event/${selectedEvent.value.id}`,
|
||||
subtitle: formatLongDate(selectedEvent.value.startsAt),
|
||||
title: selectedEvent.value.title,
|
||||
})
|
||||
}
|
||||
|
||||
function closeForm(): void {
|
||||
timePickerField.value = null
|
||||
screen.value = editingEvent.value ? 'detail' : 'main'
|
||||
@@ -781,6 +797,10 @@ onMounted(async () => {
|
||||
<p>{{ selectedEvent.note }}</p>
|
||||
</div>
|
||||
</section>
|
||||
<button class="calendar__delete calendar__share" type="button" @click="shareEvent">
|
||||
<Share2 :size="18" />
|
||||
{{ phone.t('Apps.easyShare.share') }}
|
||||
</button>
|
||||
<button class="calendar__delete" type="button" @click="deleteEvent">
|
||||
<Trash2 :size="18" />
|
||||
{{ phone.t('Apps.calendar.deleteEvent') }}
|
||||
@@ -1739,6 +1759,10 @@ onMounted(async () => {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.calendar__share {
|
||||
color: #0a84ff !important;
|
||||
}
|
||||
|
||||
.calendar__group--fields input,
|
||||
.calendar__group--fields textarea {
|
||||
width: 100%;
|
||||
|
||||
@@ -50,6 +50,7 @@ import CityMarktSelect from '@/components/citymarkt/CityMarktSelect.vue'
|
||||
import CityMarktGallery from '@/components/citymarkt/CityMarktGallery.vue'
|
||||
import CityMarktOfferCard from '@/components/citymarkt/CityMarktOfferCard.vue'
|
||||
import { useAccountStore } from '@/stores/account'
|
||||
import { useEasyShareStore } from '@/stores/easyshare'
|
||||
import { useMarketplaceStore } from '@/stores/marketplace'
|
||||
import { useMessageMediaStore } from '@/stores/messageMedia'
|
||||
import { usePagesStore } from '@/stores/pages'
|
||||
@@ -115,6 +116,7 @@ const phone = usePhoneStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const account = useAccountStore()
|
||||
const easyShare = useEasyShareStore()
|
||||
const marketplace = useMarketplaceStore()
|
||||
const messageMedia = useMessageMediaStore()
|
||||
const pages = usePagesStore()
|
||||
@@ -367,6 +369,19 @@ async function shareToLocalPages(): Promise<void> {
|
||||
)
|
||||
}
|
||||
|
||||
function shareListing(): void {
|
||||
if (!selectedListing.value) return
|
||||
easyShare.open({
|
||||
appId: 'citymarkt',
|
||||
copyText: `${selectedListing.value.title}\n${selectedListing.value.description}`,
|
||||
id: selectedListing.value.id,
|
||||
kind: 'link',
|
||||
link: `skyphone://citymarkt/listing/${selectedListing.value.id}`,
|
||||
subtitle: formatPrice(selectedListing.value),
|
||||
title: selectedListing.value.title,
|
||||
})
|
||||
}
|
||||
|
||||
async function loadFeed(): Promise<void> {
|
||||
await marketplace.load({
|
||||
category: category.value,
|
||||
@@ -1100,6 +1115,14 @@ onMounted(async () => {
|
||||
{{ phone.t('Apps.citymarkt.phone') }}:
|
||||
{{ selectedListing.phone_number }}
|
||||
</p>
|
||||
<button
|
||||
v-if="selectedListing.status === 'active' || selectedListing.status === 'reserved'"
|
||||
class="citymarkt__pages-share"
|
||||
type="button"
|
||||
@click="shareListing"
|
||||
>
|
||||
<Share2 :size="17" /><span><strong>{{ phone.t('Apps.easyShare.share') }}</strong></span>
|
||||
</button>
|
||||
<template v-if="selectedListing.is_owner">
|
||||
<button
|
||||
v-if="
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
Plus,
|
||||
RefreshCw,
|
||||
Send,
|
||||
Share2,
|
||||
Settings2,
|
||||
Trash2,
|
||||
UserRound,
|
||||
@@ -75,6 +76,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { useCallsStore } from '@/stores/calls'
|
||||
import { useCompaniesStore } from '@/stores/companies'
|
||||
import { useEasyShareStore } from '@/stores/easyshare'
|
||||
import { useMessageMediaStore } from '@/stores/messageMedia'
|
||||
import { useMessagesStore } from '@/stores/messages'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
@@ -130,6 +132,7 @@ const phone = usePhoneStore()
|
||||
const calls = useCallsStore()
|
||||
const messages = useMessagesStore()
|
||||
const companies = useCompaniesStore()
|
||||
const easyShare = useEasyShareStore()
|
||||
const mediaPicker = useMessageMediaStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -494,6 +497,19 @@ async function setRoute(company: CompanySummary): Promise<void> {
|
||||
showToast(phone.t('Apps.companies.routeSet'))
|
||||
}
|
||||
|
||||
function shareCompany(company: Company): void {
|
||||
easyShare.open({
|
||||
appId: 'companies',
|
||||
copyText: `${company.name}\n${company.description}`,
|
||||
id: company.id,
|
||||
imageUrl: company.logoUrl,
|
||||
kind: 'profile',
|
||||
link: `skyphone://companies/profile/${company.id}`,
|
||||
subtitle: company.phoneNumber ?? company.categoryName,
|
||||
title: company.name,
|
||||
})
|
||||
}
|
||||
|
||||
function openRequestComposer(company: Company): void {
|
||||
if (!company.acceptsRequests) {
|
||||
showToast(phone.t('Apps.companies.actionUnavailable.request'))
|
||||
@@ -1618,6 +1634,9 @@ onBeforeUnmount(() => {
|
||||
phone.t('Apps.companies.profile.request')
|
||||
}}
|
||||
</k-button>
|
||||
<k-button rounded outline @click="shareCompany(activeCompany)">
|
||||
<Share2 :size="17" />{{ phone.t('Apps.easyShare.share') }}
|
||||
</k-button>
|
||||
</k-glass>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
Navigation,
|
||||
Plane,
|
||||
Route,
|
||||
Share2,
|
||||
Sparkles,
|
||||
Sailboat,
|
||||
ShieldAlert,
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import { useGarageStore } from '@/stores/garage'
|
||||
import { useEasyShareStore } from '@/stores/easyshare'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
import type {
|
||||
GarageVehicle,
|
||||
@@ -50,6 +52,7 @@ type GarageFilter = 'all' | GarageVehicleStatus
|
||||
|
||||
const phone = usePhoneStore()
|
||||
const garage = useGarageStore()
|
||||
const easyShare = useEasyShareStore()
|
||||
const activeFilter = ref<GarageFilter>('all')
|
||||
const query = ref('')
|
||||
const selectedVehicle = ref<GarageVehicle | null>(null)
|
||||
@@ -110,6 +113,18 @@ function displayName(vehicle: GarageVehicle): string {
|
||||
return phone.t('Apps.garage.unknownVehicle')
|
||||
}
|
||||
|
||||
function shareVehicle(vehicle: GarageVehicle): void {
|
||||
easyShare.open({
|
||||
appId: 'garage',
|
||||
copyText: `${displayName(vehicle)}\n${vehicle.plate}`,
|
||||
id: vehicle.plate,
|
||||
kind: 'document',
|
||||
link: `skyphone://garage/vehicle/${vehicle.plate}`,
|
||||
subtitle: vehicle.plate,
|
||||
title: displayName(vehicle),
|
||||
})
|
||||
}
|
||||
|
||||
function modelName(vehicle: GarageVehicle): string {
|
||||
if (vehicle.name && vehicle.nickname) return vehicle.name
|
||||
if (typeof vehicle.model === 'string' && vehicle.model) return vehicle.model
|
||||
@@ -480,6 +495,9 @@ onBeforeUnmount(() => {
|
||||
<small>{{ phone.t('Apps.garage.vin') }}</small>
|
||||
<strong>{{ selectedVehicle.vin }}</strong>
|
||||
</div>
|
||||
<k-button large rounded outline @click="shareVehicle(selectedVehicle)">
|
||||
<Share2 :size="18" />{{ phone.t('Apps.easyShare.share') }}
|
||||
</k-button>
|
||||
</section>
|
||||
</k-sheet>
|
||||
<k-dialog
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
LockOpen,
|
||||
Plus,
|
||||
Router,
|
||||
Share2,
|
||||
UserRound,
|
||||
UsersRound,
|
||||
WifiOff,
|
||||
@@ -34,6 +35,7 @@ import {
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
import { useHousingStore } from '@/stores/housing'
|
||||
import { useEasyShareStore } from '@/stores/easyshare'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
import type {
|
||||
HousingKey,
|
||||
@@ -43,6 +45,7 @@ import type {
|
||||
|
||||
const phone = usePhoneStore()
|
||||
const housing = useHousingStore()
|
||||
const easyShare = useEasyShareStore()
|
||||
const selectedPropertyId = ref<string | null>(null)
|
||||
const candidatesOpened = ref(false)
|
||||
const revokeCandidate = ref<HousingKey | null>(null)
|
||||
@@ -195,6 +198,18 @@ function accessLabel(property: HousingProperty): string {
|
||||
)
|
||||
}
|
||||
|
||||
function shareProperty(property: HousingProperty): void {
|
||||
easyShare.open({
|
||||
appId: 'house',
|
||||
copyText: property.name,
|
||||
id: property.id,
|
||||
kind: 'document',
|
||||
link: `skyphone://house/property/${property.id}`,
|
||||
subtitle: accessLabel(property),
|
||||
title: property.name,
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void housing.load()
|
||||
})
|
||||
@@ -413,6 +428,15 @@ onBeforeUnmount(() => {
|
||||
<Camera :size="23" />
|
||||
<span>{{ phone.t('Apps.house.viewCamera') }}</span>
|
||||
</k-glass>
|
||||
<k-glass
|
||||
:highlight="false"
|
||||
component="button"
|
||||
type="button"
|
||||
@click="shareProperty(selectedProperty)"
|
||||
>
|
||||
<Share2 :size="23" />
|
||||
<span>{{ phone.t('Apps.easyShare.share') }}</span>
|
||||
</k-glass>
|
||||
</div>
|
||||
|
||||
<k-list inset strong class="house-facts">
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
Images,
|
||||
MapPin,
|
||||
Plus,
|
||||
Share2,
|
||||
Store,
|
||||
Trash2,
|
||||
UserRound,
|
||||
@@ -34,6 +35,7 @@ import CityMarktSelect from '@/components/citymarkt/CityMarktSelect.vue'
|
||||
import CityMarktGallery from '@/components/citymarkt/CityMarktGallery.vue'
|
||||
import { useAccountStore } from '@/stores/account'
|
||||
import { useMessageMediaStore } from '@/stores/messageMedia'
|
||||
import { useEasyShareStore } from '@/stores/easyshare'
|
||||
import { usePagesStore } from '@/stores/pages'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
import type { PagesCategory, PagesPost } from '@/types/pages'
|
||||
@@ -54,6 +56,7 @@ type Tab = 'feed' | 'create' | 'profile'
|
||||
const phone = usePhoneStore()
|
||||
const account = useAccountStore()
|
||||
const messageMedia = useMessageMediaStore()
|
||||
const easyShare = useEasyShareStore()
|
||||
const pages = usePagesStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -263,6 +266,19 @@ function openCityMarktListing(): void {
|
||||
})
|
||||
}
|
||||
|
||||
function sharePost(): void {
|
||||
if (!selected.value) return
|
||||
easyShare.open({
|
||||
appId: 'local-pages',
|
||||
copyText: `${selected.value.title}\n${selected.value.body}`,
|
||||
id: selected.value.id,
|
||||
kind: 'post',
|
||||
link: `skyphone://local-pages/post/${selected.value.id}`,
|
||||
subtitle: `@${selected.value.author_name}`,
|
||||
title: selected.value.title,
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const selection = messageMedia.consumeMany<MediaContext>('local-pages:compose')
|
||||
if (selection) {
|
||||
@@ -390,7 +406,7 @@ onMounted(() => {
|
||||
<div v-if="selected.images.length" class="pages__gallery" :style="{ background: selected.images[galleryIndex]?.gradient }"><button v-if="selected.images.length > 1" @click="moveGallery(-1)"><ChevronLeft /></button><button v-if="selected.images.length > 1" @click="moveGallery(1)"><ChevronRight /></button><span>{{ galleryIndex + 1 }} / {{ selected.images.length }}</span></div>
|
||||
<article><div class="pages__author"><span>{{ selected.author_name.charAt(0).toUpperCase() }}</span><div><strong>@{{ selected.author_name }}</strong><small>{{ relativeDate(selected.created_at) }}</small></div><i>{{ label('categories', selected.category) }}</i></div><h1>{{ selected.title }}</h1><p>{{ selected.body }}</p><div class="pages__location"><MapPin :size="17" /><div><small>{{ phone.t('Apps.localPages.location') }}</small><strong>{{ selected.district ? phone.t(`Apps.citymarkt.districts.${selected.district}`) : phone.t('Apps.localPages.allLosSantos') }}</strong></div></div><button v-if="selected.source_type === 'citymarkt'" class="pages__market-link" @click="openCityMarktListing"><Store :size="18" /><span><small>{{ phone.t('Apps.localPages.sharedFrom') }}</small><strong>{{ phone.t('Apps.localPages.openCityMarkt') }}</strong></span><b v-if="selected.citymarkt_price">${{ Number(selected.citymarkt_price).toLocaleString() }}</b></button></article>
|
||||
</div>
|
||||
<div class="pages__detail-actions"><button :class="{ active: selected.is_liked }" @click="react('like')"><Heart :size="19" :fill="selected.is_liked ? 'currentColor' : 'none'" />{{ selected.like_count }} {{ phone.t('Apps.localPages.likes') }}</button><button @click="react('save')"><Bookmark :size="19" :fill="selected.is_saved ? 'currentColor' : 'none'" />{{ phone.t('Apps.localPages.save') }}</button></div>
|
||||
<div class="pages__detail-actions"><button :class="{ active: selected.is_liked }" @click="react('like')"><Heart :size="19" :fill="selected.is_liked ? 'currentColor' : 'none'" />{{ selected.like_count }} {{ phone.t('Apps.localPages.likes') }}</button><button @click="sharePost"><Share2 :size="19" />{{ phone.t('Apps.easyShare.share') }}</button><button @click="react('save')"><Bookmark :size="19" :fill="selected.is_saved ? 'currentColor' : 'none'" />{{ phone.t('Apps.localPages.save') }}</button></div>
|
||||
</section>
|
||||
|
||||
<section v-else class="pages__compose">
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
RotateCcw,
|
||||
Search,
|
||||
Send,
|
||||
Share2,
|
||||
ShieldCheck,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
@@ -26,6 +27,7 @@ import MailMarkdownEditor, {
|
||||
type MailEditorLabels,
|
||||
} from '@/components/MailMarkdownEditor.vue'
|
||||
import { useMailStore } from '@/stores/mail'
|
||||
import { useEasyShareStore } from '@/stores/easyshare'
|
||||
import { usePhoneStore } from '@/stores/phone'
|
||||
import type {
|
||||
MailComposeDraft,
|
||||
@@ -75,6 +77,7 @@ const MAIL_SWIPE_COMMIT_ANIMATION_MS = 180
|
||||
|
||||
const phone = usePhoneStore()
|
||||
const mail = useMailStore()
|
||||
const easyShare = useEasyShareStore()
|
||||
const authMode = ref<AuthMode>('login')
|
||||
const authEmail = ref('')
|
||||
const authPassword = ref('')
|
||||
@@ -563,6 +566,19 @@ function composeForward(): void {
|
||||
beginCompose(buildForwardDraft(selectedMessage.value))
|
||||
}
|
||||
|
||||
function shareMessage(): void {
|
||||
if (!selectedMessage.value) return
|
||||
easyShare.open({
|
||||
appId: 'mail',
|
||||
copyText: `${selectedMessage.value.subject}\n${mailPlainText(selectedMessage.value.body)}`,
|
||||
id: selectedMessage.value.id,
|
||||
kind: 'document',
|
||||
link: `skyphone://mail/message/${selectedMessage.value.id}`,
|
||||
subtitle: selectedMessage.value.sender,
|
||||
title: selectedMessage.value.subject || phone.t('Apps.mail.untitled'),
|
||||
})
|
||||
}
|
||||
|
||||
async function mutateSelected(
|
||||
endpoint: string,
|
||||
extra: Record<string, unknown> = {},
|
||||
@@ -1004,6 +1020,13 @@ onBeforeUnmount(() => {
|
||||
</article>
|
||||
|
||||
<footer class="mail-action-bar">
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="phone.t('Apps.easyShare.share')"
|
||||
@click="shareMessage"
|
||||
>
|
||||
<Share2 :size="20" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:aria-label="phone.t('Apps.mail.reply')"
|
||||
|
||||
Reference in New Issue
Block a user