feat: better android file picker, validate that selected directories have read/write permissions

This commit is contained in:
ThaUnknown 2025-07-20 16:38:48 +02:00
parent 6b37f517c9
commit 4bf572e71f
No known key found for this signature in database
6 changed files with 55 additions and 21 deletions

View file

@ -1,6 +1,6 @@
{
"name": "ui",
"version": "6.4.71",
"version": "6.4.72",
"license": "BUSL-1.1",
"private": true,
"packageManager": "pnpm@9.15.5",
@ -33,6 +33,7 @@
"gql.tada": "^1.8.10",
"hayase-extensions": "github:hayase-app/extensions",
"jassub": "^1.8.6",
"native": "github:hayase-app/native",
"rollup-plugin-license": "^3.6.0",
"simple-copy": "^2.2.1",
"svelte": "^4.2.19",
@ -74,7 +75,6 @@
"js-levenshtein": "^1.1.6",
"lucide-svelte": "^0.511.0",
"marked": "^15.0.11",
"native": "github:hayase-app/native",
"p2pt": "github:ThaUnknown/p2pt#modernise",
"semver": "^7.7.2",
"simple-store-svelte": "^1.0.6",

View file

@ -89,9 +89,6 @@ importers:
marked:
specifier: ^15.0.11
version: 15.0.11
native:
specifier: github:hayase-app/native
version: https://codeload.github.com/hayase-app/native/tar.gz/31eb40c6c662dc58030bd70a279d9b8d38e3b904
p2pt:
specifier: github:ThaUnknown/p2pt#modernise
version: https://codeload.github.com/ThaUnknown/p2pt/tar.gz/9ad7a56ed6ee43f5664ebad33b803702ee349316
@ -183,6 +180,9 @@ importers:
jassub:
specifier: ^1.8.6
version: 1.8.6
native:
specifier: github:hayase-app/native
version: https://codeload.github.com/hayase-app/native/tar.gz/61d27bc30411840846e4e8ec5ea8ce875305e424
rollup-plugin-license:
specifier: ^3.6.0
version: 3.6.0(picomatch@4.0.2)(rollup@4.40.2)
@ -1900,8 +1900,8 @@ packages:
engines: {node: ^18 || >=20}
hasBin: true
native@https://codeload.github.com/hayase-app/native/tar.gz/31eb40c6c662dc58030bd70a279d9b8d38e3b904:
resolution: {tarball: https://codeload.github.com/hayase-app/native/tar.gz/31eb40c6c662dc58030bd70a279d9b8d38e3b904}
native@https://codeload.github.com/hayase-app/native/tar.gz/61d27bc30411840846e4e8ec5ea8ce875305e424:
resolution: {tarball: https://codeload.github.com/hayase-app/native/tar.gz/61d27bc30411840846e4e8ec5ea8ce875305e424}
version: 1.0.0
natural-compare@1.4.0:
@ -4550,7 +4550,7 @@ snapshots:
nanoid@5.1.5: {}
native@https://codeload.github.com/hayase-app/native/tar.gz/31eb40c6c662dc58030bd70a279d9b8d38e3b904: {}
native@https://codeload.github.com/hayase-app/native/tar.gz/61d27bc30411840846e4e8ec5ea8ce875305e424: {}
natural-compare@1.4.0: {}

View file

@ -79,6 +79,7 @@ export function hover (node: HTMLElement, [cb = noop, hoverUpdate = noop]: [type
const ctrl = new AbortController()
node.addEventListener('wheel', e => {
// cheap way to update hover state on scroll
// TODO: this is bad on touch, but good on mouse, fix it
if (document.elementsFromPoint(e.clientX, e.clientY).includes(node)) {
if (lastHoverElement !== hoverUpdate) lastHoverElement?.(false)
lastHoverElement = hoverUpdate

View file

@ -41,5 +41,6 @@ export default {
playerSeek: '2',
playerSkip: false,
playerSkipFiller: false,
minimalPlayerUI: false
minimalPlayerUI: false,
androidStorageType: 'cache'
}

View file

@ -1,14 +1,29 @@
<script lang='ts'>
import { toast } from 'svelte-sonner'
import SettingCard from '$lib/components/SettingCard.svelte'
import { Button } from '$lib/components/ui/button'
import { SingleCombo } from '$lib/components/ui/combobox'
import { Input } from '$lib/components/ui/input'
import { Switch } from '$lib/components/ui/switch'
import native from '$lib/modules/native'
import { settings, SUPPORTS } from '$lib/modules/settings'
async function selectDownloadFolder () {
$settings.torrentPath = await native.selectDownload()
async function selectDownloadFolder (type?: string) {
try {
$settings.torrentPath = await native.selectDownload(type as 'cache' | 'internal' | 'sdcard' | undefined)
} catch (error) {
toast.error('Failed to select download folder. Please try again.', {
description: error instanceof Error ? error.message : 'Unknown error occurred.'
})
}
}
const androidDirectories = {
cache: 'Cache',
internal: 'Internal Storage',
sdcard: 'SD Card'
} as const
</script>
<div class='space-y-3 pb-10 lg:max-w-4xl'>
@ -27,14 +42,15 @@
{/if}
<div class='font-weight-bold text-xl font-bold'>Client Settings</div>
<SettingCard let:id title='Torrent Download Location' description='Path to the folder used to store torrents. By default this is the TMP folder, which might lose data when your OS tries to reclaim storage. {SUPPORTS.isAndroid ? 'RESTART IS REQUIRED. /sdcard/ is internal storage, not external SD Cards. /storage/AB12-34CD/ is external storage, not internal. Thank you Android!' : ''}'>
<SettingCard let:id title='Torrent Download Location' description={`Path to the folder used to store torrents. By default this is the TEMP cache folder, which might lose data when your OS tries to reclaim storage.${SUPPORTS.isAndroid ? '\n\nSD Card saves to the Cards Download folder. If SD Card is not available torrents will automatically be saved to the Phone\'s Downloads folder' : ''}`}>
<div class='flex'>
{#if !SUPPORTS.isAndroid}
<Input type='url' bind:value={$settings.torrentPath} readonly {id} placeholder='/tmp' class='sm:w-60 bg-background rounded-r-none pointer-events-none' />
<Input type='url' bind:value={$settings.torrentPath} readonly {id} placeholder='/tmp/webtorrent' class='sm:w-60 bg-background rounded-r-none pointer-events-none' />
<Button class='rounded-l-none font-bold' on:click={() => selectDownloadFolder()} variant='secondary'>Select Folder</Button>
{:else}
<Input type='text' bind:value={$settings.torrentPath} {id} placeholder='/tmp' class='sm:w-60 bg-background rounded-r-none' />
<Input type='text' bind:value={$settings.torrentPath} {id} placeholder='/tmp/webtorrent' class='sm:w-60 bg-background rounded-r-none border-r-0' />
<SingleCombo bind:value={$settings.androidStorageType} items={androidDirectories} class='w-32 shrink-0 border-input border rounded-l-none ' onSelected={selectDownloadFolder} />
{/if}
<Button class='rounded-l-none font-bold' on:click={selectDownloadFolder} variant='secondary'>Select Folder</Button>
</div>
</SettingCard>
<SettingCard let:id title='Persist Files' description="Keeps torrents files instead of deleting them after a new torrent is played. This doesn't seed the files, only keeps them on your drive. This will quickly fill up your storage.">

View file

@ -1,9 +1,12 @@
<script lang='ts'>
import { toast } from 'svelte-sonner'
import Footer, { type Checks } from '../Footer.svelte'
import Progress from '../Progress.svelte'
import SettingCard from '$lib/components/SettingCard.svelte'
import { Button } from '$lib/components/ui/button'
import { SingleCombo } from '$lib/components/ui/combobox'
import { Input } from '$lib/components/ui/input'
import { Switch } from '$lib/components/ui/switch'
import native from '$lib/modules/native'
@ -11,10 +14,22 @@
import { SUPPORTS, settings } from '$lib/modules/settings'
import { fastPrettyBytes } from '$lib/utils'
async function selectDownloadFolder () {
$settings.torrentPath = await native.selectDownload()
async function selectDownloadFolder (type?: string) {
try {
$settings.torrentPath = await native.selectDownload(type as 'cache' | 'internal' | 'sdcard' | undefined)
} catch (error) {
toast.error('Failed to select download folder. Please try again.', {
description: error instanceof Error ? error.message : 'Unknown error occurred.'
})
}
}
const androidDirectories = {
cache: 'Cache',
internal: 'Internal Storage',
sdcard: 'SD Card'
} as const
async function checkSpaceRequirements (_path: string): Checks['promise'] {
const space = await native.checkAvailableSpace()
if (space < 1e9) return { status: 'error', text: `${fastPrettyBytes(space)} available, 1GB is the recommended minimum.` }
@ -28,14 +43,15 @@
<Progress />
<div class='space-y-3 lg:max-w-4xl pt-5 h-full overflow-y-auto' use:dragScroll>
<SettingCard class='bg-transparent' let:id title='Torrent Download Location' description='Path to the folder used to store torrents. By default this is the TMP folder, which might lose data when your OS tries to reclaim storage. {SUPPORTS.isAndroid ? 'RESTART IS REQUIRED. /sdcard/ is internal storage, not external SD Cards. /storage/AB12-34CD/ is external storage, not internal. Thank you Android!' : ''}'>
<SettingCard let:id title='Torrent Download Location' description={`Path to the folder used to store torrents. By default this is the TEMP cache folder, which might lose data when your OS tries to reclaim storage.${SUPPORTS.isAndroid ? '\n\nSD Card saves to the Cards Download folder. If SD Card is not available torrents will automatically be saved to the Phone\'s Downloads folder' : ''}`}>
<div class='flex'>
{#if !SUPPORTS.isAndroid}
<Input type='url' bind:value={$settings.torrentPath} readonly {id} placeholder='/tmp' class='sm:w-60 bg-background rounded-r-none pointer-events-none' />
<Input type='url' bind:value={$settings.torrentPath} readonly {id} placeholder='/tmp/webtorrent' class='sm:w-60 bg-background rounded-r-none pointer-events-none' />
<Button class='rounded-l-none font-bold' on:click={() => selectDownloadFolder()} variant='secondary'>Select Folder</Button>
{:else}
<Input type='text' bind:value={$settings.torrentPath} {id} placeholder='/tmp' class='sm:w-60 bg-background rounded-r-none' />
<Input type='text' bind:value={$settings.torrentPath} {id} placeholder='/tmp/webtorrent' class='sm:w-60 bg-background rounded-r-none border-r-0' />
<SingleCombo bind:value={$settings.androidStorageType} items={androidDirectories} class='w-32 shrink-0 border-input border rounded-l-none ' onSelected={selectDownloadFolder} />
{/if}
<Button class='rounded-l-none font-bold' on:click={selectDownloadFolder} variant='secondary'>Select Folder</Button>
</div>
</SettingCard>
<SettingCard class='bg-transparent' let:id title='Persist Files' description="Keeps torrents files instead of deleting them after a new torrent is played. This doesn't seed the files, only keeps them on your drive. This will quickly fill up your storage.">