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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ jobs:
with:
python-version: '3.12'

- name: Tokens visuais (web e Windows) gerados de tools/tokens.json
run: python3 tools/generate-tokens.py --check

- name: Dependências do E2E (pyftpdlib, 7-Zip com RAR)
run: |
pip install pyftpdlib
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ All notable changes to Ferry are documented here. The format follows [Keep a Cha

## [Unreleased]

### Changed

- Redesigned interface (web and Windows), keeping the dark identity: the **Queue** is now grouped into *Needs you* (error, PKG uploaded, check on PS5, pending password), *Sending*, *Queued* and *Finished* (collapsed by default, with **Clear finished** in the group header). Idle cards are compact and only show numbers that mean something; the active card keeps %, MB/s and time remaining.
- Queue header with overall progress and the estimated finish time (only when every item has a known size).
- PS5 status moved from the sidebar footer to a badge at the top of every page.
- **Settings** split into sections with an index that follows scrolling (PS5, Destinations, Transfer, Passwords, Notifications, General; on Windows also Power). A single scroll with the scrollbar at the window edge; the passwords box grows with its content instead of scrolling on its own in the middle of the screen.
- Mobile: brand and tabs on one line, compact cards and a fixed **Add files** button at the bottom.
- Colors and sizes come from one file (`tools/tokens.json`) for web and Windows; CI flags drift.

### Fixed

- Web: repeated password requests for the same game (e.g. after a wrong password) stacked identical notices; the existing notice is now refreshed. Error and PKG notices get **View**, which jumps to the card.
- Web: long Windows paths were shown with the `\\?\` prefix; they are now shown without it (the saved value is unchanged).
- Web: the queue rewrote every card on each update; now only changed cards are (≈6× faster with 200 games).

## [1.6.2-beta.1] - 2026-10-07

### Added
Expand Down
15 changes: 15 additions & 0 deletions CHANGELOG.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ Todas as mudanças relevantes do Ferry ficam aqui. O formato segue o [Keep a Cha

## [Não lançado]

### Alterado

- Interface redesenhada (web e Windows), mantendo a identidade escura: a **Fila** agora é agrupada em *Precisa de você* (erro, PKG enviado, verifique no PS5, senha pendente), *Enviando*, *Na fila* e *Concluídos* (recolhido por padrão, com **Limpar concluídos** no cabeçalho do grupo). Cartões parados ficam compactos e só mostram números que dizem algo; o ativo mantém %, MB/s e tempo restante.
- Cabeçalho da Fila com o progresso total e o horário previsto de término (só quando todos os itens têm tamanho conhecido).
- Status do PS5 saiu do rodapé da barra lateral e virou um selo no topo de todas as páginas.
- **Configurações** em seções com índice que acompanha a rolagem (PS5, Destinos, Envio, Senhas, Avisos, Geral; no Windows também Energia). Uma rolagem só, com a barra na borda da janela; a caixa de senhas cresce com o conteúdo em vez de rolar sozinha no meio da tela.
- Celular: marca e abas numa linha, cartões compactos e botão **Adicionar arquivos** fixo no rodapé.
- Cores e medidas vêm de um arquivo só (`tools/tokens.json`) para web e Windows; a CI acusa divergência.

### Corrigido

- Web: pedidos repetidos de senha para o mesmo jogo (ex.: depois de uma senha incorreta) empilhavam avisos iguais; agora o aviso existente é renovado. Avisos de erro e de PKG ganham **Ver**, que leva ao cartão.
- Web: caminhos longos do Windows apareciam com o prefixo `\\?\`; agora aparecem sem ele (o valor salvo não muda).
- Web: a fila reescrevia todos os cartões a cada atualização; agora só os que mudaram (≈6× mais rápido com 200 jogos).

## [1.6.2-beta.1] - 2026-10-07

### Adicionado
Expand Down
2 changes: 1 addition & 1 deletion README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Abre `http://<ip-del-servidor>:8021`. La primera vez, la página pide crear el u
## Cómo usar

1. Ejecuta un payload de FTP en la PS5 (**ftpsrv**, puerto 2121, o el FTP de **etaHEN**, puerto 1337).
2. Abre la app → **Configurações** (Ajustes): IP de la PS5, puerto, destino (**M.2** `/mnt/ext1/homebrew` o **SSD interno** `/data/homebrew`). Todo se guarda solo. El pie de la barra lateral muestra si la PS5 está en línea.
2. Abre la app → **Configurações** (Ajustes): IP de la PS5, puerto, destino (**M.2** `/mnt/ext1/homebrew` o **SSD interno** `/data/homebrew`). Todo se guarda solo. El sello en la parte superior de cada página muestra si la PS5 está en línea.
3. En **Fila** (Cola), haz clic en el centro de la pantalla (abre el selector de archivos) o arrastra los archivos a la ventana. Selecciona **todas las partes** a la vez.
4. Listo: cuando estén todas las partes, el juego se extrae y se envía a `<destino>/<carpeta del juego>`.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Open `http://<server-ip>:8021`. On first open the page asks you to create a user
## How to use

1. Run an FTP payload on the PS5 (**ftpsrv**, port 2121, or **etaHEN**'s FTP, port 1337).
2. Open **Settings**: PS5 IP, port, destination (**M.2** `/mnt/ext1/homebrew` or **internal SSD** `/data/homebrew`). Everything saves automatically. The bottom of the sidebar shows whether the PS5 is online.
2. Open **Settings**: PS5 IP, port, destination (**M.2** `/mnt/ext1/homebrew` or **internal SSD** `/data/homebrew`). Everything saves automatically. The badge at the top of every page shows whether the PS5 is online.
3. In **Queue**, click the middle of the screen (opens the file picker) or drag the files onto the window. Select **all parts** at once.
4. Done: once every part is present, the game is extracted and sent to `<destination>/<game folder>`.

Expand Down
2 changes: 1 addition & 1 deletion README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Abra `http://<ip-do-servidor>:8021`. Na primeira abertura a página pede para cr
## Como usar

1. Rode o payload de FTP no PS5 (**ftpsrv**, porta 2121, ou o FTP do **etaHEN**, porta 1337).
2. Abra o app → **Configurações**: IP do PS5, porta, destino (**M.2** `/mnt/ext1/homebrew` ou **SSD interno** `/data/homebrew`). Tudo salva sozinho. O pé da barra lateral mostra se o PS5 está online.
2. Abra o app → **Configurações**: IP do PS5, porta, destino (**M.2** `/mnt/ext1/homebrew` ou **SSD interno** `/data/homebrew`). Tudo salva sozinho. O selo no topo de todas as páginas mostra se o PS5 está online.
3. Na **Fila**, clique no centro da tela (abre o seletor do Windows) ou arraste os arquivos para a janela. Selecione **todas as partes** de uma vez.
4. Pronto: quando todas as partes estiverem presentes, o jogo é extraído e enviado para `<destino>/<pasta do jogo>`.

Expand Down
79 changes: 68 additions & 11 deletions app/App.xaml
Original file line number Diff line number Diff line change
@@ -1,29 +1,50 @@
<Application x:Class="Ferry.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:Ferry"
ThemeMode="Dark"
StartupUri="MainWindow.xaml">
<Application.Resources>
<!-- "PlayStation noir": preto-azulado, quase monocromático, um acento (azul). Verde/vermelho/âmbar só para estado. -->
<Color x:Key="AccentColor">#6F97FF</Color>
<!-- tokens:start (gerado por tools/generate-tokens.py a partir de tools/tokens.json; não editar à mão) -->
<Color x:Key="BgColor">#0A0D12</Color>
<Color x:Key="SideColor">#0D1117</Color>
<Color x:Key="HoverColor">#0F141B</Color>
<Color x:Key="FieldColor">#121821</Color>
<Color x:Key="LineColor">#1B222D</Color>
<Color x:Key="Line2Color">#29323F</Color>
<Color x:Key="TextColor">#E9EDF3</Color>
<Color x:Key="Text2Color">#A9B3C2</Color>
<Color x:Key="MutedColor">#8490A2</Color>
<Color x:Key="IdleColor">#4A5363</Color>
<Color x:Key="AccentColor">#6F97FF</Color>
<Color x:Key="AccentTextColor">#9DB8FF</Color>
<Color x:Key="GreenColor">#4ADE80</Color>
<Color x:Key="RedColor">#FF7A7A</Color>
<Color x:Key="AmberColor">#F2B84B</Color>
<SolidColorBrush x:Key="Bg" Color="#0A0D12"/>
<SolidColorBrush x:Key="Side" Color="#0D1117"/>
<SolidColorBrush x:Key="Hover" Color="#0F141B"/>
<SolidColorBrush x:Key="Line" Color="#1B222D"/>
<SolidColorBrush x:Key="Line2" Color="#29323F"/>
<SolidColorBrush x:Key="Bg" Color="{StaticResource BgColor}"/>
<SolidColorBrush x:Key="Side" Color="{StaticResource SideColor}"/>
<SolidColorBrush x:Key="Hover" Color="{StaticResource HoverColor}"/>
<SolidColorBrush x:Key="Field" Color="{StaticResource FieldColor}"/>
<SolidColorBrush x:Key="Line" Color="{StaticResource LineColor}"/>
<SolidColorBrush x:Key="Line2" Color="{StaticResource Line2Color}"/>
<SolidColorBrush x:Key="Text" Color="{StaticResource TextColor}"/>
<SolidColorBrush x:Key="Text2" Color="{StaticResource Text2Color}"/>
<SolidColorBrush x:Key="Muted" Color="#8490A2"/>
<SolidColorBrush x:Key="Muted" Color="{StaticResource MutedColor}"/>
<SolidColorBrush x:Key="Idle" Color="{StaticResource IdleColor}"/>
<SolidColorBrush x:Key="Accent" Color="{StaticResource AccentColor}"/>
<SolidColorBrush x:Key="AccentText" Color="#9DB8FF"/>
<SolidColorBrush x:Key="AccentText" Color="{StaticResource AccentTextColor}"/>
<SolidColorBrush x:Key="Green" Color="{StaticResource GreenColor}"/>
<SolidColorBrush x:Key="Red" Color="{StaticResource RedColor}"/>
<SolidColorBrush x:Key="Amber" Color="{StaticResource AmberColor}"/>
<sys:Double x:Key="PageTitleSize">34</sys:Double>
<sys:Double x:Key="JobTitleSize">22</sys:Double>
<sys:Double x:Key="JobTitleActiveSize">26</sys:Double>
<sys:Double x:Key="StatSize">28</sys:Double>
<sys:Double x:Key="LabelSize">13</sys:Double>
<sys:Double x:Key="RadiusSize">6</sys:Double>
<!-- tokens:end -->

<!-- controles Fluent (slider, checkbox, foco da caixa de texto) no acento do Ferry, não no do Windows -->
<Color x:Key="SystemAccentColor">#6F97FF</Color>
Expand All @@ -47,6 +68,8 @@
<FontFamily x:Key="ContentControlThemeFontFamily">pack://application:,,,/Ferry;component/fonts/#Geist</FontFamily>
<FontFamily x:Key="Icons">Segoe Fluent Icons, Segoe MDL2 Assets</FontFamily>
<BooleanToVisibilityConverter x:Key="Vis"/>
<local:JobGroupConverter x:Key="JobGroup"/>
<local:GroupTitleConverter x:Key="GroupTitleText"/>

<!-- foco do teclado: contorno claro, visível no fundo escuro -->
<Style x:Key="Focus">
Expand Down Expand Up @@ -210,16 +233,50 @@
</Setter>
</Style>

<!-- título de seção das Configurações -->
<Style x:Key="GroupTitle" TargetType="TextBlock">
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontSize" Value="22"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="{StaticResource Text}"/>
<Setter Property="Margin" Value="0,0,0,12"/>
<Setter Property="Margin" Value="0,0,0,16"/>
</Style>
<!-- subgrupo dentro da seção: versalete discreto -->
<Style x:Key="SubTitle" TargetType="TextBlock">
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Typography.Capitals" Value="AllSmallCaps"/>
<Setter Property="Foreground" Value="{StaticResource Text2}"/>
<Setter Property="Margin" Value="0,0,0,10"/>
</Style>
<Style x:Key="Group" TargetType="Border">
<Setter Property="BorderBrush" Value="{StaticResource Line}"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
<Setter Property="Padding" Value="0,16"/>
<Setter Property="Padding" Value="0,28"/>
</Style>

<!-- índice das Configurações: fio à esquerda; a seção visível ganha o acento -->
<Style x:Key="SectionItem" TargetType="RadioButton">
<Setter Property="Foreground" Value="{StaticResource Muted}"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource Focus}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Border x:Name="Bd" Background="Transparent" BorderBrush="{StaticResource Line}" BorderThickness="2,0,0,0" Padding="12,8">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True"><Setter Property="Foreground" Value="{StaticResource Text2}"/></Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource Accent}"/>
<Setter Property="Foreground" Value="{StaticResource Text}"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Label" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource Muted}"/>
Expand Down
Loading
Loading