Merge pull request #3366 from pangeachat/fluffychat-merge
Fluffychat merge
5
.github/workflows/main_deploy.yaml
vendored
|
|
@ -20,9 +20,12 @@ jobs:
|
|||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
- run: flutter pub get
|
||||
- uses: moonrepo/setup-rust@v1
|
||||
- run: rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
|
||||
- name: Prepare web
|
||||
run: ./scripts/prepare-web.sh
|
||||
- run: rm ./assets/vodozemac/.gitignore
|
||||
- run: flutter pub get
|
||||
- name: Build Release Web
|
||||
run: ./scripts/build-web.sh
|
||||
|
||||
|
|
|
|||
4
.github/workflows/release.yaml
vendored
|
|
@ -48,6 +48,8 @@ jobs:
|
|||
cache: true
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install nodejs -y
|
||||
- uses: moonrepo/setup-rust@v1
|
||||
- run: rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
|
||||
- run: flutter pub get
|
||||
- name: Prepare web
|
||||
run: ./scripts/prepare-web.sh
|
||||
|
|
@ -201,4 +203,4 @@ jobs:
|
|||
aws s3 sync ./build/web s3://$WEBAPP_S3_BUCKET
|
||||
- name: AWS CloudFront Invalidation
|
||||
run: |
|
||||
aws cloudfront create-invalidation --distribution-id $CF_DISTRIBUTION_ID --paths "/*"
|
||||
aws cloudfront create-invalidation --distribution-id $CF_DISTRIBUTION_ID --paths "/*"
|
||||
|
|
|
|||
2
.github/workflows/versions.env
vendored
|
|
@ -1,2 +1,2 @@
|
|||
FLUTTER_VERSION=3.32.1
|
||||
FLUTTER_VERSION=3.32.4
|
||||
JAVA_VERSION=17
|
||||
|
|
|
|||
1546
.gitignore
vendored
21
CHANGELOG.md
|
|
@ -1,3 +1,24 @@
|
|||
## v2.0.0
|
||||
|
||||
This version migrates to Vodozemac and Matrix Dart SDK 1.0.0. This is a breaking
|
||||
change. The user should not notice the migration at all but downgrading from
|
||||
v2.0.0 to a previous version is not possible without losing the session.
|
||||
|
||||
- fix: Do not set read markers for sending events (Christian Kußowski)
|
||||
- fix: fix compile error related to MxcImage (gilice)
|
||||
- fix: Forward last version of events when forwarding (Christian Kußowski)
|
||||
- fix: Ban button displayed for already banned users (Christian Kußowski)
|
||||
- fix: Route back to room list after leaving a chat (Christian Kußowski)
|
||||
- build: Switch to matrix sdk 1.0.0 (Christian Kußowski)
|
||||
- build: Upgrade flutter to 3.32.2 (krille-chan)
|
||||
- build: Update to flutter 3.32.4 (Christian Kußowski)
|
||||
- chore: Add missing mounted check (Christian Kußowski)
|
||||
- chore: highlight select mode actions (Christian Kußowski)
|
||||
- refactor: sdk 1.0 (Christian Kußowski)
|
||||
- refactor: New message context menu (Christian Kußowski)
|
||||
- refactor: Nicer popupmenus (Christian Kußowski)
|
||||
- Translated using Weblate (Spanish) (Kimby)
|
||||
|
||||
## v1.27.0
|
||||
- feat: Add confirmation dialog before accepting invite (krille-chan)
|
||||
- feat: Add feature flag for refresh tokens (Christian Kußowski)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
FROM ghcr.io/cirruslabs/flutter as builder
|
||||
RUN sudo apt update && sudo apt install curl wget jq -y
|
||||
RUN sudo apt update && sudo apt install curl wget jq build-essential -y
|
||||
|
||||
WORKDIR /tmp
|
||||
RUN wget https://github.com/mikefarah/yq/releases/download/v4.40.5/yq_linux_amd64.tar.gz
|
||||
|
|
@ -8,6 +8,9 @@ RUN mv yq_linux_amd64 /usr/bin/yq
|
|||
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y
|
||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||
RUN rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu
|
||||
RUN ./scripts/prepare-web.sh
|
||||
COPY config.* /app/
|
||||
RUN flutter pub get
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleInstance"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
|
|
|
|||
1
assets/vodozemac/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
vodozemac_bindings_dart*
|
||||
|
|
@ -46,7 +46,12 @@
|
|||
target="_blank" class="text-xl underline hover:text-purple-800 dark:hover:text-purple-400">matrix</a>]
|
||||
</p>
|
||||
|
||||
<img src="screenshots/screenshots.png" alt="Mobile and desktop screenshots" class="max-w-xl mb-16 w-full px-8" />
|
||||
<div class="flex flex-wrap justify-center mb-16 w-full px-8 gap-4">
|
||||
<img src="screenshots/mobile.png" alt="Mobile screenshot"
|
||||
class="h-96 w-auto object-contain rounded-xl border border-gray-300 shadow-xl" />
|
||||
<img src="screenshots/desktop.png" alt="Desktop screenshot"
|
||||
class="h-96 w-auto object-contain rounded-xl border border-gray-300 shadow-xl" />
|
||||
</div>
|
||||
|
||||
<div class="max-w-lg mb-16 flex justify-center flex-wrap">
|
||||
<a href="https://apps.apple.com/app/fluffychat/id1551469600"><img src="appstore-badge.png"
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 433 KiB |
|
After Width: | Height: | Size: 385 KiB |
|
After Width: | Height: | Size: 1 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 557 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 408 KiB |
|
After Width: | Height: | Size: 288 KiB |
|
After Width: | Height: | Size: 555 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 556 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 389 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 542 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/screenshots/android_1.png
Normal file
|
After Width: | Height: | Size: 435 KiB |
BIN
docs/screenshots/android_2.png
Normal file
|
After Width: | Height: | Size: 952 KiB |
BIN
docs/screenshots/android_3.png
Normal file
|
After Width: | Height: | Size: 248 KiB |
BIN
docs/screenshots/android_4.png
Normal file
|
After Width: | Height: | Size: 426 KiB |
BIN
docs/screenshots/android_5.png
Normal file
|
After Width: | Height: | Size: 951 KiB |
BIN
docs/screenshots/android_6.png
Normal file
|
After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 117 KiB |
BIN
docs/screenshots/linux_1.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
docs/screenshots/linux_2.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
docs/screenshots/linux_3.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
docs/screenshots/linux_4.png
Normal file
|
After Width: | Height: | Size: 258 KiB |
BIN
docs/screenshots/linux_5.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 518 KiB |
|
Before Width: | Height: | Size: 370 KiB |
|
Before Width: | Height: | Size: 358 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 345 KiB |
|
|
@ -1,3 +1,8 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
|
||||
# Workaround for ruby 3.4 https://github.com/fastlane/fastlane/issues/29183
|
||||
gem "abbrev"
|
||||
gem "mutex_m"
|
||||
gem "ostruct"
|
||||
|
|
@ -3,6 +3,7 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/archive/archive.dart';
|
||||
|
|
@ -60,7 +61,9 @@ abstract class AppRoutes {
|
|||
GoRouterState state,
|
||||
) {
|
||||
// #Pangea
|
||||
// Matrix.of(context).client.isLogged() ? '/rooms' : null;
|
||||
// Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
||||
// ? '/rooms'
|
||||
// : null;
|
||||
return PAuthGaurd.loggedInRedirect(context, state);
|
||||
// Pangea#
|
||||
}
|
||||
|
|
@ -70,7 +73,9 @@ abstract class AppRoutes {
|
|||
GoRouterState state,
|
||||
) {
|
||||
// #Pangea
|
||||
// Matrix.of(context).client.isLogged() ? null : '/home';
|
||||
// Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
||||
// ? null
|
||||
// : '/home';
|
||||
return PAuthGaurd.loggedOutRedirect(context, state);
|
||||
// Pangea#
|
||||
}
|
||||
|
|
@ -81,7 +86,9 @@ abstract class AppRoutes {
|
|||
GoRoute(
|
||||
path: '/',
|
||||
redirect: (context, state) =>
|
||||
Matrix.of(context).client.isLogged() ? '/rooms' : '/home',
|
||||
Matrix.of(context).widget.clients.any((client) => client.isLogged())
|
||||
? '/rooms'
|
||||
: '/home',
|
||||
),
|
||||
GoRoute(
|
||||
path: '/home',
|
||||
|
|
@ -100,7 +107,7 @@ abstract class AppRoutes {
|
|||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const Login(),
|
||||
Login(client: state.extra as Client),
|
||||
),
|
||||
redirect: loggedInRedirect,
|
||||
),
|
||||
|
|
@ -430,7 +437,7 @@ abstract class AppRoutes {
|
|||
// pageBuilder: (context, state) => defaultPageBuilder(
|
||||
// context,
|
||||
// state,
|
||||
// const Login(),
|
||||
// Login(client: state.extra as Client),
|
||||
// ),
|
||||
// redirect: loggedOutRedirect,
|
||||
// ),
|
||||
|
|
@ -448,17 +455,6 @@ abstract class AppRoutes {
|
|||
},
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'homeserver',
|
||||
pageBuilder: (context, state) {
|
||||
return defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
const SettingsHomeserver(),
|
||||
);
|
||||
},
|
||||
redirect: loggedOutRedirect,
|
||||
),
|
||||
GoRoute(
|
||||
path: 'security',
|
||||
redirect: loggedOutRedirect,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import 'app_config.dart';
|
|||
abstract class FluffyThemes {
|
||||
static const double columnWidth = 380.0;
|
||||
|
||||
static const double maxTimelineWidth = columnWidth * 2;
|
||||
|
||||
// #Pangea
|
||||
// static const double navRailWidth = 80.0;
|
||||
static const double navRailWidth = 72.0;
|
||||
|
|
@ -63,8 +65,11 @@ abstract class FluffyThemes {
|
|||
? colorScheme.surfaceContainerHighest
|
||||
: colorScheme.surfaceContainer,
|
||||
popupMenuTheme: PopupMenuThemeData(
|
||||
color: colorScheme.surfaceContainerLow,
|
||||
iconColor: colorScheme.onSurface,
|
||||
textStyle: TextStyle(color: colorScheme.onSurface),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
),
|
||||
),
|
||||
segmentedButtonTheme: SegmentedButtonThemeData(
|
||||
|
|
|
|||
|
|
@ -1,29 +1,35 @@
|
|||
{
|
||||
"repeatPassword": "Gentag password",
|
||||
"@repeatPassword": {},
|
||||
"notAnImage": "Ikke en billedfil.",
|
||||
"@notAnImage": {},
|
||||
"setCustomPermissionLevel": "Indstil særligt tilladelsesniveau",
|
||||
"@setCustomPermissionLevel": {},
|
||||
"setPermissionsLevelDescription": "Vælg en prædefineret rolle herunder eller indtaste et særligt tilladelsesniveau mellem 0 og 100.",
|
||||
"@setPermissionsLevelDescription": {},
|
||||
"ignoreUser": "Ignorér bruger",
|
||||
"@ignoreUser": {},
|
||||
"remove": "Fjern",
|
||||
"@remove": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"importNow": "Importer nu",
|
||||
"@importNow": {},
|
||||
"importEmojis": "Importer emojis",
|
||||
"@importEmojis": {},
|
||||
"normalUser": "Normal bruger",
|
||||
"@normalUser": {},
|
||||
"importFromZipFile": "Importer fra .zip fil",
|
||||
"@importFromZipFile": {},
|
||||
"alwaysUse24HourFormat": "true",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"description": "Set to true to always display time of day in 24 hour format."
|
||||
}
|
||||
}
|
||||
"repeatPassword": "Gentag password",
|
||||
"@repeatPassword": {},
|
||||
"notAnImage": "Ikke en billedfil.",
|
||||
"@notAnImage": {},
|
||||
"setCustomPermissionLevel": "Indstil særligt tilladelsesniveau",
|
||||
"@setCustomPermissionLevel": {},
|
||||
"setPermissionsLevelDescription": "Vælg en prædefineret rolle herunder eller indtaste et særligt tilladelsesniveau mellem 0 og 100.",
|
||||
"@setPermissionsLevelDescription": {},
|
||||
"ignoreUser": "Ignorér bruger",
|
||||
"@ignoreUser": {},
|
||||
"remove": "Fjern",
|
||||
"@remove": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"importNow": "Importer nu",
|
||||
"@importNow": {},
|
||||
"importEmojis": "Importer emojis",
|
||||
"@importEmojis": {},
|
||||
"normalUser": "Normal bruger",
|
||||
"@normalUser": {},
|
||||
"importFromZipFile": "Importer fra .zip fil",
|
||||
"@importFromZipFile": {},
|
||||
"alwaysUse24HourFormat": "true",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"description": "Set to true to always display time of day in 24 hour format."
|
||||
},
|
||||
"exportEmotePack": "Eksportér Emote-pakke som .zip-fil",
|
||||
"@exportEmotePack": {},
|
||||
"replace": "Erstat",
|
||||
"@replace": {},
|
||||
"about": "Om",
|
||||
"@about": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3240,6 +3240,7 @@
|
|||
"commandHint_logout": "Logout your current device",
|
||||
"commandHint_logoutall": "Logout all active devices",
|
||||
"displayNavigationRail": "Show navigation rail on mobile",
|
||||
"customReaction": "Custom reaction",
|
||||
"accountInformation": "Account information",
|
||||
"addGroupDescription": "Add a chat description",
|
||||
"addNewFriend": "Add new friend",
|
||||
|
|
|
|||
|
|
@ -3342,6 +3342,51 @@
|
|||
"@notSupportedOnThisDevice": {},
|
||||
"enterNewChat": "Ingresar a nuevo chat",
|
||||
"@enterNewChat": {},
|
||||
"pleaseWaitUntilInvited": "Por favor espera, hasta que alguien del chat te invite.",
|
||||
"@pleaseWaitUntilInvited": {},
|
||||
"commandHint_roomupgrade": "Actualizar este chat a la versión de chat dada",
|
||||
"@commandHint_roomupgrade": {},
|
||||
"checkList": "Lista de tareas",
|
||||
"@checkList": {},
|
||||
"countInvited": "{count} invitado",
|
||||
"@countInvited": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sentVoiceMessage": "🎙️ {duration} - Mensaje de voz de {sender}",
|
||||
"@sentVoiceMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
"sender": {
|
||||
"type": "String"
|
||||
},
|
||||
"duration": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"setCustomPermissionLevel": "Agregar nivel personalizado de permiso",
|
||||
"@setCustomPermissionLevel": {},
|
||||
"setPermissionsLevelDescription": "Por favor elige un rol predeterminado o un nivel de permiso personalizado entre 0 a 100.",
|
||||
"@setPermissionsLevelDescription": {},
|
||||
"ignoreUser": "Ignorar usuario",
|
||||
"@ignoreUser": {},
|
||||
"normalUser": "Usuario normal",
|
||||
"@normalUser": {},
|
||||
"commandHint_logout": "Salir del dispositivo actual",
|
||||
"@commandHint_logout": {},
|
||||
"commandHint_logoutall": "Salir de todos los dispositivos activos",
|
||||
"@commandHint_logoutall": {},
|
||||
"displayNavigationRail": "Mostrar carril de navegación en móvil",
|
||||
"@displayNavigationRail": {},
|
||||
"youHaveKnocked": "Has sido golpeado",
|
||||
"@youHaveKnocked": {},
|
||||
"approve": "Aprobar",
|
||||
"@approve": {},
|
||||
"accountInformation": "Información de la cuenta",
|
||||
"addGroupDescription": "Agregar una descripción al grupo",
|
||||
"alreadyHaveAnAccount": "¿Ya tiene una cuenta?",
|
||||
|
|
@ -5450,11 +5495,6 @@
|
|||
},
|
||||
"downloadGboard": "Descargar Gboard",
|
||||
"autocorrectNotAvailable": "Desafortunadamente, tu plataforma no es compatible actualmente con esta función. ¡Mantente atento a futuros desarrollos!",
|
||||
"setCustomPermissionLevel": "Establecer nivel de permiso personalizado",
|
||||
"setPermissionsLevelDescription": "Por favor, elige un rol predefinido a continuación o ingresa un nivel de permiso personalizado entre 0 y 100.",
|
||||
"ignoreUser": "Ignorar usuario",
|
||||
"normalUser": "Usuario normal",
|
||||
"commandHint_roomupgrade": "Mejorar esta sala a la versión de sala dada",
|
||||
"completeActivitiesToUnlock": "¡Completa al menos una actividad para desbloquear la traducción!",
|
||||
"constructUseGaDesc": "Asistencia gramatical",
|
||||
"constructUseTaDesc": "Asistencia de traducción",
|
||||
|
|
@ -5530,9 +5570,6 @@
|
|||
"ban": "Prohibir",
|
||||
"unban": "Desbloquear",
|
||||
"kick": "Expulsar",
|
||||
"approve": "Aprobar",
|
||||
"youHaveKnocked": "Has llamado",
|
||||
"pleaseWaitUntilInvited": "Por favor, espera hasta que alguien de la sala te invite.",
|
||||
"lemma": "Lema",
|
||||
"grammarFeature": "Característica gramatical",
|
||||
"grammarTag": "Etiqueta gramatical",
|
||||
|
|
@ -5560,7 +5597,6 @@
|
|||
"configureSpace": "Configurar espacio",
|
||||
"pinMessages": "Fijar mensajes",
|
||||
"setJoinRules": "Establecer reglas de unión",
|
||||
"displayNavigationRail": "Mostrar barra de navegación en móvil",
|
||||
"changeGeneralSettings": "Cambiar configuraciones generales",
|
||||
"inviteOtherUsersToRoom": "Invitar a otros usuarios",
|
||||
"changeTheNameOfTheSpace": "Cambiar el nombre del espacio",
|
||||
|
|
@ -5568,26 +5604,6 @@
|
|||
"changeThePermissions": "Cambiar los permisos",
|
||||
"introductions": "Introducciones",
|
||||
"announcements": "Anuncios",
|
||||
"@setCustomPermissionLevel": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@setPermissionsLevelDescription": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@ignoreUser": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@normalUser": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@commandHint_roomupgrade": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@completeActivitiesToUnlock": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -5908,18 +5924,6 @@
|
|||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@approve": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@youHaveKnocked": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@pleaseWaitUntilInvited": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@lemma": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
@ -6036,10 +6040,6 @@
|
|||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@displayNavigationRail": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
},
|
||||
"@changeGeneralSettings": {
|
||||
"type": "String",
|
||||
"placeholders": {}
|
||||
|
|
|
|||
6752
lib/l10n/intl_et.arb
6748
lib/l10n/intl_eu.arb
6766
lib/l10n/intl_ga.arb
6748
lib/l10n/intl_gl.arb
6752
lib/l10n/intl_id.arb
6720
lib/l10n/intl_lv.arb
6746
lib/l10n/intl_nl.arb
6748
lib/l10n/intl_pl.arb
|
|
@ -3351,7 +3351,7 @@
|
|||
"@approve": {},
|
||||
"youHaveKnocked": "Ви постукали",
|
||||
"@youHaveKnocked": {},
|
||||
"sentVoiceMessage": "🎙️ {duration} - {sender}",
|
||||
"sentVoiceMessage": "🎙️ {duration} - {sender} - Голосове повідомлення від {sender}",
|
||||
"@sentVoiceMessage": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
|
|
@ -3373,5 +3373,9 @@
|
|||
}
|
||||
},
|
||||
"checkList": "Контрольний список",
|
||||
"@checkList": {}
|
||||
"@checkList": {},
|
||||
"commandHint_logout": "Вийти на цьому пристрої",
|
||||
"@commandHint_logout": {},
|
||||
"commandHint_logoutall": "Вийти на всіх активних пристроях",
|
||||
"@commandHint_logoutall": {}
|
||||
}
|
||||
|
|
|
|||
6754
lib/l10n/intl_zh.arb
|
|
@ -50,6 +50,10 @@ void main() async {
|
|||
// widget bindings are initialized already.
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// #Pangea
|
||||
// await vod.init(wasmPath: './assets/assets/vodozemac/');
|
||||
// Pangea#
|
||||
|
||||
Logs().nativeColors = !PlatformInfos.isIOS;
|
||||
final store = await SharedPreferences.getInstance();
|
||||
final clients = await ClientManager.getClients(store: store);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// ignore_for_file: depend_on_referenced_packages, implementation_imports
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -252,8 +249,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
context.go('/rooms');
|
||||
}
|
||||
|
||||
EmojiPickerType emojiPickerType = EmojiPickerType.keyboard;
|
||||
|
||||
// #Pangea
|
||||
// void requestHistory([_]) async {
|
||||
Future<void> requestHistory() async {
|
||||
|
|
@ -680,6 +675,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
// Pangea#
|
||||
if (state != AppLifecycleState.resumed) return;
|
||||
if (!mounted) return;
|
||||
setReadMarker();
|
||||
}
|
||||
|
||||
|
|
@ -695,6 +691,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
return;
|
||||
}
|
||||
// Pangea#
|
||||
if (eventId?.isValidMatrixId == false) return;
|
||||
if (_setReadMarkerFuture != null) return;
|
||||
if (_scrolledUp) return;
|
||||
if (scrollUpBannerEventId != null) return;
|
||||
|
|
@ -933,7 +930,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// editEventId: editEvent?.eventId,
|
||||
// parseCommands: parseCommands,
|
||||
// );
|
||||
|
||||
// If the message and the sendController text don't match, it's possible
|
||||
// that there was a delay in tokenization before send, and the user started
|
||||
// typing a new message. We don't want to erase that, so only reset the input
|
||||
|
|
@ -1040,7 +1036,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// text: pendingText,
|
||||
// selection: const TextSelection.collapsed(offset: 0),
|
||||
// );
|
||||
// Pangea#
|
||||
|
||||
setState(() {
|
||||
// #Pangea
|
||||
|
|
@ -1235,13 +1230,11 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
} else {
|
||||
inputFocus.unfocus();
|
||||
}
|
||||
emojiPickerType = EmojiPickerType.keyboard;
|
||||
setState(() => showEmojiPicker = !showEmojiPicker);
|
||||
}
|
||||
|
||||
void _inputFocusListener() {
|
||||
if (showEmojiPicker && inputFocus.hasFocus) {
|
||||
emojiPickerType = EmojiPickerType.keyboard;
|
||||
setState(() => showEmojiPicker = false);
|
||||
}
|
||||
}
|
||||
|
|
@ -1443,9 +1436,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
bool get canEditSelectedEvents {
|
||||
if (isArchived ||
|
||||
selectedEvents.length != 1 ||
|
||||
// #Pangea
|
||||
selectedEvents.single.messageType != MessageTypes.Text ||
|
||||
// Pangea#
|
||||
!selectedEvents.first.status.isSent) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1455,21 +1445,18 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
void forwardEventsAction() async {
|
||||
if (selectedEvents.isEmpty) return;
|
||||
final timeline = this.timeline;
|
||||
if (timeline == null) return;
|
||||
|
||||
final forwardEvents = List<Event>.from(selectedEvents)
|
||||
.map((event) => event.getDisplayEvent(timeline))
|
||||
.toList();
|
||||
|
||||
await showScaffoldDialog(
|
||||
context: context,
|
||||
builder: (context) => ShareScaffoldDialog(
|
||||
items: selectedEvents
|
||||
// #Pangea
|
||||
// https://github.com/pangeachat/client/issues/2934
|
||||
// .map((event) => ContentShareItem(event.content))
|
||||
.map(
|
||||
(event) => timeline != null
|
||||
? ContentShareItem(
|
||||
event.getDisplayEvent(timeline!).content,
|
||||
)
|
||||
: ContentShareItem(event.content),
|
||||
)
|
||||
// Pangea#
|
||||
items: forwardEvents
|
||||
.map((event) => ContentShareItem(event.content))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
|
|
@ -1576,34 +1563,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
void onEmojiSelected(_, Emoji? emoji) {
|
||||
switch (emojiPickerType) {
|
||||
case EmojiPickerType.reaction:
|
||||
senEmojiReaction(emoji);
|
||||
break;
|
||||
case EmojiPickerType.keyboard:
|
||||
typeEmoji(emoji);
|
||||
onInputBarChanged(sendController.text);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void senEmojiReaction(Emoji? emoji) {
|
||||
setState(() => showEmojiPicker = false);
|
||||
if (emoji == null) return;
|
||||
// make sure we don't send the same emoji twice
|
||||
if (_allReactionEvents.any(
|
||||
(e) => e.content.tryGetMap('m.relates_to')?['key'] == emoji.emoji,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
// #Pangea
|
||||
// return sendEmojiAction(emoji.emoji);
|
||||
sendEmojiAction(emoji.emoji);
|
||||
|
||||
// don't need to clear these when sending while in select mode,
|
||||
// but do need to clear these when reacting from the large emoji picker
|
||||
setState(() => selectedEvents.clear());
|
||||
// Pangea#
|
||||
typeEmoji(emoji);
|
||||
onInputBarChanged(sendController.text);
|
||||
}
|
||||
|
||||
void typeEmoji(Emoji? emoji) {
|
||||
|
|
@ -1618,7 +1579,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
final selection = sendController.selection;
|
||||
final newText = sendController.text.isEmpty
|
||||
? emoji.emoji
|
||||
|
|
@ -1632,54 +1592,12 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
}
|
||||
|
||||
late Iterable<Event> _allReactionEvents;
|
||||
|
||||
void emojiPickerBackspace() {
|
||||
switch (emojiPickerType) {
|
||||
case EmojiPickerType.reaction:
|
||||
setState(() => showEmojiPicker = false);
|
||||
break;
|
||||
case EmojiPickerType.keyboard:
|
||||
sendController
|
||||
..text = sendController.text.characters.skipLast(1).toString()
|
||||
..selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: sendController.text.length),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pickEmojiReactionAction(Iterable<Event> allReactionEvents) async {
|
||||
// #Pangea
|
||||
closeSelectionOverlay();
|
||||
// Pangea#
|
||||
_allReactionEvents = allReactionEvents;
|
||||
emojiPickerType = EmojiPickerType.reaction;
|
||||
setState(() => showEmojiPicker = true);
|
||||
}
|
||||
|
||||
void sendEmojiAction(String? emoji) async {
|
||||
final events = List<Event>.from(selectedEvents);
|
||||
// #Pangea
|
||||
// keep this event selected in case the user wants to send another emoji
|
||||
// setState(() => selectedEvents.clear());
|
||||
// Pangea#
|
||||
// if reaction already exists, don't send it again
|
||||
if (timeline == null ||
|
||||
events.any(
|
||||
(e) => e.aggregatedEvents(timeline!, RelationshipTypes.reaction).any(
|
||||
(re) => re.content.tryGetMap('m.relates_to')?['key'] == emoji,
|
||||
),
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (final event in events) {
|
||||
await room.sendReaction(
|
||||
event.eventId,
|
||||
emoji!,
|
||||
sendController
|
||||
..text = sendController.text.characters.skipLast(1).toString()
|
||||
..selection = TextSelection.fromPosition(
|
||||
TextPosition(offset: sendController.text.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
|
|
@ -1711,7 +1629,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
selectedEvents.add(event);
|
||||
});
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
void clearSingleSelectedEvent() {
|
||||
if (selectedEvents.length <= 1) {
|
||||
|
|
@ -1785,9 +1702,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
final matches = selectedEvents.where((e) => e.eventId == event.eventId);
|
||||
if (matches.isNotEmpty) {
|
||||
setState(() => selectedEvents.remove(matches.first));
|
||||
}
|
||||
// Pangea#
|
||||
else {
|
||||
// Pangea#
|
||||
} else {
|
||||
setState(
|
||||
() => selectedEvents.add(event),
|
||||
);
|
||||
|
|
@ -1892,7 +1808,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
Timer? _storeInputTimeoutTimer;
|
||||
Duration storeInputTimeout = const Duration(milliseconds: 500);
|
||||
static const Duration _storeInputTimeout = Duration(milliseconds: 500);
|
||||
|
||||
void onInputBarChanged(String text) {
|
||||
if (_inputTextIsEmpty != text.isEmpty) {
|
||||
|
|
@ -1902,7 +1818,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
_storeInputTimeoutTimer?.cancel();
|
||||
_storeInputTimeoutTimer = Timer(storeInputTimeout, () async {
|
||||
_storeInputTimeoutTimer = Timer(_storeInputTimeout, () async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('draft_$roomId', text);
|
||||
});
|
||||
|
|
@ -1946,12 +1862,14 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
bool get isArchived =>
|
||||
{Membership.leave, Membership.ban}.contains(room.membership);
|
||||
|
||||
// #Pangea
|
||||
// void showEventInfo([Event? event]) =>
|
||||
// (event ?? selectedEvents.single).showInfoDialog(context);
|
||||
void showEventInfo([Event? event]) {
|
||||
(event ?? selectedEvents.single).showInfoDialog(context);
|
||||
// #Pangea
|
||||
clearSelectedEvents();
|
||||
// Pangea#
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
void onPhoneButtonTap() async {
|
||||
// VoIP required Android SDK 21
|
||||
|
|
@ -2009,7 +1927,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
replyEvent = null;
|
||||
editEvent = null;
|
||||
});
|
||||
|
||||
// #Pangea
|
||||
String? get buttonEventID => timeline!.events
|
||||
.firstWhereOrNull(
|
||||
|
|
@ -2270,5 +2187,3 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum EmojiPickerType { reaction, keyboard }
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class ChatAppBarTitle extends StatelessWidget {
|
|||
return Text(
|
||||
controller.selectedEvents.length.toString(),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
color: Theme.of(context).colorScheme.onTertiaryContainer,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,6 +201,10 @@ class ChatEventList extends StatelessWidget {
|
|||
// Pangea#
|
||||
selected: controller.selectedEvents
|
||||
.any((e) => e.eventId == event.eventId),
|
||||
singleSelected:
|
||||
controller.selectedEvents.singleOrNull?.eventId ==
|
||||
event.eventId,
|
||||
onEdit: () => controller.editSelectedEventAction(),
|
||||
timeline: timeline,
|
||||
displayReadMarker: i > 0 &&
|
||||
controller.readMarkerEventId == event.eventId,
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
if (controller.showEmojiPicker &&
|
||||
controller.emojiPickerType == EmojiPickerType.reaction) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
const height = 48.0;
|
||||
|
||||
if (!controller.room.otherPartyCanReceiveMessages) {
|
||||
|
|
@ -40,6 +37,10 @@ class ChatInputRow extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
final selectedTextButtonStyle = TextButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.onTertiaryContainer,
|
||||
);
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
|
@ -51,7 +52,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
height: height,
|
||||
child: TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.error,
|
||||
foregroundColor: Colors.orange,
|
||||
),
|
||||
onPressed: controller.deleteErrorEventsAction,
|
||||
child: Row(
|
||||
|
|
@ -66,6 +67,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
SizedBox(
|
||||
height: height,
|
||||
child: TextButton(
|
||||
style: selectedTextButtonStyle,
|
||||
onPressed: controller.forwardEventsAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
|
|
@ -83,6 +85,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
? SizedBox(
|
||||
height: height,
|
||||
child: TextButton(
|
||||
style: selectedTextButtonStyle,
|
||||
onPressed: controller.replyAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
|
|
@ -95,6 +98,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
: SizedBox(
|
||||
height: height,
|
||||
child: TextButton(
|
||||
style: selectedTextButtonStyle,
|
||||
onPressed: controller.sendAgainAction,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
|
|
@ -276,8 +280,8 @@ class ChatInputRow extends StatelessWidget {
|
|||
: null,
|
||||
// #Pangea
|
||||
// onSubmitted: controller.onInputBarSubmitted,
|
||||
onSubmitted: (value) =>
|
||||
controller.onInputBarSubmitted(value, context),
|
||||
onSubmitted: (_) =>
|
||||
controller.onInputBarSubmitted(_, context),
|
||||
// Pangea#
|
||||
onSubmitImage: controller.sendImageFromClipBoard,
|
||||
focusNode: controller.inputFocus,
|
||||
|
|
|
|||
|
|
@ -45,15 +45,6 @@ class ChatView extends StatelessWidget {
|
|||
tooltip: L10n.of(context).copy,
|
||||
onPressed: controller.copyEventsAction,
|
||||
),
|
||||
if (controller.canSaveSelectedEvent)
|
||||
// Use builder context to correctly position the share dialog on iPad
|
||||
Builder(
|
||||
builder: (context) => IconButton(
|
||||
icon: Icon(Icons.adaptive.share),
|
||||
tooltip: L10n.of(context).share,
|
||||
onPressed: () => controller.saveSelectedEvent(context),
|
||||
),
|
||||
),
|
||||
if (controller.canPinSelectedEvents)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.push_pin_outlined),
|
||||
|
|
@ -81,6 +72,19 @@ class ChatView extends StatelessWidget {
|
|||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
if (controller.canSaveSelectedEvent)
|
||||
PopupMenuItem(
|
||||
onTap: () => controller.saveSelectedEvent(context),
|
||||
value: null,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.download_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).downloadFile),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: _EventContextAction.info,
|
||||
child: Row(
|
||||
|
|
@ -193,15 +197,18 @@ class ChatView extends StatelessWidget {
|
|||
actionsIconTheme: IconThemeData(
|
||||
color: controller.selectedEvents.isEmpty
|
||||
? null
|
||||
: theme.colorScheme.tertiary,
|
||||
: theme.colorScheme.onTertiaryContainer,
|
||||
),
|
||||
backgroundColor: controller.selectedEvents.isEmpty
|
||||
? null
|
||||
: theme.colorScheme.tertiaryContainer,
|
||||
automaticallyImplyLeading: false,
|
||||
leading: controller.selectMode
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: controller.clearSelectedEvents,
|
||||
tooltip: L10n.of(context).close,
|
||||
color: theme.colorScheme.tertiary,
|
||||
color: theme.colorScheme.onTertiaryContainer,
|
||||
)
|
||||
: FluffyThemes.isColumnMode(context)
|
||||
? null
|
||||
|
|
@ -297,12 +304,12 @@ class ChatView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
SafeArea(
|
||||
child:
|
||||
// #Pangea
|
||||
Stack(
|
||||
// #Pangea
|
||||
// child: Column(
|
||||
child: Stack(
|
||||
children: [
|
||||
// Pangea#
|
||||
Column(
|
||||
// Pangea#
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
|
|
@ -310,13 +317,14 @@ class ChatView extends StatelessWidget {
|
|||
child: ChatEventList(controller: controller),
|
||||
),
|
||||
),
|
||||
if (controller.showScrollDownButton)
|
||||
Divider(
|
||||
height: 1,
|
||||
color: theme.dividerColor,
|
||||
),
|
||||
if (controller.room.isExtinct)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
margin: EdgeInsets.all(bottomSheetPadding),
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
|
|
@ -327,18 +335,16 @@ class ChatView extends StatelessWidget {
|
|||
else if (controller.room.canSendDefaultMessages &&
|
||||
controller.room.membership == Membership.join)
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: bottomSheetPadding,
|
||||
left: bottomSheetPadding,
|
||||
right: bottomSheetPadding,
|
||||
),
|
||||
margin: EdgeInsets.all(bottomSheetPadding),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
maxWidth: FluffyThemes.maxTimelineWidth,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: theme.colorScheme.surfaceContainerHigh,
|
||||
color: controller.selectedEvents.isNotEmpty
|
||||
? theme.colorScheme.tertiaryContainer
|
||||
: theme.colorScheme.surfaceContainerHigh,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
|
|
@ -386,7 +392,6 @@ class ChatView extends StatelessWidget {
|
|||
// : Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// ReactionsPicker(controller),
|
||||
// ReplyDisplay(controller),
|
||||
// ChatInputRow(controller),
|
||||
// ChatEmojiPicker(controller),
|
||||
|
|
|
|||
|
|
@ -519,6 +519,7 @@ class InputBar extends StatelessWidget {
|
|||
// builder: (context, controller, focusNode) => TextField(
|
||||
// controller: controller,
|
||||
// focusNode: focusNode,
|
||||
// readOnly: readOnly,
|
||||
// contextMenuBuilder: (c, e) => markdownContextBuilder(c, e, controller),
|
||||
// contentInsertionConfiguration: ContentInsertionConfiguration(
|
||||
// onContentInserted: (KeyboardInsertedContent content) {
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/app_emojis.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import '../../config/themes.dart';
|
||||
|
||||
class ReactionsPicker extends StatelessWidget {
|
||||
final ChatController controller;
|
||||
|
||||
const ReactionsPicker(this.controller, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
if (controller.showEmojiPicker) return const SizedBox.shrink();
|
||||
final display = controller.editEvent == null &&
|
||||
controller.replyEvent == null &&
|
||||
controller.room.canSendDefaultMessages &&
|
||||
controller.selectedEvents.isNotEmpty;
|
||||
return AnimatedContainer(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
// #Pangea
|
||||
// height: (display) ? 56 : 0,
|
||||
height: (display) ? AppConfig.reactionsPickerHeight : 0,
|
||||
// Pangea#
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
if (!display) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final emojis = List<String>.from(AppEmojis.emojis);
|
||||
final allReactionEvents = controller.selectedEvents.first
|
||||
.aggregatedEvents(
|
||||
controller.timeline!,
|
||||
RelationshipTypes.reaction,
|
||||
)
|
||||
.where(
|
||||
(event) =>
|
||||
event.senderId == event.room.client.userID &&
|
||||
event.type == 'm.reaction',
|
||||
);
|
||||
|
||||
for (final event in allReactionEvents) {
|
||||
try {
|
||||
emojis.remove(event.content.tryGetMap('m.relates_to')!['key']);
|
||||
} catch (_) {}
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
// #Pangea
|
||||
// color: theme.colorScheme.onInverseSurface,
|
||||
// Pangea#
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomRight: Radius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.only(right: 1),
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: emojis.length,
|
||||
itemBuilder: (c, i) => InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => controller.sendEmojiAction(emojis[i]),
|
||||
child: Container(
|
||||
// #Pangea
|
||||
// width: 56,
|
||||
// height: 56,
|
||||
width: AppConfig.reactionsPickerHeight,
|
||||
height: AppConfig.reactionsPickerHeight,
|
||||
// Pangea#
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
emojis[i],
|
||||
// #Pangea
|
||||
// style: const TextStyle(fontSize: 30),
|
||||
style: const TextStyle(fontSize: 20),
|
||||
// Pangea#
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
width: 36,
|
||||
// #Pangea
|
||||
// height: 56,
|
||||
height: AppConfig.reactionsPickerHeight,
|
||||
// Pangea#
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.add_outlined),
|
||||
),
|
||||
onTap: () =>
|
||||
controller.pickEmojiReactionAction(allReactionEvents),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ class SeenByRow extends StatelessWidget {
|
|||
alignment: Alignment.center,
|
||||
child: AnimatedContainer(
|
||||
constraints:
|
||||
const BoxConstraints(maxWidth: FluffyThemes.columnWidth * 2.5),
|
||||
const BoxConstraints(maxWidth: FluffyThemes.maxTimelineWidth),
|
||||
height: seenByUsers.isEmpty ? 0 : 24,
|
||||
duration: seenByUsers.isEmpty
|
||||
? Duration.zero
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class TypingIndicators extends StatelessWidget {
|
|||
alignment: Alignment.center,
|
||||
child: AnimatedContainer(
|
||||
constraints:
|
||||
const BoxConstraints(maxWidth: FluffyThemes.columnWidth * 2.5),
|
||||
const BoxConstraints(maxWidth: FluffyThemes.maxTimelineWidth),
|
||||
height: typingUsers.isEmpty ? 0 : avatarSize + 8,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
|
|
|
|||
|
|
@ -1177,7 +1177,13 @@ class ChatListController extends State<ChatList>
|
|||
// if (client.prevBatch == null) {
|
||||
if (client.onSync.value?.nextBatch == null) {
|
||||
// Pangea#
|
||||
await client.onSync.stream.first;
|
||||
await client.onSyncStatus.stream
|
||||
.firstWhere((status) => status.status == SyncStatus.finished);
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
waitForFirstSync = true;
|
||||
});
|
||||
|
||||
// Display first login bootstrap if enabled
|
||||
// #Pangea
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||
: status.calcLocalizedString(context),
|
||||
hintStyle: TextStyle(
|
||||
color: status.error != null
|
||||
? theme.colorScheme.error
|
||||
? Colors.orange
|
||||
: theme.colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
|
|
@ -88,8 +88,8 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||
strokeWidth: 2,
|
||||
value: status.progress,
|
||||
valueColor: status.error != null
|
||||
? AlwaysStoppedAnimation<Color>(
|
||||
theme.colorScheme.error,
|
||||
? const AlwaysStoppedAnimation<Color>(
|
||||
Colors.orange,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -69,10 +69,11 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
homeserverController.text.trim().toLowerCase().replaceAll(' ', '-');
|
||||
|
||||
if (homeserverInput.isEmpty) {
|
||||
final client = await Matrix.of(context).getLoginClient();
|
||||
setState(() {
|
||||
error = loginFlows = null;
|
||||
isLoading = false;
|
||||
Matrix.of(context).getLoginClient().homeserver = null;
|
||||
client.homeserver = null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -88,7 +89,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
if (homeserver.scheme.isEmpty) {
|
||||
homeserver = Uri.https(homeserverInput, '');
|
||||
}
|
||||
final client = Matrix.of(context).getLoginClient();
|
||||
final client = await Matrix.of(context).getLoginClient();
|
||||
final (_, _, loginFlows) = await client.checkHomeserver(homeserver);
|
||||
this.loginFlows = loginFlows;
|
||||
if (supportsSso && !legacyPasswordLogin) {
|
||||
|
|
@ -105,6 +106,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
}
|
||||
context.push(
|
||||
'${GoRouter.of(context).routeInformationProvider.value.uri.path}/login',
|
||||
extra: client,
|
||||
);
|
||||
} catch (e) {
|
||||
setState(
|
||||
|
|
@ -142,8 +144,8 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
: isDefaultPlatform
|
||||
? '${AppConfig.appOpenUrlScheme.toLowerCase()}://login'
|
||||
: 'http://localhost:3001//login';
|
||||
|
||||
final url = Matrix.of(context).getLoginClient().homeserver!.replace(
|
||||
final client = await Matrix.of(context).getLoginClient();
|
||||
final url = client.homeserver!.replace(
|
||||
path: '/_matrix/client/v3/login/sso/redirect',
|
||||
queryParameters: {'redirectUrl': redirectUrl},
|
||||
);
|
||||
|
|
@ -164,11 +166,11 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
isLoading = true;
|
||||
});
|
||||
try {
|
||||
await Matrix.of(context).getLoginClient().login(
|
||||
LoginType.mLoginToken,
|
||||
token: token,
|
||||
initialDeviceDisplayName: PlatformInfos.clientName,
|
||||
);
|
||||
await client.login(
|
||||
LoginType.mLoginToken,
|
||||
token: token,
|
||||
initialDeviceDisplayName: PlatformInfos.clientName,
|
||||
);
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
error = e.toLocalizedString(context);
|
||||
|
|
@ -200,7 +202,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
isLoading = true;
|
||||
});
|
||||
try {
|
||||
final client = Matrix.of(context).getLoginClient();
|
||||
final client = await Matrix.of(context).getLoginClient();
|
||||
await client.importDump(String.fromCharCodes(await file.readAsBytes()));
|
||||
Matrix.of(context).initMatrix();
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ class HomeserverPickerView extends StatelessWidget {
|
|||
final theme = Theme.of(context);
|
||||
|
||||
return LoginScaffold(
|
||||
enforceMobileMode: Matrix.of(context).client.isLogged(),
|
||||
enforceMobileMode:
|
||||
Matrix.of(context).widget.clients.any((client) => client.isLogged()),
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
title: Text(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class Login extends StatefulWidget {
|
||||
const Login({super.key});
|
||||
final Client client;
|
||||
const Login({required this.client, super.key});
|
||||
|
||||
@override
|
||||
LoginController createState() => LoginController();
|
||||
|
|
@ -132,17 +133,18 @@ class LoginController extends State<Login> {
|
|||
// } else {
|
||||
// identifier = AuthenticationUserIdentifier(user: username);
|
||||
// }
|
||||
// await matrix.getLoginClient().login(
|
||||
// LoginType.mLoginPassword,
|
||||
// identifier: identifier,
|
||||
// // To stay compatible with older server versions
|
||||
// // ignore: deprecated_member_use
|
||||
// user: identifier.type == AuthenticationIdentifierTypes.userId
|
||||
// ? username
|
||||
// : null,
|
||||
// password: passwordController.text,
|
||||
// initialDeviceDisplayName: PlatformInfos.clientName,
|
||||
// );
|
||||
// final client = await matrix.getLoginClient();
|
||||
// client.login(
|
||||
// LoginType.mLoginPassword,
|
||||
// identifier: identifier,
|
||||
// // To stay compatible with older server versions
|
||||
// // ignore: deprecated_member_use
|
||||
// user: identifier.type == AuthenticationIdentifierTypes.userId
|
||||
// ? username
|
||||
// : null,
|
||||
// password: passwordController.text,
|
||||
// initialDeviceDisplayName: PlatformInfos.clientName,
|
||||
// );
|
||||
// } on MatrixException catch (exception) {
|
||||
// setState(() => passwordError = exception.errorMessage);
|
||||
// return setState(() => loading = false);
|
||||
|
|
@ -168,14 +170,13 @@ class LoginController extends State<Login> {
|
|||
void _checkWellKnown(String userId) async {
|
||||
if (mounted) setState(() => usernameError = null);
|
||||
if (!userId.isValidMatrixId) return;
|
||||
final oldHomeserver = Matrix.of(context).getLoginClient().homeserver;
|
||||
final oldHomeserver = widget.client.homeserver;
|
||||
try {
|
||||
var newDomain = Uri.https(userId.domain!, '');
|
||||
Matrix.of(context).getLoginClient().homeserver = newDomain;
|
||||
widget.client.homeserver = newDomain;
|
||||
DiscoveryInformation? wellKnownInformation;
|
||||
try {
|
||||
wellKnownInformation =
|
||||
await Matrix.of(context).getLoginClient().getWellknown();
|
||||
wellKnownInformation = await widget.client.getWellknown();
|
||||
if (wellKnownInformation.mHomeserver.baseUrl.toString().isNotEmpty) {
|
||||
newDomain = wellKnownInformation.mHomeserver.baseUrl;
|
||||
}
|
||||
|
|
@ -183,10 +184,10 @@ class LoginController extends State<Login> {
|
|||
// do nothing, newDomain is already set to a reasonable fallback
|
||||
}
|
||||
if (newDomain != oldHomeserver) {
|
||||
await Matrix.of(context).getLoginClient().checkHomeserver(newDomain);
|
||||
await widget.client.checkHomeserver(newDomain);
|
||||
|
||||
if (Matrix.of(context).getLoginClient().homeserver == null) {
|
||||
Matrix.of(context).getLoginClient().homeserver = oldHomeserver;
|
||||
if (widget.client.homeserver == null) {
|
||||
widget.client.homeserver = oldHomeserver;
|
||||
// okay, the server we checked does not appear to be a matrix server
|
||||
Logs().v(
|
||||
'$newDomain is not running a homeserver, asking to use $oldHomeserver',
|
||||
|
|
@ -209,13 +210,13 @@ class LoginController extends State<Login> {
|
|||
usernameError = null;
|
||||
if (mounted) setState(() {});
|
||||
} else {
|
||||
Matrix.of(context).getLoginClient().homeserver = oldHomeserver;
|
||||
widget.client.homeserver = oldHomeserver;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Matrix.of(context).getLoginClient().homeserver = oldHomeserver;
|
||||
widget.client.homeserver = oldHomeserver;
|
||||
usernameError = e.toLocalizedString(context);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
|
@ -238,12 +239,11 @@ class LoginController extends State<Login> {
|
|||
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
final response = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () =>
|
||||
Matrix.of(context).getLoginClient().requestTokenToResetPasswordEmail(
|
||||
clientSecret,
|
||||
input,
|
||||
sendAttempt++,
|
||||
),
|
||||
future: () => widget.client.requestTokenToResetPasswordEmail(
|
||||
clientSecret,
|
||||
input,
|
||||
sendAttempt++,
|
||||
),
|
||||
);
|
||||
if (response.error != null) return;
|
||||
final password = await showTextInputDialog(
|
||||
|
|
@ -283,11 +283,11 @@ class LoginController extends State<Login> {
|
|||
};
|
||||
final success = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => Matrix.of(context).getLoginClient().request(
|
||||
RequestType.POST,
|
||||
'/client/v3/account/password',
|
||||
data: data,
|
||||
),
|
||||
future: () => widget.client.request(
|
||||
RequestType.POST,
|
||||
'/client/v3/account/password',
|
||||
data: data,
|
||||
),
|
||||
);
|
||||
if (success.error == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
|
|
|||
|
|
@ -14,16 +14,15 @@ class LoginView extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final homeserver = Matrix.of(context)
|
||||
.getLoginClient()
|
||||
.homeserver
|
||||
final homeserver = controller.widget.client.homeserver
|
||||
.toString()
|
||||
.replaceFirst('https://', '');
|
||||
final title = L10n.of(context).logInTo(homeserver);
|
||||
final titleParts = title.split(homeserver);
|
||||
|
||||
return LoginScaffold(
|
||||
enforceMobileMode: Matrix.of(context).client.isLogged(),
|
||||
enforceMobileMode:
|
||||
Matrix.of(context).widget.clients.any((client) => client.isLogged()),
|
||||
appBar: AppBar(
|
||||
leading:
|
||||
controller.loadingSignIn ? null : const Center(child: BackButton()),
|
||||
|
|
|
|||
|
|
@ -266,7 +266,7 @@ class ActivityPlanMessage extends StatelessWidget {
|
|||
onSwipe: (_) {},
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
maxWidth: FluffyThemes.maxTimelineWidth,
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8.0,
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ class PutAnalyticsController extends BaseController<AnalyticsStream> {
|
|||
_analyticsStream = null;
|
||||
_languageStream?.cancel();
|
||||
_languageStream = null;
|
||||
_refreshAnalyticsIfOutdated();
|
||||
clearMessagesSinceUpdate();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class ChatInputBarState extends State<ChatInputBar> {
|
|||
right: widget.padding,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 2.5,
|
||||
maxWidth: FluffyThemes.maxTimelineWidth,
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Material(
|
||||
|
|
|
|||
|
|
@ -124,10 +124,6 @@ class PangeaChatInputRowState extends State<PangeaChatInputRow> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
if (_controller.showEmojiPicker &&
|
||||
_controller.emojiPickerType == EmojiPickerType.reaction) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
const height = 48.0;
|
||||
|
||||
return Column(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:get_storage/get_storage.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/contextual_definition_controller.dart';
|
||||
|
|
@ -24,7 +25,6 @@ import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.
|
|||
import 'package:fluffychat/pangea/user/controllers/permissions_controller.dart';
|
||||
import 'package:fluffychat/pangea/user/controllers/user_controller.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../../config/app_config.dart';
|
||||
import '../../choreographer/controllers/it_feedback_controller.dart';
|
||||
import '../utils/firebase_analytics.dart';
|
||||
|
||||
|
|
@ -132,7 +132,8 @@ class PangeaController {
|
|||
}
|
||||
|
||||
Future<void> checkHomeServerAction() async {
|
||||
if (matrixState.getLoginClient().homeserver != null) {
|
||||
final client = await matrixState.getLoginClient();
|
||||
if (client.homeserver != null) {
|
||||
await Future.delayed(Duration.zero);
|
||||
return;
|
||||
}
|
||||
|
|
@ -145,7 +146,7 @@ class PangeaController {
|
|||
}
|
||||
|
||||
try {
|
||||
await matrixState.getLoginClient().register();
|
||||
await client.register();
|
||||
matrixState.loginRegistrationSupported = true;
|
||||
} on MatrixException catch (e) {
|
||||
matrixState.loginRegistrationSupported =
|
||||
|
|
@ -156,7 +157,7 @@ class PangeaController {
|
|||
}
|
||||
|
||||
/// check user information if not found then redirect to Date of birth page
|
||||
_handleLoginStateChange(LoginState state) {
|
||||
_handleLoginStateChange(LoginState state, String? userID) {
|
||||
switch (state) {
|
||||
case LoginState.loggedOut:
|
||||
case LoginState.softLoggedOut:
|
||||
|
|
@ -180,12 +181,12 @@ class PangeaController {
|
|||
Sentry.configureScope(
|
||||
(scope) => scope.setUser(
|
||||
SentryUser(
|
||||
id: matrixState.client.userID,
|
||||
name: matrixState.client.userID,
|
||||
id: userID,
|
||||
name: userID,
|
||||
),
|
||||
),
|
||||
);
|
||||
GoogleAnalytics.analyticsUserUpdate(matrixState.client.userID);
|
||||
GoogleAnalytics.analyticsUserUpdate(userID);
|
||||
}
|
||||
|
||||
Future<void> resetAnalytics() async {
|
||||
|
|
@ -196,8 +197,9 @@ class PangeaController {
|
|||
}
|
||||
|
||||
void _subscribeToStreams() {
|
||||
final userID = matrixState.client.userID;
|
||||
matrixState.client.onLoginStateChanged.stream
|
||||
.listen(_handleLoginStateChange);
|
||||
.listen((state) => _handleLoginStateChange(state, userID));
|
||||
_setLanguageStream();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ class PAuthGaurd {
|
|||
GoRouterState state,
|
||||
) async {
|
||||
if (pController != null) {
|
||||
if (Matrix.of(context).client.isLogged()) {
|
||||
if (Matrix.of(context)
|
||||
.widget
|
||||
.clients
|
||||
.any((client) => client.isLogged())) {
|
||||
final bool dobIsSet =
|
||||
await pController!.userController.isUserDataAvailableAndL2Set;
|
||||
return dobIsSet ? '/rooms' : '/user_age';
|
||||
|
|
@ -34,7 +37,10 @@ class PAuthGaurd {
|
|||
GoRouterState state,
|
||||
) async {
|
||||
if (pController != null) {
|
||||
if (!Matrix.of(context).client.isLogged()) {
|
||||
if (!Matrix.of(context)
|
||||
.widget
|
||||
.clients
|
||||
.any((client) => client.isLogged())) {
|
||||
return '/home';
|
||||
}
|
||||
final bool dobIsSet =
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ class LemmaReactionPickerState extends State<LemmaReactionPicker> {
|
|||
}
|
||||
}
|
||||
|
||||
void setEmoji(String emoji) => widget.controller.sendEmojiAction(emoji);
|
||||
void setEmoji(String emoji) {}
|
||||
// widget.controller.sendEmojiAction(emoji);
|
||||
|
||||
Future<void> _refresh() async {
|
||||
setState(() {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/common/config/environment.dart';
|
||||
import 'package:fluffychat/pangea/login/pages/pangea_login_scaffold.dart';
|
||||
import 'package:fluffychat/pangea/login/widgets/app_config_dialog.dart';
|
||||
import 'package:fluffychat/pangea/login/widgets/full_width_button.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class LoginOrSignupView extends StatefulWidget {
|
||||
const LoginOrSignupView({super.key});
|
||||
|
|
@ -16,12 +18,17 @@ class LoginOrSignupView extends StatefulWidget {
|
|||
}
|
||||
|
||||
class LoginOrSignupViewState extends State<LoginOrSignupView> {
|
||||
Client? client;
|
||||
List<AppConfigOverride> _overrides = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadOverrides();
|
||||
|
||||
Matrix.of(context).getLoginClient().then((c) {
|
||||
if (mounted) setState(() => client = c);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadOverrides() async {
|
||||
|
|
@ -61,7 +68,12 @@ class LoginOrSignupViewState extends State<LoginOrSignupView> {
|
|||
),
|
||||
FullWidthButton(
|
||||
title: L10n.of(context).signIn,
|
||||
onPressed: () => context.go('/home/login'),
|
||||
onPressed: client != null
|
||||
? () => context.go(
|
||||
'/home/login',
|
||||
extra: Matrix.of(context).client,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ class SignupPageController extends State<SignupPage> {
|
|||
}
|
||||
|
||||
Future<void> _signupFuture() async {
|
||||
final client = Matrix.of(context).getLoginClient();
|
||||
final client = await Matrix.of(context).getLoginClient();
|
||||
final email = emailController.text;
|
||||
if (email.isNotEmpty) {
|
||||
Matrix.of(context).currentClientSecret =
|
||||
|
|
|
|||
|
|
@ -16,12 +16,10 @@ import 'package:fluffychat/widgets/matrix.dart';
|
|||
|
||||
Future<void> pangeaSSOLoginAction(
|
||||
IdentityProvider provider,
|
||||
Client client,
|
||||
BuildContext context,
|
||||
) async {
|
||||
final bool isDefaultPlatform =
|
||||
(PlatformInfos.isMobile || PlatformInfos.isWeb || PlatformInfos.isMacOS);
|
||||
|
||||
final redirectUrl = kIsWeb
|
||||
? Uri.parse(html.window.location.href)
|
||||
.resolveUri(
|
||||
|
|
@ -31,16 +29,15 @@ Future<void> pangeaSSOLoginAction(
|
|||
: isDefaultPlatform
|
||||
? '${AppConfig.appOpenUrlScheme.toLowerCase()}://login'
|
||||
: 'http://localhost:3001//login';
|
||||
|
||||
final url = Matrix.of(context).getLoginClient().homeserver!.replace(
|
||||
path: '/_matrix/client/v3/login/sso/redirect/${provider.id ?? ''}',
|
||||
final client = await Matrix.of(context).getLoginClient();
|
||||
final url = client.homeserver!.replace(
|
||||
path: '/_matrix/client/v3/login/sso/redirect',
|
||||
queryParameters: {'redirectUrl': redirectUrl},
|
||||
);
|
||||
|
||||
final urlScheme = isDefaultPlatform
|
||||
? Uri.parse(redirectUrl).scheme
|
||||
: "http://localhost:3001";
|
||||
|
||||
String result;
|
||||
try {
|
||||
result = await FlutterWebAuth2.authenticate(
|
||||
|
|
@ -54,7 +51,6 @@ Future<void> pangeaSSOLoginAction(
|
|||
}
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final token = Uri.parse(result).queryParameters['loginToken'];
|
||||
if (token?.isEmpty ?? false) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart';
|
|||
import 'package:fluffychat/pangea/login/utils/sso_login_action.dart';
|
||||
import 'package:fluffychat/pangea/login/widgets/full_width_button.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
enum SSOProvider { google, apple }
|
||||
|
||||
|
|
@ -67,7 +66,6 @@ class PangeaSsoButton extends StatelessWidget {
|
|||
id: provider.id,
|
||||
name: provider.name,
|
||||
),
|
||||
Matrix.of(context).getLoginClient(),
|
||||
context,
|
||||
),
|
||||
onError: (e, s) {
|
||||
|
|
|
|||
|
|
@ -533,7 +533,7 @@ class MessageSelectionPositionerState extends State<MessageSelectionPositioner>
|
|||
const double messageMargin = 16.0;
|
||||
// widget.event.isActivityMessage ? 0 : Avatar.defaultSize + 16 + 8;
|
||||
final bool showingDetails = widget.chatController.displayChatDetailsColumn;
|
||||
final double totalMaxWidth = (FluffyThemes.columnWidth * 2.5) -
|
||||
final double totalMaxWidth = FluffyThemes.maxTimelineWidth -
|
||||
(showingDetails ? FluffyThemes.columnWidth : 0) -
|
||||
messageMargin;
|
||||
double? maxWidth;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
|
||||
import 'package:fluffychat/pangea/events/utils/report_message.dart';
|
||||
|
||||
class OverlayHeader extends StatefulWidget {
|
||||
|
|
@ -118,14 +117,14 @@ class OverlayHeaderState extends State<OverlayHeader> {
|
|||
color: theme.colorScheme.primary,
|
||||
),
|
||||
|
||||
if (controller.canEditSelectedEvents &&
|
||||
!controller.selectedEvents.first.isActivityMessage)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
tooltip: l10n.edit,
|
||||
onPressed: controller.editSelectedEventAction,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
// if (controller.canEditSelectedEvents &&
|
||||
// !controller.selectedEvents.first.isActivityMessage)
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.edit_outlined),
|
||||
// tooltip: l10n.edit,
|
||||
// onPressed: controller.editSelectedEventAction,
|
||||
// color: theme.colorScheme.primary,
|
||||
// ),
|
||||
if (controller.canRedactSelectedEvents)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ Future<void> _loginFuture({
|
|||
identifier = AuthenticationUserIdentifier(user: username);
|
||||
}
|
||||
|
||||
final client = matrix.getLoginClient();
|
||||
final client = await matrix.getLoginClient();
|
||||
|
||||
final redirect = client.onLoginStateChanged.stream
|
||||
.where((state) => state == LoginState.loggedIn)
|
||||
|
|
|
|||
|
|
@ -1,153 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pages/login/login.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../../widgets/matrix.dart';
|
||||
|
||||
extension PangeaPasswordForgotten on LoginController {
|
||||
void pangeaPasswordForgotten() async {
|
||||
final TextEditingController emailController = TextEditingController();
|
||||
final TextEditingController newPasswordController = TextEditingController();
|
||||
showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (BuildContext context) => Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: AlertDialog(
|
||||
title: Text(L10n.of(context).passwordForgotten),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(L10n.of(context).enterAnEmailAddress),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: emailController,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).enterAnEmailAddress,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(L10n.of(context).cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(L10n.of(context).ok),
|
||||
onPressed: () async {
|
||||
if (emailController.text == "") return;
|
||||
final clientSecret =
|
||||
DateTime.now().millisecondsSinceEpoch.toString();
|
||||
final response = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => Matrix.of(context)
|
||||
.getLoginClient()
|
||||
.requestTokenToResetPasswordEmail(
|
||||
clientSecret,
|
||||
emailController.text,
|
||||
LoginController.sendAttempt++,
|
||||
),
|
||||
);
|
||||
if (response.error != null) {
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (BuildContext context) => Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: AlertDialog(
|
||||
title: Text(L10n.of(context).passwordForgotten),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(L10n.of(context).chooseAStrongPassword),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
obscureText: true,
|
||||
controller: newPasswordController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "******",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(L10n.of(context).cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(L10n.of(context).ok),
|
||||
onPressed: () async {
|
||||
if (newPasswordController.text == "") return;
|
||||
final ok = await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context).weSentYouAnEmail,
|
||||
// #Pangea
|
||||
// message: L10n.of(context).pleaseClickOnLink,
|
||||
message: L10n.of(context).clickOnEmailLink,
|
||||
// Pangea#
|
||||
okLabel: L10n.of(context).iHaveClickedOnLink,
|
||||
);
|
||||
if (ok != OkCancelResult.ok) return;
|
||||
final data = <String, dynamic>{
|
||||
'new_password': newPasswordController.text,
|
||||
'logout_devices': false,
|
||||
"auth": AuthenticationThreePidCreds(
|
||||
type: AuthenticationTypes.emailIdentity,
|
||||
threepidCreds: ThreepidCreds(
|
||||
sid: response.result!.sid,
|
||||
clientSecret: clientSecret,
|
||||
),
|
||||
).toJson(),
|
||||
};
|
||||
final success = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () =>
|
||||
Matrix.of(context).getLoginClient().request(
|
||||
RequestType.POST,
|
||||
'/client/r0/account/password',
|
||||
data: data,
|
||||
),
|
||||
);
|
||||
if (success.error == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
L10n.of(context).passwordHasBeenChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
usernameController.text = emailController.text;
|
||||
passwordController.text =
|
||||
newPasswordController.text;
|
||||
login();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ extension ClientDownloadContentExtension on Client {
|
|||
)
|
||||
: mxc;
|
||||
|
||||
final cachedData = await database?.getFile(cacheKey);
|
||||
final cachedData = await database.getFile(cacheKey);
|
||||
if (cachedData != null) return cachedData;
|
||||
|
||||
final httpUri = isThumbnail
|
||||
|
|
@ -55,7 +55,7 @@ extension ClientDownloadContentExtension on Client {
|
|||
}
|
||||
}
|
||||
|
||||
await database?.storeFile(cacheKey, imageData, 0);
|
||||
await database.storeFile(cacheKey, imageData, 0);
|
||||
|
||||
return imageData;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:desktop_notifications/desktop_notifications.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod;
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:matrix/encryption/utils/key_verification.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
|
@ -47,7 +48,7 @@ abstract class ClientManager {
|
|||
await store.setStringList(clientNamespace, clientNames.toList());
|
||||
}
|
||||
final clients =
|
||||
clientNames.map((name) => createClient(name, store)).toList();
|
||||
await Future.wait(clientNames.map((name) => createClient(name, store)));
|
||||
if (initialize) {
|
||||
await Future.wait(
|
||||
clients.map(
|
||||
|
|
@ -101,9 +102,15 @@ abstract class ClientManager {
|
|||
|
||||
static NativeImplementations get nativeImplementations => kIsWeb
|
||||
? const NativeImplementationsDummy()
|
||||
: NativeImplementationsIsolate(compute);
|
||||
: NativeImplementationsIsolate(
|
||||
compute,
|
||||
vodozemacInit: () => vod.init(wasmPath: './assets/assets/vodozemac/'),
|
||||
);
|
||||
|
||||
static Client createClient(String clientName, SharedPreferences store) {
|
||||
static Future<Client> createClient(
|
||||
String clientName,
|
||||
SharedPreferences store,
|
||||
) async {
|
||||
final shareKeysWith = AppSettings.shareKeysWith.getItem(store);
|
||||
final enableSoftLogout = AppSettings.enableSoftLogout.getItem(store);
|
||||
|
||||
|
|
@ -133,7 +140,7 @@ abstract class ClientManager {
|
|||
// Pangea#
|
||||
},
|
||||
logLevel: kReleaseMode ? Level.warning : Level.verbose,
|
||||
databaseBuilder: flutterMatrixSdkDatabaseBuilder,
|
||||
database: await flutterMatrixSdkDatabaseBuilder(clientName),
|
||||
supportedLoginTypes: {
|
||||
AuthenticationTypes.password,
|
||||
AuthenticationTypes.sso,
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ extension LocalizedBody on Event {
|
|||
|
||||
bool get isAttachmentSmallEnough =>
|
||||
infoMap['size'] is int &&
|
||||
infoMap['size'] < room.client.database!.maxFileSize;
|
||||
infoMap['size'] < room.client.database.maxFileSize;
|
||||
|
||||
bool get isThumbnailSmallEnough =>
|
||||
thumbnailInfoMap['size'] is int &&
|
||||
thumbnailInfoMap['size'] < room.client.database!.maxFileSize;
|
||||
thumbnailInfoMap['size'] < room.client.database.maxFileSize;
|
||||
|
||||
bool get showThumbnail =>
|
||||
[MessageTypes.Image, MessageTypes.Sticker, MessageTypes.Video]
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ import 'cipher.dart';
|
|||
import 'sqlcipher_stub.dart'
|
||||
if (dart.library.io) 'package:sqlcipher_flutter_libs/sqlcipher_flutter_libs.dart';
|
||||
|
||||
Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async {
|
||||
Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(String clientName) async {
|
||||
MatrixSdkDatabase? database;
|
||||
try {
|
||||
database = await _constructDatabase(client);
|
||||
database = await _constructDatabase(clientName);
|
||||
await database.open();
|
||||
return database;
|
||||
} catch (e, s) {
|
||||
|
|
@ -27,7 +27,7 @@ Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async {
|
|||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {"clientID": client.id},
|
||||
data: {"clientID": clientName},
|
||||
m: "Failed to open matrix sdk database. Openning fallback database.",
|
||||
);
|
||||
// Pangea#
|
||||
|
|
@ -53,7 +53,7 @@ Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async {
|
|||
|
||||
// Delete database file:
|
||||
if (database == null && !kIsWeb) {
|
||||
final dbFile = File(await _getDatabasePath(client.clientName));
|
||||
final dbFile = File(await _getDatabasePath(clientName));
|
||||
if (await dbFile.exists()) await dbFile.delete();
|
||||
}
|
||||
|
||||
|
|
@ -77,10 +77,10 @@ Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async {
|
|||
}
|
||||
}
|
||||
|
||||
Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
|
||||
Future<MatrixSdkDatabase> _constructDatabase(String clientName) async {
|
||||
if (kIsWeb) {
|
||||
html.window.navigator.storage?.persist();
|
||||
return MatrixSdkDatabase(client.clientName);
|
||||
return await MatrixSdkDatabase.init(clientName);
|
||||
}
|
||||
|
||||
final cipher = await getDatabaseCipher();
|
||||
|
|
@ -97,7 +97,7 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
|
|||
);
|
||||
}
|
||||
|
||||
final path = await _getDatabasePath(client.clientName);
|
||||
final path = await _getDatabasePath(clientName);
|
||||
|
||||
// fix dlopen for old Android
|
||||
await applyWorkaroundToOpenSqlCipherOnOldAndroidVersions();
|
||||
|
|
@ -109,7 +109,7 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
|
|||
// Pangea#
|
||||
|
||||
// migrate from potential previous SQLite database path to current one
|
||||
await _migrateLegacyLocation(path, client.clientName);
|
||||
await _migrateLegacyLocation(path, clientName);
|
||||
|
||||
// required for [getDatabasesPath]
|
||||
databaseFactory = factory;
|
||||
|
|
@ -139,8 +139,8 @@ Future<MatrixSdkDatabase> _constructDatabase(Client client) async {
|
|||
),
|
||||
);
|
||||
|
||||
return MatrixSdkDatabase(
|
||||
client.clientName,
|
||||
return await MatrixSdkDatabase.init(
|
||||
clientName,
|
||||
database: database,
|
||||
maxFileSize: 1000 * 1000 * 10,
|
||||
fileStorageLocation: fileStorageLocation?.uri,
|
||||
|
|
|
|||