Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
# Changelog

## Unreleased

## 2.0.0

- Saving no longer fails with "The row changed since it was read" once apps are pinned. The console renumbers the
pinned apps every half minute while Lineup is open; a save now checks the order, not the exact numbers (#19).
- All Apps, the App Library tile on the Media tab, can be hidden and shown again from Hidden apps, like the PS Store
and the Media Gallery (#14).
- Switching to the Media tab right after Lineup opens waits for the service instead of failing after a few seconds,
and asks again once (#15).
- Shorter Brazilian Portuguese messages, so they fit on the notification (#16).
- The QR code on the star page draws whole, so a phone can read it. It was cut off about halfway (#21).
- The Media Gallery moves between the Games and Media tabs (focus it on the row, Options, Move), and stays there
after a restart or rest mode. The PS Store only hides. Game Library and App Library never move or hide.
after a restart or rest mode. The PS Store and App Library only hide. Game Library never moves or hides.
- The Media Gallery is on its tab's row: it can be pinned and moved from the row like an app.
- Welcome can be shown, on every profile, and hidden again (Options, Hidden apps).
- Every other system tile that is showing can be hidden, and stays hidden.
- Every other system tile that is showing on a tab can be hidden, and stays hidden.
- The Media Gallery goes back to its place in the Games row's system group when it moves back from Media.
- Moving an app now moves its concept too, which 1.8.0 left on the old tab. Apps moved before are fixed when
Lineup starts.
- More options has Remove TabShifter leftovers: TabShifter's "TS" tile goes from both databases.
- More options has Remove TabShifter leftovers: TabShifter's "TS" tile goes from both databases, and its folders.
The message says when only folders were left.
- Backups: the first time this version runs, it keeps both databases for good in /data/lineup/backups
(original-app.db, original-appinfo.db), before its own checks write. After an upgrade they already hold what
earlier versions of Lineup changed, so they are not a copy from before Lineup. Two backups in the same second no
Expand Down
24 changes: 14 additions & 10 deletions app/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import {Archives, Downloads, FileSystem, Http, Payloads} from '@ps5-react/core';
import {T} from './i18n.js';

export const VERSION = '1.8.0';
export const VERSION = '2.0.0';
// "local", or the commit of a CI build, as LINEUPD_BUILD in lineupd.c: a service of another build is replaced.
export const BUILD = 'local';
const SERVICE = `${VERSION}+${BUILD}`;
Expand Down Expand Up @@ -85,13 +85,17 @@ export async function ensureService(onStep) {
throw problem(T.service.noAnswer);
}

// The service's own message is English detail; the title is always in the interface language.
async function read(path, what) {
// The service's own message is English detail; the title is always in the interface language. The service answers one
// request at a time and checks its databases right after starting, so a read can wait behind that: `patient` waits
// longer and asks again once.
async function read(path, what, patient = false) {
let reply;
try {
reply = await call(path, {timeoutMs: 8000});
} catch {
throw problem(T.service.stopped(what));
for (let attempt = 0; !reply; attempt++) {
try {
reply = await call(path, {timeoutMs: patient ? 20000 : 8000});
} catch {
if (!patient || attempt) throw problem(T.service.stopped(what));
}
}
if (!reply.ok) throw new Problem(T.service.readFailed(what), reply.data.error ?? T.service.status(reply.status));
return reply.data;
Expand All @@ -103,7 +107,7 @@ export const profiles = async () => (await read('/profiles', T.service.profiles)
* (triggers lost) or 'off'.
*/
export async function row(user, tab = 'games') {
const data = await read(`/row?user=${user}&tab=${tab}`, T.service.row);
const data = await read(`/row?user=${user}&tab=${tab}`, T.service.row, true);
return {games: data.games ?? [], pins: data.pins ?? 'off'};
}
export const iconUrl = game => `${BASE}/icon/${game.titleId}?m=${game.icon}`;
Expand Down Expand Up @@ -147,11 +151,11 @@ export async function hidden(user) {
return {hidden: data.hidden ?? [], system: data.system ?? []};
}

/** Removes TabShifter's tile from the home screen databases. Resolves to {removed} or {error}. */
/** Removes TabShifter's tile and folders. Resolves to {removed, folders} (tiles and folders deleted) or {error}. */
export async function removeTabShifter() {
try {
const reply = await call('/tabshifter/remove', {method: 'POST', timeoutMs: 15000});
return reply.ok ? {removed: reply.data.removed ?? 0}
return reply.ok ? {removed: reply.data.removed ?? 0, folders: reply.data.folders ?? 0}
: {error: T.cleanup.failed(reply.data.error ?? T.service.status(reply.status))};
} catch {
return {error: T.cleanup.failed(T.pins.noAnswer)};
Expand Down
2 changes: 1 addition & 1 deletion app/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"titleId": "PPSA99077",
"contentId": "UP9000-PPSA99077_00-LINEUP0000000000",
"name": "Lineup",
"version": "01.008.000",
"version": "02.000.000",
"render": {
"width": 1920,
"height": 1080
Expand Down
25 changes: 14 additions & 11 deletions app/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const en = {
moveTo: tab => `Move to ${tab}`, hide: 'Hide from the home screen',
store: 'PS Store', storeHidden: 'Hidden', storeShown: 'Showing',
gallery: 'Media Gallery', galleryHidden: 'Hidden', galleryShown: 'Showing',
library: 'All Apps', libraryHidden: 'Hidden', libraryShown: 'Showing',
welcome: 'Welcome, on every profile', welcomeShown: 'Showing', welcomeHidden: 'Hidden',
systemLabel: 'Other system tiles', hideTile: 'Hide',
show: 'Bring back',
Expand All @@ -94,8 +95,8 @@ const en = {
body: 'Lineup backs up both home screen databases first, then takes TabShifter\'s "TS" tile out of them and '
+ 'deletes its folders. Do this only after deleting TabShifter.',
confirm: 'Remove', cancel: 'Cancel',
done: removed => (removed ? "TabShifter's tile was removed. The home screen updates when Lineup closes."
: 'No TabShifter tile was found.'),
done: (removed, folders) => (removed ? "TabShifter's tile was removed. The home screen updates when Lineup closes."
: folders ? 'No TabShifter tile was left; its folders were deleted.' : 'No TabShifter tile was found.'),
failed: detail => `Nothing was removed (${detail}).`,
},
report: {
Expand Down Expand Up @@ -189,9 +190,9 @@ const pt = {
profiles: 'os perfis',
row: 'a fileira',
hidden: 'os apps escondidos',
saveNoAnswer: 'O serviço não respondeu ao salvar. Abra o app de novo para conferir a ordem.',
saveNoAnswer: 'O serviço não respondeu. Reabra o app e confira a ordem.',
saveStatus: code => `O serviço respondeu ${code}. Nada foi gravado.`,
stale: 'A fileira mudou desde que foi lida. Ela foi carregada de novo.',
stale: 'A fileira mudou e foi carregada de novo.',
},
emptyRow: ['A fileira está vazia', 'Este perfil não tem jogos na tela inicial.'],
noProfiles: ['Nenhum perfil encontrado', 'O serviço não achou perfis com fileira na tela inicial.'],
Expand All @@ -217,7 +218,7 @@ const pt = {
missingBody: 'O banco de dados da tela inicial foi reconstruído ou atualizado, e os apps não seguram mais o '
+ 'lugar. Fixar de novo?',
reapply: 'Fixar de novo', later: 'Agora não',
keepFree: 'Deixe um lugar da tela inicial sem fixar, para o último app aberto.',
keepFree: 'Deixe um lugar livre para o último app aberto.',
fullTitle: 'Todos os lugares da tela inicial estão fixos',
fullBody: 'O último app aberto fica sem lugar ali e vai parar na biblioteca. '
+ 'Liberar o primeiro lugar para ele?',
Expand All @@ -234,6 +235,7 @@ const pt = {
moveTo: tab => `Mover para ${tab}`, hide: 'Esconder da tela inicial',
store: 'PS Store', storeHidden: 'Escondida', storeShown: 'Aparecendo',
gallery: 'Galeria de mídia', galleryHidden: 'Escondida', galleryShown: 'Aparecendo',
library: 'Todos os apps', libraryHidden: 'Escondido', libraryShown: 'Aparecendo',
welcome: 'Boas-vindas, em todos os perfis', welcomeShown: 'Aparecendo', welcomeHidden: 'Escondida',
systemLabel: 'Outros blocos do sistema', hideTile: 'Esconder',
show: 'Trazer de volta',
Expand All @@ -244,10 +246,10 @@ const pt = {
moveBody: 'Ele muda em todos os perfis e perde o lugar fixo. Dá para mover de volta do mesmo jeito.',
confirmHide: 'Esconder', confirmMove: 'Mover', cancel: 'Cancelar',
done: {
hide: name => `${name} escondido. A tela inicial atualiza quando o Lineup fechar.`,
show: name => `${name} voltou. A tela inicial atualiza quando o Lineup fechar.`,
games: name => `${name} foi para Jogos. A tela inicial atualiza quando o Lineup fechar.`,
media: name => `${name} foi para Mídia. A tela inicial atualiza quando o Lineup fechar.`,
hide: name => `${name} escondido. A tela inicial atualiza ao fechar o Lineup.`,
show: name => `${name} voltou. A tela inicial atualiza ao fechar o Lineup.`,
games: name => `${name} foi para Jogos. A tela inicial atualiza ao fechar o Lineup.`,
media: name => `${name} foi para Mídia. A tela inicial atualiza ao fechar o Lineup.`,
},
failed: detail => `Nada mudou (${detail}).`,
},
Expand All @@ -257,8 +259,9 @@ const pt = {
body: 'O Lineup faz backup dos dois bancos da tela inicial antes, tira deles o bloco "TS" do TabShifter e apaga '
+ 'as pastas dele. Faça isso só depois de apagar o TabShifter.',
confirm: 'Remover', cancel: 'Cancelar',
done: removed => (removed ? 'O bloco do TabShifter foi removido. A tela inicial atualiza quando o Lineup fechar.'
: 'Nenhum bloco do TabShifter foi encontrado.'),
done: (removed, folders) => (removed ? 'O bloco do TabShifter foi removido. A tela inicial atualiza ao fechar o Lineup.'
: folders ? 'Não havia bloco do TabShifter; as pastas foram apagadas.'
: 'Nenhum bloco do TabShifter foi encontrado.'),
failed: detail => `Nada foi removido (${detail}).`,
},
report: {
Expand Down
24 changes: 17 additions & 7 deletions app/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ const PICK_HINTS = [['cross', H.choose], ['circle', H.back]];
const STORE = 'NPXS40047';
const GALLERY = 'NPXS40075';
const WELCOME = 'NPXS40063';
const APP_LIBRARY = 'NPXS40139';

// The side sheet's pages: title and subtitle. The menu and Updates work out their own subtitle.
const SHEETS = {menu: [T.menu.title], profiles: [T.profilesTitle, T.profilesSubtitle], updates: [T.updates.title],
Expand Down Expand Up @@ -631,7 +632,7 @@ function App() {
const removeTabShifter = async () => {
const p = ref.current;
patch({layer: 'busy'});
const {removed, error} = await api.removeTabShifter();
const {removed, folders, error} = await api.removeTabShifter();
if (!error && removed) {
const keep = {focus: p.focus};
try {
Expand All @@ -642,7 +643,7 @@ function App() {
}
}
close();
toast(error || T.cleanup.done(removed));
toast(error || T.cleanup.done(removed, folders));
};
const askApp = layer => {
if (changed(ref.current)) toast(T.tabs.saveFirst);
Expand All @@ -669,11 +670,12 @@ function App() {
const hiddenApps = s.hidden ?? [];
const storeHidden = hiddenApps.some(app => app.titleId === STORE);
const galleryHidden = hiddenApps.some(app => app.titleId === GALLERY);
const otherHidden = hiddenApps.filter(app => app.titleId !== STORE && app.titleId !== GALLERY);
const libraryHidden = hiddenApps.some(app => app.titleId === APP_LIBRARY);
const otherHidden = hiddenApps.filter(app => ![STORE, GALLERY, APP_LIBRARY].includes(app.titleId));
const otherTab = s.tab === 'games' ? 'media' : 'games';
// Welcome shows on every profile; the other system tiles showing can hide on this one.
const welcome = (s.system ?? []).find(tile => tile.titleId === WELCOME);
const otherSystem = (s.system ?? []).filter(tile => tile.visible && ![STORE, GALLERY, WELCOME].includes(tile.titleId));
const otherSystem = (s.system ?? []).filter(tile => tile.visible && ![STORE, GALLERY, WELCOME, APP_LIBRARY].includes(tile.titleId));

if (s.phase === 'loading') {
return (
Expand Down Expand Up @@ -745,6 +747,8 @@ function App() {
onPress={() => changeApp(STORE, storeHidden ? 'show' : 'hide', T.apps.store)} />,
<SheetItem key="gallery" label={T.apps.gallery} detail={galleryHidden ? T.apps.galleryHidden : T.apps.galleryShown}
onPress={() => changeApp(GALLERY, galleryHidden ? 'show' : 'hide', T.apps.gallery)} />,
<SheetItem key="library" label={T.apps.library} detail={libraryHidden ? T.apps.libraryHidden : T.apps.libraryShown}
onPress={() => changeApp(APP_LIBRARY, libraryHidden ? 'show' : 'hide', T.apps.library)} />,
welcome && (welcome.shown || welcome.tab !== 'none') ? (
<SheetItem key="welcome" label={T.apps.welcome}
detail={welcome.shown ? T.apps.welcomeShown : T.apps.welcomeHidden}
Expand Down Expand Up @@ -774,9 +778,15 @@ function App() {
] : s.layer === 'star' ? [
// The repo's address as a code the phone's camera reads: the audience is at the TV, with no mouse.
<View key="code" className="mx-5 mb-2 p-8 self-start rounded-[10px] bg-fg">
<Svg width={px(220)} height={px(220)} viewBox={`0 0 ${qr.SIZE} ${qr.SIZE}`}>
<Path d={qr.PATH} fill={C.canvas} />
</Svg>
<View style={{width: px(220), height: px(220)}}>
{qr.PATHS.map(d => (
<View key={d} className="absolute left-0 top-0">
<Svg width={px(220)} height={px(220)} viewBox={`0 0 ${qr.SIZE} ${qr.SIZE}`}>
<Path d={d} fill={C.canvas} />
</Svg>
</View>
))}
</View>
</View>,
<SheetNote key="body">{T.star.body}</SheetNote>,
<SheetItem key="done" label={T.star.done} onPress={() => patch({layer: 'more'})} />,
Expand Down
Loading
Loading