feat(mobile): sign-in, tabs, search, collection and background player (M5)
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m39s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / ts / ts (packages + web) (push) Successful in 1m5s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 39s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m39s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 1m2s
ci / ts / ts (packages + web) (push) Successful in 1m5s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 39s
- expo-router app in src/app: connect (server check, recent servers), login, mandatory credential / password change, tabs Home / Search / Collection / Profile, album and artist pages, full-screen player - AuthSession per server with the refresh token in expo-secure-store (Android Keystore); language from the profile - player on react-native-track-player: ExoPlayer queue with a notification and lock-screen controls via a playback service, 500 MB stream cache, quality by network (Wi-Fi high, cellular medium), play events with played time, reactions, playback state restore and the endless My Wave with feedback - home: pulsing wave circle driven by BPM / energy in the cover palette, activity and mood chips that switch the wave on the fly - New Architecture is off until react-native-track-player supports it; the release build runs codegen only when it is on; query-string is a direct dependency because expo-router 5.1 imports it undeclared Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eaedd1fc62
commit
9fd616da57
28 files changed
+2590
-197
No files matched your search
@@ -0,0 +1,51 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ScrollView } from 'react-native';
|
||||
|
||||
import { useSessionCtx } from '../lib/session';
|
||||
import { Button, ErrorBox, Field, H1, Muted, Screen, useErrorText } from '../ui/kit';
|
||||
|
||||
export default function LoginScreen() {
|
||||
const { t } = useTranslation(['auth', 'common']);
|
||||
const { server, session, disconnect } = useSessionCtx();
|
||||
const errorText = useErrorText();
|
||||
const [username, setUsername] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<unknown>(null);
|
||||
|
||||
const submit = async () => {
|
||||
if (!session) return;
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
await session.login(username.trim(), password); // переход сделает Gate
|
||||
} catch (e) {
|
||||
setError(e);
|
||||
setPassword('');
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<ScrollView contentContainerStyle={{ padding: 20, gap: 14, flexGrow: 1, justifyContent: 'center' }} keyboardShouldPersistTaps="handled">
|
||||
<H1>{t('login.title')}</H1>
|
||||
<Muted>{server?.replace(/^https?:\/\//, '')}</Muted>
|
||||
<Field label={t('login.username')} value={username} onChangeText={setUsername} autoComplete="username" />
|
||||
<Field
|
||||
label={t('login.password')}
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
secure
|
||||
autoComplete="password"
|
||||
onSubmitEditing={() => void submit()}
|
||||
/>
|
||||
<ErrorBox>{error ? errorText(error) : null}</ErrorBox>
|
||||
<Button title={t('login.submit')} onPress={() => void submit()} busy={busy} disabled={!username.trim() || !password} />
|
||||
<Button title={t('common:mobile.change_server')} variant="ghost" onPress={() => void disconnect()} />
|
||||
</ScrollView>
|
||||
</Screen>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user