Fix Amber NIP-44 gate on Stash, mobile Crew detail layout, remove Quick Snap
- signerCapabilities: recognize x-amber login type as NIP-44/NIP-04 capable (Amber implements nip44_encrypt/decrypt via native intents and URL redirects). Fixes "Encryption Required" wall on Stash for users logged in with Amber on Android. - CrewDetailPage: restructure hero header so actions wrap below the title on mobile instead of squeezing it in a cramped 3-column flex; smaller avatar/title on small screens, truncating location. - MapPage: remove Quick Snap from the mobile Add speed-dial and all its handlers, refs, web-fallback input, and now-unused imports. - zapstore.yaml: explicit orange icon + metadata_sources: [] so the correct logo is always used on the Zapstore listing. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
f836763e75
commit
a83611072a
4 changed files with 99 additions and 186 deletions
|
|
@ -36,9 +36,12 @@ export function hasNip44(
|
||||||
if (!login) return false;
|
if (!login) return false;
|
||||||
// nsec + bunker: NIP-44 always available (pure JS / remote signer implements it)
|
// nsec + bunker: NIP-44 always available (pure JS / remote signer implements it)
|
||||||
if (login.type === 'nsec' || login.type === 'bunker') return true;
|
if (login.type === 'nsec' || login.type === 'bunker') return true;
|
||||||
|
// amber (NIP-55): the Amber signer implements nip44_encrypt / nip44_decrypt
|
||||||
|
// over native intents (CapacitorAmberSigner) and URL redirects (AmberSigner).
|
||||||
|
if (login.type === 'x-amber') return true;
|
||||||
// extension: probe window.nostr directly (no throwing getter)
|
// extension: probe window.nostr directly (no throwing getter)
|
||||||
if (login.type === 'extension') return !!windowNostr()?.nip44;
|
if (login.type === 'extension') return !!windowNostr()?.nip44;
|
||||||
// amber / unknown: no NIP-44
|
// unknown: no NIP-44
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,6 +54,7 @@ export function hasNip04(
|
||||||
const login = logins.find((l) => l.pubkey === user.pubkey);
|
const login = logins.find((l) => l.pubkey === user.pubkey);
|
||||||
if (!login) return false;
|
if (!login) return false;
|
||||||
if (login.type === 'nsec' || login.type === 'bunker') return true;
|
if (login.type === 'nsec' || login.type === 'bunker') return true;
|
||||||
|
if (login.type === 'x-amber') return true;
|
||||||
if (login.type === 'extension') return !!windowNostr()?.nip04;
|
if (login.type === 'extension') return !!windowNostr()?.nip04;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -239,80 +239,89 @@ export default function CrewDetailPage() {
|
||||||
{/* Hero */}
|
{/* Hero */}
|
||||||
<Card className="bg-black/90 border-2 border-teal-500/40 overflow-hidden">
|
<Card className="bg-black/90 border-2 border-teal-500/40 overflow-hidden">
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<div className="flex items-start gap-4 mb-4">
|
<div className="mb-4">
|
||||||
<Avatar className="h-20 w-20 rounded-2xl border-2 border-teal-500/40">
|
{/* Top: avatar + title/meta. Actions move below on mobile so the
|
||||||
<AvatarImage src={crew.image} alt={crew.name} className="object-cover" />
|
title never gets squeezed by the button column. */}
|
||||||
<AvatarFallback className="rounded-2xl bg-teal-900/40 text-teal-300 font-black text-xl">
|
<div className="flex items-start gap-3 sm:gap-4">
|
||||||
{crew.name.slice(0, 2).toUpperCase()}
|
<Avatar className="h-14 w-14 sm:h-20 sm:w-20 rounded-2xl border-2 border-teal-500/40 flex-shrink-0">
|
||||||
</AvatarFallback>
|
<AvatarImage src={crew.image} alt={crew.name} className="object-cover" />
|
||||||
</Avatar>
|
<AvatarFallback className="rounded-2xl bg-teal-900/40 text-teal-300 font-black text-lg sm:text-xl">
|
||||||
<div className="flex-1 min-w-0">
|
{crew.name.slice(0, 2).toUpperCase()}
|
||||||
<div className="flex items-center gap-2 mb-2 flex-wrap">
|
</AvatarFallback>
|
||||||
<h1 className="text-3xl font-black uppercase text-teal-400" style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }}>
|
</Avatar>
|
||||||
{crew.name}
|
<div className="flex-1 min-w-0">
|
||||||
</h1>
|
<div className="flex items-center gap-2 mb-2 flex-wrap">
|
||||||
<Badge variant="outline" className="gap-1 text-teal-400 border-teal-500/40">
|
<h1 className="text-2xl sm:text-3xl font-black uppercase text-teal-400 break-words" style={{ fontFamily: 'Impact, "Arial Black", sans-serif' }}>
|
||||||
<Users className="h-3 w-3" /> Crew
|
{crew.name}
|
||||||
</Badge>
|
</h1>
|
||||||
<CrewNip05Badge nip05={crew.nip05} founderPubkey={crew.pubkey} />
|
<Badge variant="outline" className="gap-1 text-teal-400 border-teal-500/40">
|
||||||
<CrewRepBadge crew={crew} />
|
<Users className="h-3 w-3" /> Crew
|
||||||
</div>
|
</Badge>
|
||||||
<div className="flex flex-wrap items-center gap-3 text-sm text-gray-400">
|
<CrewNip05Badge nip05={crew.nip05} founderPubkey={crew.pubkey} />
|
||||||
{crew.location && (
|
<CrewRepBadge crew={crew} />
|
||||||
<span className="flex items-center gap-1">
|
|
||||||
<MapPin className="h-4 w-4 text-teal-400" /> {crew.location}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{crew.founded && (
|
|
||||||
<span className="flex items-center gap-1">
|
|
||||||
<Calendar className="h-4 w-4" /> Since {crew.founded}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-end gap-2">
|
|
||||||
{canManage && (
|
|
||||||
<CrewInviteDialog
|
|
||||||
crewName={crew.name}
|
|
||||||
founderPubkey={crew.pubkey}
|
|
||||||
identifier={crew.identifier}
|
|
||||||
isFounder={isFounder}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isFounder && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Link to={`/edit-crew/${crew.pubkey}/${crew.identifier}`}>
|
|
||||||
<Button variant="outline" size="sm" className="gap-1.5 border-teal-500/40 text-teal-400 hover:bg-teal-500/10">
|
|
||||||
<Edit className="h-3.5 w-3.5" /> Edit
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<DeleteEntityDialog
|
|
||||||
event={crew.event}
|
|
||||||
entityLabel="crew"
|
|
||||||
entityName={crew.name}
|
|
||||||
redirectTo="/crews"
|
|
||||||
triggerText="Disband"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-gray-400">
|
||||||
{isAdmin && !isFounder && (
|
{crew.location && (
|
||||||
<Badge className="gap-1 bg-teal-500/20 text-teal-300 border-0">
|
<span className="flex items-center gap-1 min-w-0">
|
||||||
<ShieldCheck className="h-3 w-3" /> You're an admin
|
<MapPin className="h-4 w-4 text-teal-400 flex-shrink-0" />
|
||||||
</Badge>
|
<span className="truncate">{crew.location}</span>
|
||||||
)}
|
</span>
|
||||||
{role === 'member' && (
|
)}
|
||||||
<Badge variant="outline" className="gap-1 border-teal-500/30 text-teal-400">
|
{crew.founded && (
|
||||||
<Users className="h-3 w-3" /> Member
|
<span className="flex items-center gap-1">
|
||||||
</Badge>
|
<Calendar className="h-4 w-4 flex-shrink-0" /> Since {crew.founded}
|
||||||
)}
|
</span>
|
||||||
{role === 'none' && (
|
)}
|
||||||
<CrewRequestToJoin
|
</div>
|
||||||
crewName={crew.name}
|
</div>
|
||||||
founderPubkey={crew.pubkey}
|
|
||||||
admins={crew.admins}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Actions: full-width wrap under the header, comfortable tap targets */}
|
||||||
|
{(canManage || isFounder || (isAdmin && !isFounder) || role === 'member' || role === 'none') && (
|
||||||
|
<div className="mt-4 flex flex-wrap items-center gap-2">
|
||||||
|
{canManage && (
|
||||||
|
<CrewInviteDialog
|
||||||
|
crewName={crew.name}
|
||||||
|
founderPubkey={crew.pubkey}
|
||||||
|
identifier={crew.identifier}
|
||||||
|
isFounder={isFounder}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isFounder && (
|
||||||
|
<>
|
||||||
|
<Link to={`/edit-crew/${crew.pubkey}/${crew.identifier}`}>
|
||||||
|
<Button variant="outline" size="sm" className="gap-1.5 border-teal-500/40 text-teal-400 hover:bg-teal-500/10">
|
||||||
|
<Edit className="h-3.5 w-3.5" /> Edit
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<DeleteEntityDialog
|
||||||
|
event={crew.event}
|
||||||
|
entityLabel="crew"
|
||||||
|
entityName={crew.name}
|
||||||
|
redirectTo="/crews"
|
||||||
|
triggerText="Disband"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{isAdmin && !isFounder && (
|
||||||
|
<Badge className="gap-1 bg-teal-500/20 text-teal-300 border-0">
|
||||||
|
<ShieldCheck className="h-3 w-3" /> You're an admin
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{role === 'member' && (
|
||||||
|
<Badge variant="outline" className="gap-1 border-teal-500/30 text-teal-400">
|
||||||
|
<Users className="h-3 w-3" /> Member
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{role === 'none' && (
|
||||||
|
<CrewRequestToJoin
|
||||||
|
crewName={crew.name}
|
||||||
|
founderPubkey={crew.pubkey}
|
||||||
|
admins={crew.admins}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{crew.bio && (
|
{crew.bio && (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useMemo, useEffect, useRef, lazy, Suspense } from 'react';
|
import { useState, useMemo, useEffect, lazy, Suspense } from 'react';
|
||||||
import { useSeoMeta } from '@unhead/react';
|
import { useSeoMeta } from '@unhead/react';
|
||||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
import { Layout } from '@/components/Layout';
|
import { Layout } from '@/components/Layout';
|
||||||
|
|
@ -66,7 +66,6 @@ import {
|
||||||
Bitcoin,
|
Bitcoin,
|
||||||
SlidersHorizontal,
|
SlidersHorizontal,
|
||||||
Check,
|
Check,
|
||||||
Camera,
|
|
||||||
Navigation,
|
Navigation,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
MapPin,
|
MapPin,
|
||||||
|
|
@ -83,7 +82,6 @@ import {
|
||||||
import { SpotChat } from '@/components/SpotChat';
|
import { SpotChat } from '@/components/SpotChat';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { isNativeCapture, captureFromNativeCamera } from '@/lib/quickCapture';
|
|
||||||
import { getCurrentPosition, GeolocationError, isNativeGeolocation } from '@/lib/geolocation';
|
import { getCurrentPosition, GeolocationError, isNativeGeolocation } from '@/lib/geolocation';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -603,16 +601,11 @@ export default function MapPage() {
|
||||||
return () => window.removeEventListener('sasquatch:panelchange', handler);
|
return () => window.removeEventListener('sasquatch:panelchange', handler);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Mobile "Add" speed-dial (fans out Add Spot / Shop / Tour / Quick Snap)
|
// Mobile "Add" speed-dial (fans out Add Spot / Shop / Event / Gear / Tour)
|
||||||
const [addDialOpen, setAddDialOpen] = useState(false);
|
const [addDialOpen, setAddDialOpen] = useState(false);
|
||||||
// Mobile layers popover
|
// Mobile layers popover
|
||||||
const [layersOpen, setLayersOpen] = useState(false);
|
const [layersOpen, setLayersOpen] = useState(false);
|
||||||
|
|
||||||
// Quick Snap — camera-first. On native (the APK) we open the OS camera
|
|
||||||
// directly via the runtime Camera plugin (with its built-in preview/retake).
|
|
||||||
// On web there's no reliable way to force the camera from JS, so we fall back
|
|
||||||
// to a file input hinting the rear camera. A single photo is all a spot needs.
|
|
||||||
const quickSnapInputRef = useRef<HTMLInputElement>(null);
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
// BTC Map center for fetching places
|
// BTC Map center for fetching places
|
||||||
|
|
@ -797,80 +790,6 @@ export default function MapPage() {
|
||||||
navigate(`/create-spot?lat=${lat.toFixed(6)}&lng=${lng.toFixed(6)}`);
|
navigate(`/create-spot?lat=${lat.toFixed(6)}&lng=${lng.toFixed(6)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Quick Snap entry point — camera-first on native, file input on web.
|
|
||||||
const handleQuickSnap = async () => {
|
|
||||||
setAddDialOpen(false);
|
|
||||||
if (isNativeCapture()) {
|
|
||||||
try {
|
|
||||||
const file = await captureFromNativeCamera();
|
|
||||||
if (file) handleQuickCameraCapture(file);
|
|
||||||
return; // handled natively (user cancel returns null → do nothing)
|
|
||||||
} catch (err) {
|
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
|
||||||
console.warn('[QuickSnap] native camera failed:', msg);
|
|
||||||
// A permission error means the OS blocked the camera. Don't silently
|
|
||||||
// open the gallery — that's confusing and looks like the bug. Tell the
|
|
||||||
// user how to fix it and stop here.
|
|
||||||
if (/permission|denied|not authorized|unavailable/i.test(msg)) {
|
|
||||||
toast({
|
|
||||||
title: 'Camera permission needed',
|
|
||||||
description:
|
|
||||||
'Allow camera access for SkateSpots in your device Settings → Apps → SkateSpots → Permissions, then try again.',
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Any other native error → fall back to the photo picker so the user
|
|
||||||
// can still add a spot.
|
|
||||||
toast({
|
|
||||||
title: 'Camera unavailable',
|
|
||||||
description: 'Opening the photo picker instead.',
|
|
||||||
});
|
|
||||||
// fall through to the web/file-input path below
|
|
||||||
}
|
|
||||||
}
|
|
||||||
quickSnapInputRef.current?.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Web file-input change → hand the chosen photo to the upload flow.
|
|
||||||
const handleQuickSnapInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const file = e.target.files?.[0];
|
|
||||||
// Reset the input so picking the same file again still fires onChange.
|
|
||||||
e.target.value = '';
|
|
||||||
if (file) handleQuickCameraCapture(file);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle quick camera capture — the whole point of Quick Snap is SPEED, so we
|
|
||||||
// do NOT block on the Blossom upload here (that needs an Amber signature and
|
|
||||||
// can be slow or fail — which previously left the user stranded after taking
|
|
||||||
// the photo). Instead: stash the raw photo, grab location, and navigate to the
|
|
||||||
// Add Spot form IMMEDIATELY. The form uploads the photo there.
|
|
||||||
const handleQuickCameraCapture = async (file: File) => {
|
|
||||||
try {
|
|
||||||
// Stash the captured photo as a data URL so the Create Spot page can pick
|
|
||||||
// it up and upload it in the right context.
|
|
||||||
const dataUrl = await new Promise<string>((resolve, reject) => {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = () => resolve(reader.result as string);
|
|
||||||
reader.onerror = () => reject(new Error('Could not read photo'));
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
});
|
|
||||||
sessionStorage.setItem('quickCapturePendingPhoto', dataUrl);
|
|
||||||
// Clear any older uploaded-URL key so the form doesn't use a stale image.
|
|
||||||
sessionStorage.removeItem('quickCaptureImage');
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('[QuickSnap] could not stash photo:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to grab location quickly, but never let it block navigation.
|
|
||||||
try {
|
|
||||||
const { lat, lng } = await getCurrentPosition();
|
|
||||||
navigate(`/create-spot?lat=${lat.toFixed(6)}&lng=${lng.toFixed(6)}&quickCapture=true`);
|
|
||||||
} catch {
|
|
||||||
navigate('/create-spot?quickCapture=true');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter combined spots and shops
|
// Filter combined spots and shops
|
||||||
const filteredSpots = useMemo(() => {
|
const filteredSpots = useMemo(() => {
|
||||||
let filtered = allLocations.filter((item) => {
|
let filtered = allLocations.filter((item) => {
|
||||||
|
|
@ -1232,7 +1151,7 @@ export default function MapPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick actions for logged in users.
|
{/* Quick actions for logged in users.
|
||||||
Note: Add Spot / Shop / Tour / Quick Snap live in the
|
Note: Add Spot / Shop / Event / Gear / Tour live in the
|
||||||
map's "Add" speed-dial FAB — here we only surface the
|
map's "Add" speed-dial FAB — here we only surface the
|
||||||
shortcuts that aren't already on the map (Marketplace,
|
shortcuts that aren't already on the map (Marketplace,
|
||||||
Stash) to avoid duplication. */}
|
Stash) to avoid duplication. */}
|
||||||
|
|
@ -1803,19 +1722,6 @@ export default function MapPage() {
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
{/* Quick Snap — camera-first (native) / file-input (web) */}
|
|
||||||
<button
|
|
||||||
onClick={handleQuickSnap}
|
|
||||||
className="flex items-center gap-2.5 animate-in fade-in slide-in-from-bottom-2 duration-200"
|
|
||||||
style={{ animationDelay: '200ms' }}
|
|
||||||
>
|
|
||||||
<span className="px-2.5 py-1 rounded-lg bg-black/85 backdrop-blur text-white text-xs font-bold shadow-lg border border-white/10">
|
|
||||||
Quick Snap
|
|
||||||
</span>
|
|
||||||
<span className="h-11 w-11 rounded-full flex items-center justify-center shadow-lg bg-black/80 border border-white/20">
|
|
||||||
<Camera className="h-4 w-4 text-[#ccff00]" />
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
@ -1828,7 +1734,7 @@ export default function MapPage() {
|
||||||
? 'bg-white rotate-45'
|
? 'bg-white rotate-45'
|
||||||
: 'bg-[#ff0040] shadow-[#ff0040]/40 hover:bg-[#ff0040]/90'
|
: 'bg-[#ff0040] shadow-[#ff0040]/40 hover:bg-[#ff0040]/90'
|
||||||
)}
|
)}
|
||||||
aria-label={addDialOpen ? 'Close add menu' : 'Add spot, shop, tour, or snap'}
|
aria-label={addDialOpen ? 'Close add menu' : 'Add spot, shop, event, gear, or tour'}
|
||||||
>
|
>
|
||||||
<Plus className={cn('h-7 w-7 transition-colors', addDialOpen ? 'text-black' : 'text-white')} strokeWidth={2.5} />
|
<Plus className={cn('h-7 w-7 transition-colors', addDialOpen ? 'text-black' : 'text-white')} strokeWidth={2.5} />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1900,18 +1806,6 @@ export default function MapPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Snap — web fallback input (native uses the Camera plugin above).
|
|
||||||
`capture="environment"` hints the rear camera; browsers may honor it
|
|
||||||
or show a chooser. Single photo is all we need. */}
|
|
||||||
<input
|
|
||||||
ref={quickSnapInputRef}
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
capture="environment"
|
|
||||||
className="hidden"
|
|
||||||
onChange={handleQuickSnapInput}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Spot Bottom Sheet — slides up when a marker is tapped, map stays visible */}
|
{/* Spot Bottom Sheet — slides up when a marker is tapped, map stays visible */}
|
||||||
<Sheet open={!!selectedSpot} onOpenChange={(open) => { if (!open) setSelectedSpot(null); }}>
|
<Sheet open={!!selectedSpot} onOpenChange={(open) => { if (!open) setSelectedSpot(null); }}>
|
||||||
<SheetContent
|
<SheetContent
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,11 @@ description: |
|
||||||
website: https://skate-spots.org
|
website: https://skate-spots.org
|
||||||
license: custom
|
license: custom
|
||||||
|
|
||||||
|
# App icon (local path or URL). YAML config ALWAYS wins over the icon
|
||||||
|
# extracted from the APK or pulled from external metadata sources.
|
||||||
|
# This is the orange SkateSpots logo used everywhere else.
|
||||||
|
icon: ./public/images/icon-1024.png
|
||||||
|
|
||||||
# Tags for Zapstore discovery
|
# Tags for Zapstore discovery
|
||||||
tags:
|
tags:
|
||||||
- sports
|
- sports
|
||||||
|
|
@ -80,9 +85,10 @@ supported_nips:
|
||||||
# METADATA SOURCES
|
# METADATA SOURCES
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
# Pull icon + screenshots from Play Store (no GitHub needed)
|
# Do NOT enrich icon/metadata from external sources — we set everything
|
||||||
metadata_sources:
|
# explicitly here so the correct orange logo is always used. Play Store
|
||||||
- playstore
|
# enrichment was overriding our icon with the wrong (blue launcher) image.
|
||||||
|
metadata_sources: []
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
# RELEASE NOTES
|
# RELEASE NOTES
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue