Compare commits

..

6 Commits

Author SHA1 Message Date
680669df00 new readme
Some checks failed
Main Deploy Workflow / deploy_web (push) Has been cancelled
Main Deploy Workflow / deploy_playstore_internal (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
2026-09-27 17:48:57 -03:00
d13f35e045 fix linux: desabilita impeller para evitar tela preta em gpu hibrida intel/nvidia 2026-09-27 17:30:42 -03:00
9fce19dab2 feat: versao do app e aviso de alpha na pagina sobre
Mostra a versao (via getVersion/package_info_plus) ao lado de um badge
alpha e um aviso de que o app ainda esta em desenvolvimento.
2026-09-27 17:30:42 -03:00
c0a4c71766 feat: long press no grid do mural abre o viewer de imagem em tela cheia
Toque simples segue abrindo o showModalBottomSheet com o post; long press
abre o mesmo ImageViewer das imagens de sala, com pin-to-zoom e deslize
entre as midias do mural.
2026-09-27 17:30:37 -03:00
d65be8a1ef fix: back do android saia da sala em vez de fechar o viewer de imagem
O PopScope sozinho nao resolvia: GoRouterDelegate.popRoute() itera
_findCurrentNavigators(), que devolve [root, shell] e retorna
states.reversed, ou seja, o shell navigator e consultado primeiro. O
viewer e um showDialog no root navigator, entao o shell nunca ve o
PopEntry dele, popa a sala e retorna true antes de chegar no dialog.

BackButtonListener registra no BackButtonDispatcher raiz, que e
consultado antes do RouterDelegate.popRoute(), entao o viewer fecha
primeiro. Aplicado tambem no MxcImageViewer (avatar/sticker), que tinha
o mesmo defeito.
2026-09-27 17:30:37 -03:00
9f8877a741 fix: icones de notificacao e splash ainda usavam o artwork do FluffyChat
- notifications_icon.png: gera a silhueta branca a partir do logo atual
  nas 5 densidades (era o drawable do Initial commit, nunca trocado)
- splash.png: idem, mantendo os canvas originais e o logo a 64% da largura
- apaga ic_launcher_foreground.xml e ic_launcher_monochrome.xml, vectors
  antigos com o roxo do FluffyChat que competiam com os PNGs por densidade
- foreground service passa a usar notifications_icon em vez do inexistente
  ic_launcher (caia no icone do launcher)
- bumpara o id do canal para tatuchat_push_v2, senao o Android mantem o
  icone antigo congelado no canal ja criado
- titulo do foreground service: FluffyChat -> TatuChat
2026-09-27 17:30:32 -03:00
20 changed files with 357 additions and 172 deletions

295
README.md
View File

@@ -5,133 +5,280 @@ SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat
SPDX-License-Identifier: AGPL-3.0-or-later SPDX-License-Identifier: AGPL-3.0-or-later
--> -->
[TatuChat](https://tatuchat.weltonmoura.com.br/home/) is an open source, nonprofit and cute [[matrix](https://matrix.org)] client written in [Flutter](https://flutter.dev). The goal of the app is to create an easy to use instant messenger which is open source and accessible for everyone. # TatuChat
### Links: <p align="center">
<img src="https://tatuchat.weltonmoura.com.br/home/assets/logo.png" height="180" alt="TatuChat">
</p>
- 🌐 [[Weblate] Translate TatuChat into your language](https://hosted.weblate.org/projects/fluffychat/) <p align="center">
- 🌍 [[m] Join the community](https://matrix.to/#/#fluffy-space:matrix.org) Cliente de mensagens instantâneas <strong>open source</strong>, <strong>sem fins lucrativos</strong> e
- 📰 [[Mastodon] Get updates on social media](https://troet.cafe/@krille) multiplataforma, construído sobre o protocolo
- 💝 [[Liberapay] Support TatuChat development](https://de.liberapay.com/KrilleChritzelius) <a href="https://matrix.org">Matrix</a> com <a href="https://flutter.dev">Flutter</a>.
TatuChat é um <em>fork</em> do acclaimed
<a href="https://fluffy.chat">FluffyChat</a>, com foco em privacidade, simplicidade e
código aberto.
</p>
<a href='https://ko-fi.com/krille' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi5.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a> > 🌐 **Site, downloads e novidades:** <https://tatuchat.weltonmoura.com.br/home/>
### Screenshots: > ⚠️ **Status do projeto: alfa.** O aplicativo está em desenvolvimento ativo e ainda pode
> apresentar erros, perder dados ou mudar de comportamento entre versões. Use com cuidado e
> reporte problemas para ajudar a tornar o TatuChat mais estável.
<img src="https://tatuchat.weltonmoura.com.br/home/assets/logo.png" height="300"> ## O que é o TatuChat
# Features Um mensageiro federado e descentralizado: nenhuma empresa no meio, nenhum servidor único. Você
escolhe o *homeserver* que quiser (ou usa um dos recomendados), conversa com quem está em outros
servidores e continua sendo dono dos seus dados. Tudo com criptografia ponta a ponta
(Olm/Megolm via [Vodozemac](https://github.com/matrix-org/vodozemac) em WebAssembly).
- 📩 Send all kinds of messages, images and files - 🔐 Criptografia ponta a ponta, com verificação por emoji e *cross-signing*
- 🤙 Video calls with Matrix RTC - 💾 Backup de conversas criptografado
- 🎙️ Voice messages - 📩 Texto, imagens, áudio, vídeo, arquivos, stickers, emotes e localização
- 📍 Location sharing - 🤙 Videochamadas pelo Matrix RTC (LiveKit)
- 🔔 Push notifications - 🎙️ Mensagens de voz
- 💬 Unlimited private and public group chats - 🔔 Notificações push, com ou sem Google (UnifiedPush / ntfy)
- 📣 Public channels with thousands of participants - 🌌 Espaços para navegar hierarquias inteiras de salas
- 🛠️ Feature rich group moderation including all matrix features - 🎨 Design Material You, com cores dinâmicas do Android
- 🔍 Discover and join public groups
- 🎨 Material You design
- 😄 Custom emotes and stickers
- 🌌 Spaces
- 🔐 End to end encryption
- 🔒 Encrypted chat backup
- 😀 Emoji verification & cross signing
... and much more.
## 🚀 Inovações do TatuChat
# Installation O TatuChat reescreveu a forma de navegar e de consumir conteúdo dentro do Matrix. As seções
abaixo descrevem os módulos desenvolvidos sobre o FluffyChat.
Please visit the website for installation instructions: ### Abas e barra lateral
- https://tatuchat.weltonmoura.com.br/home/ O app não usa mais a *navigation rail* do FluffyChat. A navegação principal virou uma
**barra lateral** (desktop e tablet) com as entradas *Criar grupo*, *Definir status*,
*Convidar contato*, *Arquivo*, **Apps** e *Configurações*, além do seletor de contas.
# Configuration and Mobile Device Management (MDM) A tela inicial foi dividida em **quatro abas**, com barra superior em telas largas e barra
inferior em telas compactas:
TatuChat supports configuration via MDM on Android&iOS (since v2.10.0) and via a config.json file on web. You can see the populated configuration for MDM on Android in this file under `/android/app/src/main/res/xml/app_restrictions.xml`. | Aba | O que mostra |
An example configuration can be found in the `config.sample.json` file. | --- | --- |
| **Conversas** | Apenas conversas normais |
| **Canais** | Apenas canais |
| **Espaços** | Os espaços em que você entrou, como tela inicial |
| **Murais** | Apenas murais |
# How to build Canais e murais **não aparecem** na lista de conversas, e vice-versa: cada tipo tem sua própria
aba, com busca própria — e os murais mostram ainda o número de inscritos de cada um.
1. To build TatuChat you need [Flutter](https://flutter.dev) and [Rust](https://www.rust-lang.org/tools/install) ### 📣 Canais
2. Clone the repo: Um canal é uma sala pública de distribuição, no estilo de um feed, não de um chat. O tipo é
``` gravado no estado compartilhado da sala (`tatuchat.room_type = "canal"`), com uma tag pessoal
git clone https://gitea.weltonmoura.com.br/welton/tatuchat.git (`tatuchat_canal`) para identificação local.
cd tatuchat
```
3. Choose your target platform below and enable support for it.
3.1 If you want, enable Googles Firebase Cloud Messaging:
`./scripts/add-firebase-messaging.sh` A sala usa a página de chat normal, mas em **modo feed**:
4. Debug with: `flutter run` - 🗞️ Publicações viram **cartões de largura total**, em vez de balões
- ✍️ O nome do autor aparece **sempre**, inclusive nas suas próprias publicações, para que o
feed nunca pareça anônimo
- ⬅️ Tudo alinhado à esquerda, com balões totalmente arredondados
- 🖼️ Imagens ocupam a largura total, mantendo a proporção original
- 🔐 **Somente administradores publicam no nível principal**; dentro das threads, qualquer
participante comenta
- 💬 Cada publicação abre uma **thread de comentários**, com reações, respostas, edição,
exclusão e denúncia
### 🖼️ Murais
Um mural é uma página dedicada — não uma variante da timeline. O tipo é gravado em
`tatuchat.room_type = "mural"` (tag pessoal `tatuchat_mural`), e abrir o mural **nunca** mostra
a lista de mensagens: mostra um **grid visual**.
- 🟦 Grade responsiva de 3 colunas alimentada apenas por **imagens, vídeos e stickers**
- 🎨 Cabeçalho com capa, avatar, nome, descrição, autor e número de inscritos
- 👆 **Toque simples** abre o visualizador do post, com painel de comentários e campo de
resposta
- 🤏 **Toque longo** abre a imagem em tela cheia, com *pinch-to-zoom* e deslize entre as mídias
do mural
- 💬 Comentários são **threads Matrix reais**, com árvore aninhada, reações, resposta,
exclusão do próprio comentário e denúncia
- 🧹 A timeline carrega em blocos conforme você desce, para não travar em murais grandes
### 🌌 Espaços
Matrix costuma tratar espaços como um recurso de segunda classe. No TatuChat eles viraram uma
**tela inicial** completa:
- 🧭 A aba **Espaços** é a porta de entrada: explorer a hierarquia inteira de uma conta sem sair
do app
- 🔎 Busca e filtro dentro do espaço, com capa do espaço e destaque da sala ativa
- ➕ Ingresso rápido em salas filhas públicas, direto da hierarquia
- 🛠️ Edição das salas filhas a partir do próprio explorador
- ↔️ A sala ativa é carregada na coluna lateral e preservada entre navegações
### 🧩 Mini Apps
O TatuChat é **extensível pela comunidade**: qualquer pessoa pode escrever um mini app e
instalá-lo no próprio app, sem loja, sem build e sem publishing.
Um mini app é **um único arquivo `.html`**, executado em um *web view* isolado:
- 📥 Instalação **por arquivo** ou **por URL**
- 🔐 Cada app tem seu próprio armazenamento chave-valor, persistido no *account data* do Matrix
(ou seja, o app sincroniza entre os seus dispositivos)
- 🎨 A ponte expõe a paleta e o brilho do tema atual, para o app acompanhar o Material You
- 👤 A ponte expõe o perfil do usuário (id, nome, avatar e homeserver)
- 📦 Dois mini apps já vêm embutidos: **`tasks`** (lista de tarefas) e **`accountdata`**
(editor de *account data*)
As ações disponíveis na ponte são:
| Ação | O que faz |
| --- | --- |
| `io.tatuchat.storage.load` | Lê o armazenamento do app |
| `io.tatuchat.storage.save` | Sobrescreve o armazenamento do app |
| `io.tatuchat.identity.get` | Perfil do usuário + avatar como data URI |
| `io.tatuchat.theme.get` | Paleta e brilho do tema em hex CSS |
| `io.tatuchat.accountdata.list` | Lista todas as chaves de *account data* |
| `io.tatuchat.accountdata.get` | Lê uma chave de *account data* |
| `io.tatuchat.accountdata.set` | Escreve uma chave de *account data* |
**📖 Documentação completa:** [`docs/miniapps.md`](docs/miniapps.md) — manifesto em `<meta>`,
protocolo da ponte, exemplo de HTML e limitações.
> 🔐 **Segurança — leia antes de instalar apps de terceiros.** As ações de `accountdata.*` não
> são restritas por app: um mini app pode ler e escrever qualquer chave de *account data* da
> conta, incluindo as chaves de outros mini apps. Não existe modelo de permissões nem pedido de
> consentimento. Instale apenas apps cujo código você auditou. O app **não** expõe salas,
> mensagens, arquivos nem envio de mensagens pela ponte.
### Outros detalhes
- 🎨 **Identidade visual própria:** cor primária esmeralda `#00D084`, paleta de sementes
customizada, ícone, splash e ícone de notificação próprios
- ⏱️ **Status personalizado aparece na hora:** a atualização de status é aplicada no cache local
imediatamente, sem esperar o *sync* do homeserver
- 🖼️ **Papel de parede e tema sincronizados:** via *account data* da conta Matrix, o tema segue
entre dispositivos; há também a opção de suavizar a cor primária
- 🔗 Deep links próprios (`im.tatuchat://chat/`) e convites com `client=im.tatuchat`
## 📲 Instalação
Downloads para Android, iOS, Linux, Windows, macOS e Web (PWA), além de notas de versão:
- <https://tatuchat.weltonmoura.com.br/home/>
## ⚙️ Configuração e MDM
O TatuChat aceita configuração gerenciada em Android e iOS (via MDM) e por um arquivo
`config.json` na versão web.
- **Android:** as restrições declaradas ficam em
`android/app/src/main/res/xml/app_restrictions.xml`, com as chaves `defaultHomeserver`,
`presetHomeserver` e `welcomeText`. O restante das chaves é lido de `ManagedConfigurations`.
- **Web:** sirva um `config.json` no mesmo caminho do app. O exemplo completo está em
[`config.sample.json`](config.sample.json).
⚠️ Passe **somente os valores que você realmente precisa alterar**. Todas as chaves são
opcionais; por exemplo, para trocar o homeserver padrão, altere apenas `defaultHomeserver`.
## 🛠️ Como compilar
1. Para compilar o TatuChat você precisa do [Flutter](https://flutter.dev) (3.47.3, Dart
`>=3.11.1 <4.0.0`); para a versão web, também do [Rust](https://www.rust-lang.org/tools/install),
usado para compilar o Vodozemac.
2. Clone o repositório:
```bash
git clone https://gitea.weltonmoura.com.br/welton/tatuchat.git
cd tatuchat
```
3. Escolha a plataforma alvo abaixo e habilite o suporte a ela.
4. Opcionalmente, habilite o Google Firebase Cloud Messaging:
```bash
./scripts/add-firebase-messaging.sh
```
5. Debug com `flutter run`.
### Android ### Android
* Build with: `flutter build apk` ```bash
flutter build apk --release
# ou, por arquitetura (APKs bem menores):
flutter build apk --release --split-per-abi
```
Para assinar, preencha `android/key.properties` (já ignorado pelo Git) com `storeFile`,
`keyAlias`, `storePassword` e `keyPassword`; o script `prepare-android-release.sh` faz isso a
partir do segredo `$FDROID_KEY` em CI.
### iOS / iPadOS ### iOS / iPadOS
* Have a Mac with Xcode installed, and set up for Xcode-managed app signing * Um Mac com Xcode instalado, configurado para o *app signing* gerenciado pelo Xcode
* If you want automatic app installation to connected devices, make sure you have Apple Configurator installed, with the Automation Tools (`cfgutil`) enabled * Se quiser instalar automaticamente nos dispositivos conectados, tenha o Apple Configurator com
* Set a few environment variables as ferramentas de automação (`cfgutil`) habilitadas
* FLUFFYCHAT_NEW_TEAM: the Apple Developer team that your certificates should live under * Defina algumas variáveis de ambiente:
* FLUFFYCHAT_NEW_GROUP: the group you want App IDs and such to live under (ie: com.example.fluffychat) * `FLUFFYCHAT_NEW_TEAM`: o time de Apple Developer que deve emitir os certificados
* FLUFFYCHAT_INSTALL_IPA: set to `1` if you want the IPA to be deployed to connected devices after building, otherwise unset * `FLUFFYCHAT_NEW_GROUP`: o grupo onde os App IDs vivem (ex.: `com.example.tatuchat`)
* Run `./scripts/build-ios.sh` * `FLUFFYCHAT_INSTALL_IPA`: defina como `1` para fazer o *deploy* do IPA nos dispositivos
conectados após o build
* Execute `./scripts/build-ios.sh`
### Web ### Web
* Build with:
```bash ```bash
./scripts/prepare-web.sh # To install Vodozemac ./scripts/prepare-web.sh # compila o Vodozemac para WebAssembly
flutter build web --release flutter build web --release
``` ```
* Optionally configure by serving a `config.json` at the same path as fluffychat.
An example can be found at `config.sample.json`. All values there are optional.
**Please only the values, you really need**. If you e.g. only want
to change the default homeserver, then only modify the `defaultHomeserver` key.
### Desktop (Linux, Windows, macOS) ### Desktop (Linux, Windows, macOS)
* Enable Desktop support in Flutter: https://flutter.dev/desktop Habilite o suporte a desktop do Flutter: <https://flutter.dev/desktop>
#### Install custom dependencies (Linux) Dependências no Linux:
```bash ```bash
sudo apt install libjsoncpp1 libsecret-1-dev libsecret-1-0 librhash0 libwebkit2gtk-4.0-dev lld sudo apt install libjsoncpp1 libsecret-1-dev libsecret-1-0 librhash0 libwebkit2gtk-4.0-dev lld
``` ```
* Build with one of these:
```bash ```bash
flutter build linux --release flutter build linux --release
flutter build windows --release flutter build windows --release
flutter build macos --release flutter build macos --release
``` ```
## How to run integration tests ## 🧪 Testes de integração
You need to have docker installed locally! Run the preparation script before every test run: Os testes completos precisam de Docker (Synapse) rodando localmente. Antes de cada execução:
```sh ```bash
./scripts/prepare_integration_test.sh ./scripts/prepare_integration_test.sh
```
Then run all tests with:
```sh
flutter test integration_test/mobile_test.dart flutter test integration_test/mobile_test.dart
``` ```
## 📜 Licença
# Special thanks TatuChat é software livre, licença **AGPL-3.0-or-later**. Ele é derivado do FluffyChat, também
AGPL-3.0-or-later, Copyright de Christian Kußowski e colaboradores. O arquivo `LICENSES/` traz as
licenças de todas as dependências (REUSE).
* <a href="https://github.com/fabiyamada">Fabiyamada</a> is a graphics designer and has made the tatuchat logo and the banner. Big thanks for her great designs. ## 🔗 Links
* Also thanks to all translators and testers! With your help, tatuchat is now available in more than 12 languages. - 🌐 [Site do TatuChat](https://tatuchat.weltonmoura.com.br/home/)
- 💻 [Código-fonte](https://gitea.weltonmoura.com.br/welton/tatuchat)
- 🐞 [Issues e bugs](https://gitea.weltonmoura.com.br/welton/tatuchat/issues)
- 🧩 [Documentação dos Mini Apps](docs/miniapps.md)
- 🌍 [[Weblate] Traduza o TatuChat](https://hosted.weblate.org/projects/fluffychat/)
- 🦊 [Código do FluffyChat, o projeto original](https://github.com/krille-chan/fluffy-chat)
- 📄 [Política de privacidade](PRIVACY.md) · [Segurança](SECURITY.md) · [Contribuindo](CONTRIBUTING.md) · [Changelog](CHANGELOG.md)
* The Matrix Foundation for making and maintaining the [emoji translations](https://github.com/matrix-org/matrix-spec/blob/main/data-definitions/sas-emoji.json) used for emoji verification, licensed Apache 2.0 ## 🙏 Agradecimentos
* Special thanks to MTRNord, Sorunome and Advocatux. * **[Fabiyamada](https://github.com/fabiyamada)** é designer gráfico e criou o logo e o banner do
TatuChat. Muito obrigado pelos belos designs.
* A todos os tradutores e testadores! Com a ajuda de vocês o TatuChat já está disponível em mais
de 12 idiomas.
* À **Matrix Foundation**, por manter e licenciar (Apache 2.0) as
[traduções de emoji](https://github.com/matrix-org/matrix-spec/blob/main/data-definitions/sas-emoji.json)
usadas na verificação por emoji.
* A **MTRNord**, **Sorunome** e **Advocatux**.
* Ao **FluffyChat** e a toda a comunidade Matrix — o TatuChat existe graças a esse trabalho.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 882 B

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 B

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -1,29 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.33822063"
android:scaleY="0.33822063"
android:translateX="30.24"
android:translateY="31.24">
<path
android:pathData="M130.9,74.3c1.5,-0.3 2.8,-1 3.8,-2 4,-5.3 0.8,-11.8 -4.5,-12.6 -0.8,0 -1.5,-0.8 -1.5,-1.5v-0.5c0.8,-0.8 1.5,-1.8 2.5,-3.3 8.1,-10.8 11.8,-50.6 3.8,-53.7 -9.8,-3.3 -29.7,6.3 -38.3,17.4 -0.5,-0.3 -1,-1 -1,-1.8a7,7 0,0 0,-3.5 -6.8,7.8 7.8,0 0,0 -10.6,3.3c-0.5,0.8 -1.3,1.3 -2,1 -0.8,0 -1.5,-0.8 -1.5,-1.5 -0.5,-2.5 -2,-4.5 -4.3,-5.5 -4.8,-2 -9.8,0.8 -10.6,5.3 -0.3,0.8 -0.8,1.5 -1.5,1.5 -0.8,0.3 -1.5,-0.3 -2,-1a8,8 0,0 0,-6.5 -3.8,8 8,0 0,0 -7.8,7.3v0.6c0,0.8 -0.5,1.5 -1,1.8h-0.3c-8.3,-10.8 -28.5,-20.7 -38.5,-17.4 -8.1,2.8 -4.3,42.6 4,53.4 1.5,2 2.8,3.5 3.8,4.5 -0.3,0.8 -1,1.5 -1.8,1.5 -1.3,0 -2.5,0.5 -3.5,1.3a7.7,7.7 0,0 0,3 13.4c0.8,0.3 1.5,1 1.5,1.8s-0.5,1.8 -1.3,2c-1,0.5 -2,1 -2.8,2 -4,5.8 0,12.3 5.5,12.3a2,2 0,0 1,1.8 1.3c0.3,0.8 0.3,1.5 -0.5,2a6.5,6.5 0,0 0,-2 5.5c0.3,2.8 2,5.3 4.8,6.5 1.5,0.8 3,0.8 4.5,0.5 0.8,-0.3 1.5,0 2,0.8 0.5,0.5 0.5,1.5 0.3,2 -0.8,1.5 -1,3.3 -0.5,5a7.7,7.7 0,0 0,5.5 5.5c2.5,0.5 4.3,-0.3 5.5,-0.8 0.5,-0.3 -3.3,9.1 -6,15.4 -0.8,2 1.3,4.3 3.5,3.3 8.3,-3.8 22.2,-10.3 22.2,-9.8 0.5,5.3 6.5,9.1 12.3,5.3 1.3,-0.8 2,-2.3 2.3,-3.5 0.3,-0.8 1,-1.5 2,-1.5s1.8,0.5 2,1.5c0.3,1.3 0.8,2.3 1.8,3 5.8,4.5 12.3,0.8 12.8,-4.8a2,2 0,0 1,1.3 -1.8c0.8,-0.3 1.5,0 2,0.5a7.6,7.6 0,0 0,5.3 2.5c2.5,0 5,-1.3 6.5,-3.8 1,-1.5 1.3,-3 1,-5 0,-0.8 0.3,-1.5 0.8,-2s1.5,-0.5 2,0c1.5,0.8 3.3,1.3 5,0.8 2.8,-0.5 5,-2.8 5.8,-5.3 0.5,-1.8 0.3,-3.5 -0.5,-5.3 -0.3,-0.8 -0.3,-1.5 0.3,-2s1.3,-0.8 2,-0.8c1.8,0.3 3.3,0.3 4.8,-0.5 2.3,-1 3.8,-3 4.3,-5.5a7,7 0,0 0,-2 -6.5c-0.5,-0.5 -0.8,-1.3 -0.5,-2 0.3,-0.7 1,-1.3 1.8,-1.3 1.8,0 3.8,-0.5 5,-2a7.6,7.6 0,0 0,-2.5 -12.6c-0.8,-0.3 -1.3,-1 -1.3,-2 0,-0.9 0.7,-1.6 1.5,-1.6z"
android:fillColor="#4d3f92"/>
<path
android:pathData="M110.7,21.4a7,7 0,0 1,0 4.5c-0.3,0.8 0,1.5 0.5,2s1.3,0.8 2,0.5c1,-0.5 2,-0.5 3,-0.5a7,7 0,0 1,5.8 3c1,1.3 1.8,3 1.5,4.8a7,7 0,0 1,-1.3 4c-0.5,0.5 -0.5,1.5 0,2 0.3,0.3 0.5,0.8 1,0.8 1,-0.3 2,-1 2.8,-2 4.5,-6.3 5.3,-26.2 0.8,-27.7s-12.3,1.5 -17.9,6c1.1,0.5 1.6,1.3 1.8,2.6z"
android:fillColor="#fff"/>
<path
android:pathData="M18.3,42.8l0.8,-0.8c0.5,-0.8 0.3,-1.5 0,-2a7.6,7.6 0,0 1,-1.3 -5.8c0.5,-2.8 2.8,-5 5.5,-5.8 1.5,-0.5 3,-0.3 4.5,0.3 0.8,0.3 1.5,0 2,-0.5s0.8,-1.3 0.5,-2a7,7 0,0 1,0 -4.5c0.3,-1 0.8,-2 1.5,-2.8 -5.5,-4.5 -13.9,-7.8 -18.4,-6.3 -4.5,1.5 -3.7,21.4 0.9,27.7 1,1.5 2.2,2.2 4,2.5z"
android:fillColor="#fff"/>
<path
android:pathData="M40.2,73.8m-9.3,0a9.3,9.3 0,1 1,18.6 0a9.3,9.3 0,1 1,-18.6 0"
android:fillColor="#fff"/>
<path
android:pathData="M80,73.8a9.3,9.3 0,1 1,-18.6 0c0,-5 18.6,-5.3 18.6,0z"
android:fillColor="#fff"/>
<path
android:pathData="M100.9,73.8m-9.3,0a9.3,9.3 0,1 1,18.6 0a9.3,9.3 0,1 1,-18.6 0"
android:fillColor="#fff"/>
</group>
</vector>

File diff suppressed because one or more lines are too long

View File

@@ -17,7 +17,7 @@ abstract class AppConfig {
static const String inviteLinkPrefix = 'https://matrix.to/#/'; static const String inviteLinkPrefix = 'https://matrix.to/#/';
static const String deepLinkPrefix = 'im.tatuchat://chat/'; static const String deepLinkPrefix = 'im.tatuchat://chat/';
static const String schemePrefix = 'matrix:'; static const String schemePrefix = 'matrix:';
static const String pushNotificationsChannelId = 'tatuchat_push'; static const String pushNotificationsChannelId = 'tatuchat_push_v2';
static const String pushNotificationsAppId = 'chat.fluffy.tatuchat'; static const String pushNotificationsAppId = 'chat.fluffy.tatuchat';
static const double borderRadius = 18.0; static const double borderRadius = 18.0;
static const double spaceBorderRadius = 11.0; static const double spaceBorderRadius = 11.0;

View File

@@ -199,15 +199,25 @@ class ImageViewerController extends State<ImageViewer> {
} }
@override @override
Widget build(BuildContext context) => PopScope<Object?>( Widget build(BuildContext context) => BackButtonListener(
// Claim the system back button (and predictive back gesture) for the // A [PopScope] alone is not enough here: go_router's `popRoute()` asks
// viewer. Without this, the request can reach go_router instead, which // `GoRouterDelegate._findCurrentNavigators()`, which returns the shell
// pops the chat room out of the shell and leaves the image on screen. // navigator BEFORE the root one. The viewer is a dialog on the root
canPop: false, // navigator, so the shell navigator never sees its [PopEntry], pops the
onPopInvokedWithResult: (didPop, _) { // chat room instead and returns `true`, leaving the image on screen.
if (didPop) return; // A [BackButtonListener] is asked before the router gets the request at
// all, so the viewer gets to close itself first.
onBackButtonPressed: () async {
closeAction(); closeAction();
return true;
}, },
child: ImageViewerView(this), child: PopScope<Object?>(
canPop: false,
onPopInvokedWithResult: (didPop, _) {
if (didPop) return;
closeAction();
},
child: ImageViewerView(this),
),
); );
} }

View File

@@ -14,6 +14,7 @@ import 'package:tatchat/config/themes.dart';
import 'package:tatchat/l10n/l10n.dart'; import 'package:tatchat/l10n/l10n.dart';
import 'package:tatchat/pages/chat/send_file_dialog.dart'; import 'package:tatchat/pages/chat/send_file_dialog.dart';
import 'package:tatchat/pages/chat/sticker_picker_dialog.dart'; import 'package:tatchat/pages/chat/sticker_picker_dialog.dart';
import 'package:tatchat/pages/image_viewer/image_viewer.dart';
import 'package:tatchat/pages/image_viewer/video_player.dart'; import 'package:tatchat/pages/image_viewer/video_player.dart';
import 'package:tatchat/utils/adaptive_bottom_sheet.dart'; import 'package:tatchat/utils/adaptive_bottom_sheet.dart';
import 'package:tatchat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:tatchat/utils/matrix_sdk_extensions/matrix_locals.dart';
@@ -306,6 +307,19 @@ class _MuralChatViewState extends State<MuralChatView> {
); );
} }
/// Long press on a grid cell: open the same fullscreen viewer a room image
/// uses, instead of the bottom sheet, so pinch-to-zoom and swiping between
/// the media of the mural are available there too.
void _openMediaFullscreen(Event event) {
final timeline = this.timeline;
if (timeline == null) return;
showDialog<void>(
context: context,
builder: (_) =>
ImageViewer(event, timeline: timeline, outerContext: context),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
@@ -395,6 +409,7 @@ class _MuralChatViewState extends State<MuralChatView> {
client: room.client, client: room.client,
cellSize: gridCellSize, cellSize: gridCellSize,
onTap: () => _openMedia(event), onTap: () => _openMedia(event),
onLongPress: () => _openMediaFullscreen(event),
); );
}, },
), ),
@@ -656,12 +671,14 @@ class _MediaCell extends StatelessWidget {
final Client client; final Client client;
final double cellSize; final double cellSize;
final VoidCallback onTap; final VoidCallback onTap;
final VoidCallback onLongPress;
const _MediaCell({ const _MediaCell({
required this.event, required this.event,
required this.client, required this.client,
required this.cellSize, required this.cellSize,
required this.onTap, required this.onTap,
required this.onLongPress,
}); });
@override @override
@@ -678,6 +695,7 @@ class _MediaCell extends StatelessWidget {
: null; : null;
return GestureDetector( return GestureDetector(
onTap: onTap, onTap: onTap,
onLongPress: onLongPress,
child: ColoredBox( child: ColoredBox(
color: theme.colorScheme.surfaceContainerHighest, color: theme.colorScheme.surfaceContainerHighest,
child: Stack( child: Stack(

View File

@@ -72,13 +72,12 @@ abstract class PlatformInfos {
final l10n = L10n.of(context); final l10n = L10n.of(context);
final theme = Theme.of(context); final theme = Theme.of(context);
final size = MediaQuery.sizeOf(context); final size = MediaQuery.sizeOf(context);
final version = await getVersion();
if (!context.mounted) return;
await m.showDialog<void>( await m.showDialog<void>(
context: context, context: context,
builder: (context) => m.Dialog( builder: (context) => m.Dialog(
insetPadding: const EdgeInsets.symmetric( insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 40),
horizontal: 24,
vertical: 40,
),
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: 720, maxWidth: 720,
@@ -110,13 +109,52 @@ abstract class PlatformInfos {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
l10n.versionWithNumber(version),
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: theme.colorScheme.tertiaryContainer,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'alpha',
style: theme.textTheme.labelMedium?.copyWith(
color: theme.colorScheme.onTertiaryContainer,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 8),
Text(
'Esta versão ainda está em fase alpha: o '
'aplicativo está em desenvolvimento e ainda pode '
'apresentar erros.',
textAlign: TextAlign.center,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 24), const SizedBox(height: 24),
Linkify( Linkify(
text: aboutDescription, text: aboutDescription,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(color: theme.colorScheme.onSurface),
color: theme.colorScheme.onSurface,
),
options: const LinkifyOptions(humanize: false), options: const LinkifyOptions(humanize: false),
linkStyle: TextStyle( linkStyle: TextStyle(
color: theme.colorScheme.primary, color: theme.colorScheme.primary,

View File

@@ -68,9 +68,11 @@ abstract class ForegroundServices {
} }
final result = await FlutterForegroundTask.startService( final result = await FlutterForegroundTask.startService(
serviceTypes: [ForegroundServiceTypes.shortService], serviceTypes: [ForegroundServiceTypes.shortService],
notificationTitle: 'FluffyChat', notificationTitle: 'TatuChat',
notificationText: l10n.loadingMessages, notificationText: l10n.loadingMessages,
notificationIcon: NotificationIcon(metaDataName: 'ic_launcher'), notificationIcon: NotificationIcon(
metaDataName: 'notifications_icon',
),
); );
final started = result is ServiceRequestSuccess; final started = result is ServiceRequestSuccess;
Logs().d('[PushHelper] Foreground service start: $started ($result)'); Logs().d('[PushHelper] Foreground service start: $started ($result)');

View File

@@ -24,49 +24,57 @@ class MxcImageViewer extends StatelessWidget {
backgroundColor: Colors.black.withAlpha(200), backgroundColor: Colors.black.withAlpha(200),
foregroundColor: Colors.white, foregroundColor: Colors.white,
); );
return PopScope<Object?>( return BackButtonListener(
// Claim the system back button for the viewer so that it is dismissed // Same reason as the room image viewer: this dialog lives on the root
// instead of letting the router navigate away underneath it. // navigator, but go_router asks the shell navigator first, so only a
canPop: false, // [BackButtonListener] (which runs before the router) can keep the back
onPopInvokedWithResult: (didPop, _) { // button from popping the page underneath.
if (didPop) return; onBackButtonPressed: () async {
_close(context); _close(context);
return true;
}, },
child: GestureDetector( child: PopScope<Object?>(
onTap: () => _close(context), canPop: false,
child: Scaffold( onPopInvokedWithResult: (didPop, _) {
backgroundColor: Colors.black.withAlpha(128), if (didPop) return;
extendBodyBehindAppBar: true, _close(context);
appBar: AppBar( },
elevation: 0, child: GestureDetector(
leading: IconButton( onTap: () => _close(context),
style: iconButtonStyle, child: Scaffold(
icon: const Icon(Icons.close), backgroundColor: Colors.black.withAlpha(128),
onPressed: () => _close(context), extendBodyBehindAppBar: true,
color: Colors.white, appBar: AppBar(
tooltip: L10n.of(context).close, elevation: 0,
leading: IconButton(
style: iconButtonStyle,
icon: const Icon(Icons.close),
onPressed: () => _close(context),
color: Colors.white,
tooltip: L10n.of(context).close,
),
backgroundColor: Colors.transparent,
), ),
backgroundColor: Colors.transparent, body: InteractiveViewer(
), minScale: 1.0,
body: InteractiveViewer( maxScale: 10.0,
minScale: 1.0, onInteractionEnd: (endDetails) {
maxScale: 10.0, if (endDetails.velocity.pixelsPerSecond.dy >
onInteractionEnd: (endDetails) { MediaQuery.sizeOf(context).height * 1.5) {
if (endDetails.velocity.pixelsPerSecond.dy > _close(context);
MediaQuery.sizeOf(context).height * 1.5) { }
_close(context); },
} child: Center(
}, child: GestureDetector(
child: Center( // Ignore taps to not go back here:
child: GestureDetector( onTap: () {},
// Ignore taps to not go back here: child: MxcImage(
onTap: () {}, key: ValueKey(mxContent.toString()),
child: MxcImage( uri: mxContent,
key: ValueKey(mxContent.toString()), fit: BoxFit.contain,
uri: mxContent, isThumbnail: false,
fit: BoxFit.contain, animated: true,
isThumbnail: false, ),
animated: true,
), ),
), ),
), ),

View File

@@ -64,6 +64,8 @@ static void my_application_activate(GApplication* application) {
g_autoptr(FlDartProject) project = fl_dart_project_new(); g_autoptr(FlDartProject) project = fl_dart_project_new();
fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
// Disable Impeller to avoid a black screen on hybrid Intel/NVIDIA GPUs.
fl_dart_project_set_enable_impeller(project, FALSE);
FlView* view = fl_view_new(project); FlView* view = fl_view_new(project);
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));