Merge branch 'main' of https://github.com/krille-chan/fluffychat
This commit is contained in:
commit
e12723fdaa
98 changed files with 1450 additions and 1014 deletions
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
|
@ -1,5 +1,3 @@
|
||||||
*Thank you so much for your contribution to FluffyChat ❤️❤️❤️*
|
|
||||||
|
|
||||||
- [ ] I have read and understood the [contributing guidelines](https://github.com/krille-chan/fluffychat/blob/main/CONTRIBUTING.md).
|
- [ ] I have read and understood the [contributing guidelines](https://github.com/krille-chan/fluffychat/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
### Pull Request has been tested on:
|
### Pull Request has been tested on:
|
||||||
|
|
|
||||||
5
.github/workflows/integrate.yaml
vendored
5
.github/workflows/integrate.yaml
vendored
|
|
@ -128,10 +128,7 @@ jobs:
|
||||||
run: sudo xcode-select --switch /Applications/Xcode_16.4.app
|
run: sudo xcode-select --switch /Applications/Xcode_16.4.app
|
||||||
- run: brew install sqlcipher
|
- run: brew install sqlcipher
|
||||||
- uses: moonrepo/setup-rust@v1
|
- uses: moonrepo/setup-rust@v1
|
||||||
- name: Add Firebase Messaging
|
- run: ./scripts/add-firebase-messaging.sh
|
||||||
run: |
|
|
||||||
flutter pub add fcm_shared_isolate:0.1.0
|
|
||||||
sed -i '' 's,//<GOOGLE_SERVICES>,,g' lib/utils/background_push.dart
|
|
||||||
- run: flutter pub get
|
- run: flutter pub get
|
||||||
- run: flutter build ios --no-codesign
|
- run: flutter build ios --no-codesign
|
||||||
|
|
||||||
|
|
|
||||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
|
|
@ -29,7 +29,7 @@ jobs:
|
||||||
run: ./scripts/prepare-web.sh
|
run: ./scripts/prepare-web.sh
|
||||||
- run: rm ./assets/vodozemac/.gitignore
|
- run: rm ./assets/vodozemac/.gitignore
|
||||||
- name: Build Release Web
|
- name: Build Release Web
|
||||||
run: flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --release --source-maps
|
run: flutter build web --dart-define=FLUTTER_WEB_CANVASKIT_URL=canvaskit/ --release --source-maps --base-href "/web/"
|
||||||
- name: Create archive
|
- name: Create archive
|
||||||
run: tar -czf fluffychat-web.tar.gz build/web/
|
run: tar -czf fluffychat-web.tar.gz build/web/
|
||||||
- name: Upload Web Build
|
- name: Upload Web Build
|
||||||
|
|
@ -49,8 +49,8 @@ jobs:
|
||||||
- name: Clone fluffychat website
|
- name: Clone fluffychat website
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/krille-chan/fluffychat-website.git
|
git clone https://github.com/krille-chan/fluffychat-website.git
|
||||||
cp CHANGELOG.md fluffychat-website/
|
cat CHANGELOG.md >> fluffychat-website/src/changelog.md
|
||||||
cp PRIVACY.md fluffychat-website/
|
cat PRIVACY.md >> fluffychat-website/src/privacy.md
|
||||||
- name: Build website
|
- name: Build website
|
||||||
working-directory: fluffychat-website
|
working-directory: fluffychat-website
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
environment:
|
environment:
|
||||||
flutter: 3.41.5 # Keep in sync with snap/snapcraft.yaml
|
flutter: 3.41.7 # Keep in sync with snap/snapcraft.yaml
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
## v2.5.1
|
||||||
|
Update to latest version of fcm_shared_isolate to fix push on iOS.
|
||||||
|
|
||||||
## v2.5.0
|
## v2.5.0
|
||||||
FluffyChat 2.5.0 introduces a new homeserver picker for onboarding, better image compression performance and several smaller new features, design adjustments and bug fixes.
|
FluffyChat 2.5.0 introduces a new homeserver picker for onboarding, better image compression performance and several smaller new features, design adjustments and bug fixes.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ analyzer:
|
||||||
- dart_code_linter
|
- dart_code_linter
|
||||||
errors:
|
errors:
|
||||||
todo: ignore
|
todo: ignore
|
||||||
use_build_context_synchronously: ignore
|
|
||||||
exclude:
|
exclude:
|
||||||
- lib/l10n/*.dart
|
- lib/l10n/*.dart
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"audioRecordingSamplingRate": 44100,
|
"audioRecordingSamplingRate": 44100,
|
||||||
"renderHtml": true,
|
"renderHtml": true,
|
||||||
"fontSizeFactor": 1,
|
"fontSizeFactor": 1,
|
||||||
|
"messagePreviewMaxLines": 128,
|
||||||
"hideRedactedEvents": false,
|
"hideRedactedEvents": false,
|
||||||
"hideUnknownEvents": true,
|
"hideUnknownEvents": true,
|
||||||
"separateChatTypes": false,
|
"separateChatTypes": false,
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ GEM
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.3)
|
CFPropertyList (3.0.3)
|
||||||
abbrev (0.1.2)
|
abbrev (0.1.2)
|
||||||
addressable (2.8.4)
|
addressable (2.9.0)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 8.0)
|
||||||
artifactory (3.0.15)
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.4.0)
|
aws-eventstream (1.4.0)
|
||||||
|
|
@ -168,7 +168,7 @@ GEM
|
||||||
os (1.1.1)
|
os (1.1.1)
|
||||||
ostruct (0.6.3)
|
ostruct (0.6.3)
|
||||||
plist (3.6.0)
|
plist (3.6.0)
|
||||||
public_suffix (5.0.3)
|
public_suffix (7.0.5)
|
||||||
rake (13.0.3)
|
rake (13.0.3)
|
||||||
representable (3.1.1)
|
representable (3.1.1)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
|
|
|
||||||
|
|
@ -12,5 +12,7 @@ import Flutter
|
||||||
|
|
||||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
}
|
|
||||||
|
// From https://pub.dev/packages/flutter_local_notifications#-ios-setup
|
||||||
|
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,8 +172,8 @@ abstract class AppRoutes {
|
||||||
context,
|
context,
|
||||||
state,
|
state,
|
||||||
NewPrivateChat(
|
NewPrivateChat(
|
||||||
key: ValueKey('new_chat_${state.uri.query}'),
|
key: ValueKey('new_chat_${state.uri.fragment}'),
|
||||||
deeplink: state.uri.queryParameters['deeplink'],
|
deeplink: state.uri.fragment,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
redirect: loggedOutRedirect,
|
redirect: loggedOutRedirect,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
enum AppSettings<T> {
|
enum AppSettings<T> {
|
||||||
textMessageMaxLength<int>('textMessageMaxLength', 16384),
|
textMessageMaxLength<int>('textMessageMaxLength', 16384),
|
||||||
|
|
||||||
|
/// Max lines for unselected HTML/text bubbles; 0 = unlimited (no fade).
|
||||||
|
messagePreviewMaxLines<int>('chat.fluffy.message_preview_max_lines', 128),
|
||||||
audioRecordingNumChannels<int>('audioRecordingNumChannels', 1),
|
audioRecordingNumChannels<int>('audioRecordingNumChannels', 1),
|
||||||
audioRecordingAutoGain<bool>('audioRecordingAutoGain', true),
|
audioRecordingAutoGain<bool>('audioRecordingAutoGain', true),
|
||||||
audioRecordingEchoCancel<bool>('audioRecordingEchoCancel', false),
|
audioRecordingEchoCancel<bool>('audioRecordingEchoCancel', false),
|
||||||
|
|
@ -68,7 +71,9 @@ enum AppSettings<T> {
|
||||||
tos<String>('chat.fluffy.tos_url', 'https://fluffychat.im/en/tos'),
|
tos<String>('chat.fluffy.tos_url', 'https://fluffychat.im/en/tos'),
|
||||||
sendTimelineEventTimeout<int>('chat.fluffy.send_timeline_event_timeout', 15),
|
sendTimelineEventTimeout<int>('chat.fluffy.send_timeline_event_timeout', 15),
|
||||||
lastSeenSupportBanner<int>('chat.fluffy.last_seen_support_banner', 0),
|
lastSeenSupportBanner<int>('chat.fluffy.last_seen_support_banner', 0),
|
||||||
supportBannerOptOut<bool>('chat.fluffy.support_banner_opt_out', false);
|
supportBannerOptOut<bool>('chat.fluffy.support_banner_opt_out', false),
|
||||||
|
webNotificationSound<bool>('chat.fluffy.web_notification_sound', true),
|
||||||
|
chatFilter<String>('chat.fluffy.chat_filter', 'allChats');
|
||||||
|
|
||||||
final String key;
|
final String key;
|
||||||
final T defaultValue;
|
final T defaultValue;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -72,7 +72,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"areGuestsAllowedToJoin": "Es pot entrar al xat com a convidadi",
|
"areGuestsAllowedToJoin": "Es pot entrar al xat com a convidadi?",
|
||||||
"@areGuestsAllowedToJoin": {
|
"@areGuestsAllowedToJoin": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -1781,7 +1781,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"description": "Usage hint for the command /clearcache"
|
"description": "Usage hint for the command /clearcache"
|
||||||
},
|
},
|
||||||
"commandHint_join": "Uneix-te a la sala",
|
"commandHint_join": "Uneix-te a la sala indicada",
|
||||||
"@commandHint_join": {
|
"@commandHint_join": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"description": "Usage hint for the command /join"
|
"description": "Usage hint for the command /join"
|
||||||
|
|
@ -2754,7 +2754,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"federationBaseUrl": "URL base de federació",
|
"federationBaseUrl": "URL base de federació",
|
||||||
"clientWellKnownInformation": "Informació de",
|
"clientWellKnownInformation": "Informació coneguda del client:",
|
||||||
"baseUrl": "URL base",
|
"baseUrl": "URL base",
|
||||||
"identityServer": "Servidor d'identitats:",
|
"identityServer": "Servidor d'identitats:",
|
||||||
"versionWithNumber": "Versió: {version}",
|
"versionWithNumber": "Versió: {version}",
|
||||||
|
|
@ -2774,5 +2774,39 @@
|
||||||
"signUpGreeting": "El FluffyChat és descentralitzat! Tria un servidor on vulguis crear-t'hi un compte, i som-hi!",
|
"signUpGreeting": "El FluffyChat és descentralitzat! Tria un servidor on vulguis crear-t'hi un compte, i som-hi!",
|
||||||
"signInGreeting": "Si ja tens un compte a Matrix, benvingudi! Tria el teu servidor i inicia-hi sessió.",
|
"signInGreeting": "Si ja tens un compte a Matrix, benvingudi! Tria el teu servidor i inicia-hi sessió.",
|
||||||
"appIntro": "Pots xatejar amb lis tevis amiguis amb Fluffychat. És una app de missatgeria [matrix] descentralitzada! Llegeix-ne més a https://matrix.org si vols, o inicia sessió.",
|
"appIntro": "Pots xatejar amb lis tevis amiguis amb Fluffychat. És una app de missatgeria [matrix] descentralitzada! Llegeix-ne més a https://matrix.org si vols, o inicia sessió.",
|
||||||
"theProcessWasCanceled": "S'ha canceŀlat el procés."
|
"theProcessWasCanceled": "S'ha canceŀlat el procés.",
|
||||||
}
|
"join": "Entra",
|
||||||
|
"searchOrEnterHomeserverAddress": "Cerca o introdueix l'adreça del teu servidor",
|
||||||
|
"matrixId": "ID de Matrix",
|
||||||
|
"setPowerLevel": "Concedeix permisos",
|
||||||
|
"makeModerator": "Fes moderadori",
|
||||||
|
"makeAdmin": "Fes admin",
|
||||||
|
"removeModeratorRights": "Treu els drets de moderadori",
|
||||||
|
"removeAdminRights": "Treu els drets d'admin",
|
||||||
|
"powerLevel": "Nivell de permisos",
|
||||||
|
"setPowerLevelDescription": "Els nivells de permisos defineixen què pot fer uni membre d'aquesta sala, i es defineix per un número entre 0 i 100.",
|
||||||
|
"owner": "Propietàriï",
|
||||||
|
"mute": "Silencia",
|
||||||
|
"@mute": {
|
||||||
|
"description": "This should be a very short string because there is not much space in the button!"
|
||||||
|
},
|
||||||
|
"createNewChat": "Crea un nou xat",
|
||||||
|
"reset": "Reseteja",
|
||||||
|
"supportFluffyChat": "Dona suport a FluffyChat",
|
||||||
|
"support": "Aporta",
|
||||||
|
"fluffyChatSupportBannerMessage": "El FluffyChat necessita la teva ajuda!\n❤️❤️❤️\nFluffyChat serà sempre gratuït, però el seu desenvolupament i allotjament costa diners.\nEl futur del projecte depèn del suport de persones com tu.",
|
||||||
|
"skipSupportingFluffyChat": "Ignora el suport a FluffyChat",
|
||||||
|
"iDoNotWantToSupport": "No vull donar suport",
|
||||||
|
"iAlreadySupportFluffyChat": "Ja estic donant-hi suport",
|
||||||
|
"setLowPriority": "Estableix una prioritat baixa",
|
||||||
|
"unsetLowPriority": "Restableix la prioritat",
|
||||||
|
"removeCallFromChat": "Treu la trucada del xat",
|
||||||
|
"removeCallFromChatDescription": "Vols treure la trucada del xat per a totis lis membres?",
|
||||||
|
"removeCallForEveryone": "Treu la trucada per tothom",
|
||||||
|
"startVoiceCall": "Inicia una trucada",
|
||||||
|
"startVideoCall": "Fes una videotrucada",
|
||||||
|
"joinVoiceCall": "Fica't a la trucada",
|
||||||
|
"joinVideoCall": "Fica't a la videotrucada",
|
||||||
|
"live": "En directe",
|
||||||
|
"playSoundOnNotification": "Notificacions sonores"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2802,5 +2802,10 @@
|
||||||
"startVideoCall": "Start video call",
|
"startVideoCall": "Start video call",
|
||||||
"joinVoiceCall": "Join voice call",
|
"joinVoiceCall": "Join voice call",
|
||||||
"joinVideoCall": "Join video call",
|
"joinVideoCall": "Join video call",
|
||||||
"live": "Live"
|
"live": "Live",
|
||||||
|
"playSoundOnNotification": "Play sound on notification",
|
||||||
|
"addTag": "Add tag",
|
||||||
|
"removeTag": "Remove tag",
|
||||||
|
"tagName": "Tag name",
|
||||||
|
"createNewTag": "Create new tag"
|
||||||
}
|
}
|
||||||
|
|
@ -1709,7 +1709,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"oopsPushError": "¡UPS¡ Desafortunadamente, se produjo un error al configurar las notificaciones push.",
|
"oopsPushError": "¡Ups! Desafortunadamente, se produjo un error al configurar las notificaciones push.",
|
||||||
"@oopsPushError": {
|
"@oopsPushError": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -1867,7 +1867,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"pleaseEnterYourPin": "Por favor ingrese su PIN",
|
"pleaseEnterYourPin": "Por favor ingrese su pin",
|
||||||
"@pleaseEnterYourPin": {
|
"@pleaseEnterYourPin": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -2576,7 +2576,7 @@
|
||||||
"contactServerAdmin": "Contactar con el administrador del servidor",
|
"contactServerAdmin": "Contactar con el administrador del servidor",
|
||||||
"contactServerSecurity": "Contactar con seguridad del servidor",
|
"contactServerSecurity": "Contactar con seguridad del servidor",
|
||||||
"supportPage": "Página de atención",
|
"supportPage": "Página de atención",
|
||||||
"invalidUrl": "URL incorrecta",
|
"invalidUrl": "Url incorrecto",
|
||||||
"addLink": "Añadir enlace",
|
"addLink": "Añadir enlace",
|
||||||
"unableToJoinChat": "No se puede entrar al chat. Puede que la otra parte ya haya cerrado la conversación.",
|
"unableToJoinChat": "No se puede entrar al chat. Puede que la otra parte ya haya cerrado la conversación.",
|
||||||
"waitingForServer": "Esperando al servidor...",
|
"waitingForServer": "Esperando al servidor...",
|
||||||
|
|
@ -2718,7 +2718,7 @@
|
||||||
"addAnswerOption": "Añadir respuesta",
|
"addAnswerOption": "Añadir respuesta",
|
||||||
"allowMultipleAnswers": "Permitir varias respuestas",
|
"allowMultipleAnswers": "Permitir varias respuestas",
|
||||||
"pollHasBeenEnded": "La encuesta ha terminado",
|
"pollHasBeenEnded": "La encuesta ha terminado",
|
||||||
"countVotes": "{count, plural, =1{One vote} other{{count} votes}}",
|
"countVotes": "{count, plural, =1{Un voto} other{{count} votos}}",
|
||||||
"@countVotes": {
|
"@countVotes": {
|
||||||
"type": "int",
|
"type": "int",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
|
@ -2786,5 +2786,40 @@
|
||||||
"theProcessWasCanceled": "El proceso se ha cancelado.",
|
"theProcessWasCanceled": "El proceso se ha cancelado.",
|
||||||
"join": "Unirse",
|
"join": "Unirse",
|
||||||
"searchOrEnterHomeserverAddress": "Buscar o pon la dirección de tu servidor local",
|
"searchOrEnterHomeserverAddress": "Buscar o pon la dirección de tu servidor local",
|
||||||
"matrixId": "Matrix ID"
|
"matrixId": "Matrix ID",
|
||||||
}
|
"setPowerLevel": "Establecer nivel de poder",
|
||||||
|
"makeModerator": "Convertir en Moderador",
|
||||||
|
"makeAdmin": "Convertir en administrador",
|
||||||
|
"removeModeratorRights": "Remover derechos de moderador",
|
||||||
|
"removeAdminRights": "Remover derechos de administrador",
|
||||||
|
"powerLevel": "Nivel de Poder",
|
||||||
|
"setPowerLevelDescription": "El nivel de poder define el nivel de acciones de un miembro, usualmente esta en el rango entre 0 a 100.",
|
||||||
|
"owner": "Dueño",
|
||||||
|
"mute": "Silenciar",
|
||||||
|
"@mute": {
|
||||||
|
"description": "This should be a very short string because there is not much space in the button!"
|
||||||
|
},
|
||||||
|
"createNewChat": "Crear nuevo chat",
|
||||||
|
"reset": "Resetear",
|
||||||
|
"supportFluffyChat": "Apoyar FluffyChat",
|
||||||
|
"support": "Apoyar",
|
||||||
|
"fluffyChatSupportBannerMessage": "FluffyChat necesita TU ayuda!\n❤️❤️❤️\nFluffyChat siempre sera gratis, pero el desarrollo y mantenimiento cuesta dinero.\nEl futuro del proyecto depende del apoyo de personas como tu.",
|
||||||
|
"skipSupportingFluffyChat": "Omitir apoyo a FluffyChat",
|
||||||
|
"iDoNotWantToSupport": "No quiero apoyar",
|
||||||
|
"iAlreadySupportFluffyChat": "Ya apoyo FluffyChat",
|
||||||
|
"setLowPriority": "Colocar baja prioridad",
|
||||||
|
"unsetLowPriority": "Desactivar baja prioridad",
|
||||||
|
"removeCallFromChat": "Remover llamadas del chat",
|
||||||
|
"removeCallFromChatDescription": "Deseas remover la llamada del chat para todos los miembros?",
|
||||||
|
"removeCallForEveryone": "Remover llamadas para todos",
|
||||||
|
"startVoiceCall": "Iniciar llamada",
|
||||||
|
"startVideoCall": "Iniciar videollamada",
|
||||||
|
"joinVoiceCall": "Ingresar a llamada",
|
||||||
|
"joinVideoCall": "Ingresar a videollamada",
|
||||||
|
"live": "En Vivo",
|
||||||
|
"playSoundOnNotification": "Sonido en notificación",
|
||||||
|
"addTag": "Agregar etiqueta",
|
||||||
|
"removeTag": "Remover etiqueta",
|
||||||
|
"tagName": "Nombre de etiqueta",
|
||||||
|
"createNewTag": "Crear nueva etiqueta"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2808,5 +2808,10 @@
|
||||||
"startVideoCall": "Algata videokõne",
|
"startVideoCall": "Algata videokõne",
|
||||||
"joinVoiceCall": "Liitu häälkõnega",
|
"joinVoiceCall": "Liitu häälkõnega",
|
||||||
"joinVideoCall": "Liitu videokõnega",
|
"joinVideoCall": "Liitu videokõnega",
|
||||||
"live": "Reaalajas"
|
"live": "Reaalajas",
|
||||||
|
"playSoundOnNotification": "Lisa teavitusele helimärguanne",
|
||||||
|
"addTag": "Lisa silt",
|
||||||
|
"removeTag": "Eemalda silt",
|
||||||
|
"tagName": "Sildi nimi",
|
||||||
|
"createNewTag": "Lisa uus silt"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2792,5 +2792,22 @@
|
||||||
"description": "This should be a very short string because there is not much space in the button!"
|
"description": "This should be a very short string because there is not much space in the button!"
|
||||||
},
|
},
|
||||||
"createNewChat": "Sortu txat berria",
|
"createNewChat": "Sortu txat berria",
|
||||||
"reset": "Berrezarri"
|
"reset": "Berrezarri",
|
||||||
}
|
"supportFluffyChat": "Eman babesa FluffyChat-i",
|
||||||
|
"support": "Lagundu",
|
||||||
|
"fluffyChatSupportBannerMessage": "FluffyChat-ek ZURE laguntza behar du!\n❤️❤️❤️\nFluffyChat beti izango da doakoa, baina garapenak eta ostatatzeak dirua eskatzen du.\nProiektuaren etorkizuna zu bezalako pertsonen babesaren menpe dago.",
|
||||||
|
"iDoNotWantToSupport": "Ez diot babesik eman nahi",
|
||||||
|
"iAlreadySupportFluffyChat": "Laguntzen ari naiz dagoeneko FluffyChat",
|
||||||
|
"setLowPriority": "Ezarri lehentasun baxua",
|
||||||
|
"unsetLowPriority": "Kendu lehentasun baxua",
|
||||||
|
"removeCallFromChat": "Kendu deia txatetik",
|
||||||
|
"removeCallFromChatDescription": "Kide guztientzat kendu nahi al duzu deia txatetik?",
|
||||||
|
"removeCallForEveryone": "Kendu deia guztientzat",
|
||||||
|
"startVoiceCall": "Hasi ahots-deia",
|
||||||
|
"startVideoCall": "Hasi bideo-deia",
|
||||||
|
"joinVoiceCall": "Batu ahots-deira",
|
||||||
|
"joinVideoCall": "Batu bideo-deira",
|
||||||
|
"live": "Zuzenean",
|
||||||
|
"playSoundOnNotification": "Jo soinua jakinarazpenekin",
|
||||||
|
"skipSupportingFluffyChat": "Muzin egin FluffyChat-en laguntza eskaerari"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2806,5 +2806,18 @@
|
||||||
"iDoNotWantToSupport": "Nílim ag iarraidh tacú leis",
|
"iDoNotWantToSupport": "Nílim ag iarraidh tacú leis",
|
||||||
"iAlreadySupportFluffyChat": "Tacaím le FluffyChat cheana féin",
|
"iAlreadySupportFluffyChat": "Tacaím le FluffyChat cheana féin",
|
||||||
"setLowPriority": "Socraigh tosaíocht íseal",
|
"setLowPriority": "Socraigh tosaíocht íseal",
|
||||||
"unsetLowPriority": "Díshuiteáil tosaíocht íseal"
|
"unsetLowPriority": "Díshuiteáil tosaíocht íseal",
|
||||||
}
|
"removeCallFromChat": "Bain glao den chomhrá",
|
||||||
|
"removeCallFromChatDescription": "Ar mhaith leat an glao a bhaint den chomhrá do gach ball?",
|
||||||
|
"removeCallForEveryone": "Bain glao do gach duine",
|
||||||
|
"startVoiceCall": "Tosaigh glao gutha",
|
||||||
|
"startVideoCall": "Tosaigh glao físe",
|
||||||
|
"joinVoiceCall": "Glac páirt i nglao gutha",
|
||||||
|
"joinVideoCall": "Glac páirt i nglao físe",
|
||||||
|
"live": "Beo",
|
||||||
|
"playSoundOnNotification": "Seinn fuaim ar fhógra",
|
||||||
|
"addTag": "Cuir clib leis",
|
||||||
|
"removeTag": "Bain an chlib",
|
||||||
|
"tagName": "Ainm an chlib",
|
||||||
|
"createNewTag": "Cruthaigh clib nua"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2808,5 +2808,10 @@
|
||||||
"startVideoCall": "Iniciar chamada de vídeo",
|
"startVideoCall": "Iniciar chamada de vídeo",
|
||||||
"joinVoiceCall": "Unirse á chamada de voz",
|
"joinVoiceCall": "Unirse á chamada de voz",
|
||||||
"joinVideoCall": "Unirse á chamada de vídeo",
|
"joinVideoCall": "Unirse á chamada de vídeo",
|
||||||
"live": "En directo"
|
"live": "En directo",
|
||||||
|
"playSoundOnNotification": "Reproducir son coas notificacións",
|
||||||
|
"addTag": "Engadir etiqueta",
|
||||||
|
"removeTag": "Retirar etiqueta",
|
||||||
|
"tagName": "Nome da etiqueta",
|
||||||
|
"createNewTag": "Crear nova etiqueta"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"allSpaces": "Visas vietas",
|
"allSpaces": "Visas kopienas",
|
||||||
"supposedMxid": "Tam būtu jābūt {mxid}",
|
"supposedMxid": "Tam būtu jābūt {mxid}",
|
||||||
"@supposedMxid": {
|
"@supposedMxid": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
|
|
@ -385,7 +385,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tryAgain": "Jāmēģina vēlreiz",
|
"tryAgain": "Jāmēģina vēlreiz",
|
||||||
"areGuestsAllowedToJoin": "Vai vieslietotājiem ir ļauts pievienoties",
|
"areGuestsAllowedToJoin": "Vai vieslietotāji drīkst pievienoties?",
|
||||||
"@areGuestsAllowedToJoin": {
|
"@areGuestsAllowedToJoin": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -458,7 +458,7 @@
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"link": "Saite",
|
"link": "Saite",
|
||||||
"newSpaceDescription": "Vietas ļauj apvienot tērzēšanas un būvēt privātas vai publiskas kopienas.",
|
"newSpaceDescription": "Kopienas ļauj apvienot tērzēšanas un būvēt privātas vai publiskas cilvēku grupas, kurus vieno kaut kas kopīgs, piemēram, zinātne, matemātika, valoda, reliģija, ķīmija, medicīna, kosmoss, datori, ceļošana, grāmatu lasīšana, kriptovalūta, kiberdrošība, aparātprogrammatūra.",
|
||||||
"chatDescription": "Tērzēšanas apraksts",
|
"chatDescription": "Tērzēšanas apraksts",
|
||||||
"next": "Nākamais",
|
"next": "Nākamais",
|
||||||
"@next": {
|
"@next": {
|
||||||
|
|
@ -547,7 +547,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"spaceIsPublic": "Vieta ir publiska",
|
"spaceIsPublic": "Kopiena ir publiska",
|
||||||
"@spaceIsPublic": {
|
"@spaceIsPublic": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -790,7 +790,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"spaceName": "Vietas nosaukums",
|
"spaceName": "Kopienas nosaukums",
|
||||||
"@spaceName": {
|
"@spaceName": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -873,7 +873,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"logInTo": "PIeteikties {homeserver}",
|
"logInTo": "Pieteikties {homeserver}",
|
||||||
"@logInTo": {
|
"@logInTo": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
|
@ -1195,7 +1195,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"emoteExists": "Emocija jau pastāv.",
|
"emoteExists": "Emocija jau pastāv!",
|
||||||
"@emoteExists": {
|
"@emoteExists": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -1351,7 +1351,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"addToSpace": "Pievienot vietai",
|
"addToSpace": "Pievienot kopienai",
|
||||||
"unbanFromChat": "Atcelt liegumu tērzēšanā",
|
"unbanFromChat": "Atcelt liegumu tērzēšanā",
|
||||||
"@unbanFromChat": {
|
"@unbanFromChat": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
|
|
@ -1646,7 +1646,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"createNewSpace": "Jauna vieta",
|
"createNewSpace": "Jauna kopiena",
|
||||||
"@createNewSpace": {
|
"@createNewSpace": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -1725,7 +1725,7 @@
|
||||||
},
|
},
|
||||||
"newGroup": "Jauna kopa",
|
"newGroup": "Jauna kopa",
|
||||||
"bundleName": "Komplekta nosaukums",
|
"bundleName": "Komplekta nosaukums",
|
||||||
"removeFromSpace": "Noņemt no vietas",
|
"removeFromSpace": "Noņemt no kopienas",
|
||||||
"dateAndTimeOfDay": "{date}, {timeOfDay}",
|
"dateAndTimeOfDay": "{date}, {timeOfDay}",
|
||||||
"@dateAndTimeOfDay": {
|
"@dateAndTimeOfDay": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
|
|
@ -2060,7 +2060,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"newSpace": "Jauna vieta",
|
"newSpace": "Jauna kopiena",
|
||||||
"changePassword": "Nomainīt paroli",
|
"changePassword": "Nomainīt paroli",
|
||||||
"@changePassword": {
|
"@changePassword": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
|
|
@ -2144,7 +2144,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"pin": "PIN",
|
"pin": "Piespraust",
|
||||||
"@pin": {
|
"@pin": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -2203,8 +2203,8 @@
|
||||||
"transparent": "Caurspīdīgs",
|
"transparent": "Caurspīdīgs",
|
||||||
"searchForUsers": "Meklēt @lietotājus...",
|
"searchForUsers": "Meklēt @lietotājus...",
|
||||||
"pleaseEnterYourCurrentPassword": "Lūgums ievadīt savu pašreizējo paroli",
|
"pleaseEnterYourCurrentPassword": "Lūgums ievadīt savu pašreizējo paroli",
|
||||||
"publicSpaces": "Publiskas vietas",
|
"publicSpaces": "Publiskas kopienas",
|
||||||
"joinSpace": "Pievienoties vietai",
|
"joinSpace": "Pievienoties kopienai",
|
||||||
"createGroupAndInviteUsers": "Izveidot kopu un uzaicināt lietotājus",
|
"createGroupAndInviteUsers": "Izveidot kopu un uzaicināt lietotājus",
|
||||||
"groupCanBeFoundViaSearch": "Kopu var atrast meklēšanā",
|
"groupCanBeFoundViaSearch": "Kopu var atrast meklēšanā",
|
||||||
"commandHint_sendraw": "Nosūtīt neapstrādātu JSON",
|
"commandHint_sendraw": "Nosūtīt neapstrādātu JSON",
|
||||||
|
|
@ -2319,7 +2319,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"addChatOrSubSpace": "Pievienot tērzēšanu vai apakšvietu",
|
"addChatOrSubSpace": "Pievienot tērzēšanu vai apakškopienu",
|
||||||
"formattedMessagesDescription": "Attēlot bagātinātu ziņu saturu, piemēram, ar Markdown iezīmētu treknrakstu.",
|
"formattedMessagesDescription": "Attēlot bagātinātu ziņu saturu, piemēram, ar Markdown iezīmētu treknrakstu.",
|
||||||
"sessionLostBody": "Sesija ir zaudēta. Lūgums ziņot par šo kļūdu izstrādātājiem {url}. Kļūdas ziņojums ir: {error}",
|
"sessionLostBody": "Sesija ir zaudēta. Lūgums ziņot par šo kļūdu izstrādātājiem {url}. Kļūdas ziņojums ir: {error}",
|
||||||
"@sessionLostBody": {
|
"@sessionLostBody": {
|
||||||
|
|
@ -2410,7 +2410,7 @@
|
||||||
"changeTheCanonicalRoomAlias": "Mainīt tērzēšanas galveno publisko adresi",
|
"changeTheCanonicalRoomAlias": "Mainīt tērzēšanas galveno publisko adresi",
|
||||||
"sendRoomNotifications": "Sūtīt @istaba paziņojumus",
|
"sendRoomNotifications": "Sūtīt @istaba paziņojumus",
|
||||||
"changeTheDescriptionOfTheGroup": "Mainīt tērzēšanas aprakstu",
|
"changeTheDescriptionOfTheGroup": "Mainīt tērzēšanas aprakstu",
|
||||||
"alwaysUse24HourFormat": "nē",
|
"alwaysUse24HourFormat": "true",
|
||||||
"@alwaysUse24HourFormat": {
|
"@alwaysUse24HourFormat": {
|
||||||
"description": "Set to true to always display time of day in 24 hour format."
|
"description": "Set to true to always display time of day in 24 hour format."
|
||||||
},
|
},
|
||||||
|
|
@ -2423,7 +2423,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"goToSpace": "Doties uz vietu: {space}",
|
"goToSpace": "Doties uz kopienu: {space}",
|
||||||
"@goToSpace": {
|
"@goToSpace": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"space": {}
|
"space": {}
|
||||||
|
|
@ -2446,8 +2446,8 @@
|
||||||
"changelog": "Izmaiņu žurnāls",
|
"changelog": "Izmaiņu žurnāls",
|
||||||
"noMoreChatsFound": "Vairs netika atrasta neviena tērzēšana...",
|
"noMoreChatsFound": "Vairs netika atrasta neviena tērzēšana...",
|
||||||
"unread": "Nelasītas",
|
"unread": "Nelasītas",
|
||||||
"space": "Vieta",
|
"space": "Kopiena",
|
||||||
"spaces": "Vietas",
|
"spaces": "Kopienas",
|
||||||
"markAsUnread": "Atzīmēt kā nelasītu",
|
"markAsUnread": "Atzīmēt kā nelasītu",
|
||||||
"sendingAttachment": "Nosūta pielikumu...",
|
"sendingAttachment": "Nosūta pielikumu...",
|
||||||
"generatingVideoThumbnail": "Izveido video sīktēlu...",
|
"generatingVideoThumbnail": "Izveido video sīktēlu...",
|
||||||
|
|
@ -2562,7 +2562,7 @@
|
||||||
"more": "Vairāk",
|
"more": "Vairāk",
|
||||||
"roomNotificationSettings": "Istabu paziņojumu iestatījumi",
|
"roomNotificationSettings": "Istabu paziņojumu iestatījumi",
|
||||||
"notificationRuleEncrypted": "Šifrēts",
|
"notificationRuleEncrypted": "Šifrēts",
|
||||||
"notificationRuleJitsi": "Jitsi",
|
"notificationRuleJitsi": "Jitsi videozvani",
|
||||||
"notificationRuleIsUserMention": "Lietotāja pieminēšana",
|
"notificationRuleIsUserMention": "Lietotāja pieminēšana",
|
||||||
"notificationRuleIsRoomMentionDescription": "Paziņo lietotājam, kad tiek pieminēta istaba.",
|
"notificationRuleIsRoomMentionDescription": "Paziņo lietotājam, kad tiek pieminēta istaba.",
|
||||||
"notificationRuleMessageDescription": "Paziņo lietotājam par vispārējām ziņām.",
|
"notificationRuleMessageDescription": "Paziņo lietotājam par vispārējām ziņām.",
|
||||||
|
|
@ -2645,7 +2645,7 @@
|
||||||
"longPressToRecordVoiceMessage": "Ilga piespiešana, lai ierakstītu balss ziņu.",
|
"longPressToRecordVoiceMessage": "Ilga piespiešana, lai ierakstītu balss ziņu.",
|
||||||
"pause": "Apturēt",
|
"pause": "Apturēt",
|
||||||
"resume": "Atsākt",
|
"resume": "Atsākt",
|
||||||
"removeFromSpaceDescription": "Tērzēšana tiks noņemta no vietas, bet tā joprojām būs redzama tērzēšanu sarakstā.",
|
"removeFromSpaceDescription": "Tērzēšana tiks noņemta no kopienas, bet tā joprojām būs redzama tērzēšanu sarakstā.",
|
||||||
"countChats": "{chats} tērzēšanas",
|
"countChats": "{chats} tērzēšanas",
|
||||||
"@countChats": {
|
"@countChats": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
|
|
@ -2759,4 +2759,4 @@
|
||||||
"signUpGreeting": "FluffyChat ir decentralizēta. Jāatlasa serveris, kurā ir vēlēšanās izveidot savu kontu, un aiziet!",
|
"signUpGreeting": "FluffyChat ir decentralizēta. Jāatlasa serveris, kurā ir vēlēšanās izveidot savu kontu, un aiziet!",
|
||||||
"signInGreeting": "Jau ir Matrix konts? Laipni lūdzam atpakaļ! Jāatlasa savs mājasserveris un jāpiesakās.",
|
"signInGreeting": "Jau ir Matrix konts? Laipni lūdzam atpakaļ! Jāatlasa savs mājasserveris un jāpiesakās.",
|
||||||
"appIntro": "Ar FluffyChat vari tērzēt ar saviem draugiem. Tā ir droša un decentralizēta [matrix] ziņapmaiņas lietotne. Vairāk var uzzināt https://matrix.org, ja ir vēlēšanās, vai vienkārši jāpiesakās."
|
"appIntro": "Ar FluffyChat vari tērzēt ar saviem draugiem. Tā ir droša un decentralizēta [matrix] ziņapmaiņas lietotne. Vairāk var uzzināt https://matrix.org, ja ir vēlēšanās, vai vienkārši jāpiesakās."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2815,5 +2815,10 @@
|
||||||
"startVideoCall": "Start videosamtale",
|
"startVideoCall": "Start videosamtale",
|
||||||
"joinVoiceCall": "Bli med i lydsamtale",
|
"joinVoiceCall": "Bli med i lydsamtale",
|
||||||
"joinVideoCall": "Bli med i videosamtale",
|
"joinVideoCall": "Bli med i videosamtale",
|
||||||
"live": "Direkte"
|
"live": "Direkte",
|
||||||
|
"playSoundOnNotification": "Spill av lyd ved varsling",
|
||||||
|
"addTag": "Legg til emneknagg",
|
||||||
|
"removeTag": "Fjern emneknagg",
|
||||||
|
"tagName": "Navn på emneknagg",
|
||||||
|
"createNewTag": "Opprett ny emneknagg"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1953,7 +1953,7 @@
|
||||||
"unsupportedAndroidVersion": "Niet-ondersteunde Android-versie",
|
"unsupportedAndroidVersion": "Niet-ondersteunde Android-versie",
|
||||||
"unsupportedAndroidVersionLong": "Voor deze functie is een nieuwe Android-versie verplicht. Controleer je updates of Lineage OS-ondersteuning.",
|
"unsupportedAndroidVersionLong": "Voor deze functie is een nieuwe Android-versie verplicht. Controleer je updates of Lineage OS-ondersteuning.",
|
||||||
"videoCallsBetaWarning": "Houd er rekening mee dat videogesprekken momenteel in bèta zijn. Ze werken misschien niet zoals je verwacht of werken niet op alle platformen.",
|
"videoCallsBetaWarning": "Houd er rekening mee dat videogesprekken momenteel in bèta zijn. Ze werken misschien niet zoals je verwacht of werken niet op alle platformen.",
|
||||||
"voiceCall": "Spraakoproep",
|
"voiceCall": "Spraakgesprek",
|
||||||
"confirmEventUnpin": "Weet je zeker dat je de gebeurtenis definitief wilt losmaken?",
|
"confirmEventUnpin": "Weet je zeker dat je de gebeurtenis definitief wilt losmaken?",
|
||||||
"experimentalVideoCalls": "Videogesprekken (experimenteel)",
|
"experimentalVideoCalls": "Videogesprekken (experimenteel)",
|
||||||
"youAcceptedTheInvitation": "👍 Je hebt de uitnodiging geaccepteerd",
|
"youAcceptedTheInvitation": "👍 Je hebt de uitnodiging geaccepteerd",
|
||||||
|
|
@ -2128,7 +2128,7 @@
|
||||||
"replace": "Vervang",
|
"replace": "Vervang",
|
||||||
"report": "Rapporteer",
|
"report": "Rapporteer",
|
||||||
"reportErrorDescription": "😭 Oh nee. Er is iets misgegaan. Probeer het later nog eens. Als je wilt, kun je de bug rapporteren aan de ontwikkelaars.",
|
"reportErrorDescription": "😭 Oh nee. Er is iets misgegaan. Probeer het later nog eens. Als je wilt, kun je de bug rapporteren aan de ontwikkelaars.",
|
||||||
"sendTypingNotifications": "Typemeldingen verzenden",
|
"sendTypingNotifications": "Toon 'aan het typen'-meldingen",
|
||||||
"chatPermissions": "Chatrechten",
|
"chatPermissions": "Chatrechten",
|
||||||
"chatDescription": "Onderwerp",
|
"chatDescription": "Onderwerp",
|
||||||
"chatDescriptionHasBeenChanged": "Onderwerp gewijzigd",
|
"chatDescriptionHasBeenChanged": "Onderwerp gewijzigd",
|
||||||
|
|
@ -2799,5 +2799,18 @@
|
||||||
"supportFluffyChat": "FluffyChat steunen",
|
"supportFluffyChat": "FluffyChat steunen",
|
||||||
"support": "Steunen",
|
"support": "Steunen",
|
||||||
"setLowPriority": "Lage prioriteit instellen",
|
"setLowPriority": "Lage prioriteit instellen",
|
||||||
"unsetLowPriority": "Lage prioriteit uitschakelen"
|
"unsetLowPriority": "Lage prioriteit uitschakelen",
|
||||||
}
|
"removeCallFromChat": "Verwijder oproep van chat",
|
||||||
|
"removeCallFromChatDescription": "Wil je de oproep voor iedereen in de chat verwijderen?",
|
||||||
|
"removeCallForEveryone": "Verwijder oproep voor iedereen",
|
||||||
|
"live": "Live",
|
||||||
|
"startVoiceCall": "Start audio-gesprek",
|
||||||
|
"startVideoCall": "Start video-gesprek",
|
||||||
|
"joinVoiceCall": "Audio-gesprek opnemen",
|
||||||
|
"joinVideoCall": "Deelnemen aan video-gesprek",
|
||||||
|
"playSoundOnNotification": "Meldingsgeluid afspelen",
|
||||||
|
"addTag": "Tag toevoegen",
|
||||||
|
"removeTag": "Tag verwijderen",
|
||||||
|
"tagName": "Tagnaam",
|
||||||
|
"createNewTag": "Nieuwe tag maken"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"areGuestsAllowedToJoin": "Разрешено ли гостям присоединяться",
|
"areGuestsAllowedToJoin": "Разрешено ли гостям присоединяться?",
|
||||||
"@areGuestsAllowedToJoin": {
|
"@areGuestsAllowedToJoin": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -2105,8 +2105,8 @@
|
||||||
"notAnImage": "Это не картинка.",
|
"notAnImage": "Это не картинка.",
|
||||||
"importNow": "Импортировать сейчас",
|
"importNow": "Импортировать сейчас",
|
||||||
"importEmojis": "Импортировать эмодзи",
|
"importEmojis": "Импортировать эмодзи",
|
||||||
"importFromZipFile": "Импортировать из ZIP-файла",
|
"importFromZipFile": "Импортировать из zip-файла",
|
||||||
"exportEmotePack": "Экспортировать набор эмодзи как ZIP",
|
"exportEmotePack": "Экспортировать набор эмодзи как zip",
|
||||||
"replace": "Заменить",
|
"replace": "Заменить",
|
||||||
"googlyEyesContent": "{senderName} выпучил глаза",
|
"googlyEyesContent": "{senderName} выпучил глаза",
|
||||||
"@googlyEyesContent": {
|
"@googlyEyesContent": {
|
||||||
|
|
@ -2471,7 +2471,7 @@
|
||||||
"boldText": "Жирный шрифт",
|
"boldText": "Жирный шрифт",
|
||||||
"strikeThrough": "Перечёркнутый",
|
"strikeThrough": "Перечёркнутый",
|
||||||
"pleaseFillOut": "Пожалуйста, заполните",
|
"pleaseFillOut": "Пожалуйста, заполните",
|
||||||
"invalidUrl": "Не верный URL",
|
"invalidUrl": "Неверный url-адрес",
|
||||||
"addLink": "Добавить ссылку",
|
"addLink": "Добавить ссылку",
|
||||||
"italicText": "Italic",
|
"italicText": "Italic",
|
||||||
"unableToJoinChat": "Невозможно присоединиться к чату. Возможно, другая сторона уже закончила разговор.",
|
"unableToJoinChat": "Невозможно присоединиться к чату. Возможно, другая сторона уже закончила разговор.",
|
||||||
|
|
@ -2482,7 +2482,7 @@
|
||||||
"manageAccount": "Управление аккаунтом",
|
"manageAccount": "Управление аккаунтом",
|
||||||
"contactServerAdmin": "Админ сервера",
|
"contactServerAdmin": "Админ сервера",
|
||||||
"contactServerSecurity": "Безопасность контактов сервера",
|
"contactServerSecurity": "Безопасность контактов сервера",
|
||||||
"supportPage": "Поддержка",
|
"supportPage": "Страница поддержки",
|
||||||
"name": "Имя",
|
"name": "Имя",
|
||||||
"version": "Версия",
|
"version": "Версия",
|
||||||
"website": "Сайт",
|
"website": "Сайт",
|
||||||
|
|
@ -2699,5 +2699,124 @@
|
||||||
"type": "int"
|
"type": "int"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"versionWithNumber": "Версия: {version}",
|
||||||
|
"@versionWithNumber": {
|
||||||
|
"type": "String",
|
||||||
|
"placeholders": {
|
||||||
|
"version": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"logs": "Архивные записи",
|
||||||
|
"advancedConfigs": "Расширенные Настройки",
|
||||||
|
"advancedConfigurations": "Расширенные конфигурации",
|
||||||
|
"signUpGreeting": "FluffyChat децентрализорован! Выберите сервер, где вы хотите сделать свой аккаунт и заходите!",
|
||||||
|
"signInGreeting": "У вас есть уже аккаунт в Matrix? Добро пожаловать! Выберите свой сервер и войдите.",
|
||||||
|
"appIntro": "С FluffyChat'ом вы можете говорить со своими друзьями. Это защищённый децентрализорованный [matrix] мессенджер! Узнайте больше на https://matrix.org, если вам нравится или просто зарегистрироваться.",
|
||||||
|
"join": "Присоединиться",
|
||||||
|
"countFiles": "{count} файлов",
|
||||||
|
"@countFiles": {
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"unreadChatsInApp": "{appname}: {unread} непрочитанных чатов",
|
||||||
|
"@unreadChatsInApp": {
|
||||||
|
"type": "String",
|
||||||
|
"placeholders": {
|
||||||
|
"appname": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"unread": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"thereAreCountUsersBlocked": "Сейчас {count} пользователей заблокировано.",
|
||||||
|
"@thereAreCountUsersBlocked": {
|
||||||
|
"type": "String",
|
||||||
|
"count": {}
|
||||||
|
},
|
||||||
|
"serverLimitReached": "Достигнут серверный лимит! Подождите {seconds} секунд...",
|
||||||
|
"@serverLimitReached": {
|
||||||
|
"type": "integer",
|
||||||
|
"placeholders": {
|
||||||
|
"seconds": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"countVotes": "{count, plural, =1{Один голос} other{{count} голоса(-ов)}}",
|
||||||
|
"@countVotes": {
|
||||||
|
"type": "int",
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"countReplies": "{count, plural, =1{Один ответ} other{{count} ответа(-ов)}}",
|
||||||
|
"@countReplies": {
|
||||||
|
"type": "int",
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chatSearchedUntil": "Чат индексируется до {time}",
|
||||||
|
"@chatSearchedUntil": {
|
||||||
|
"type": "String",
|
||||||
|
"placeholders": {
|
||||||
|
"time": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"federationBaseUrl": "Основной URL федерации",
|
||||||
|
"clientWellKnownInformation": "Client-Well-Known Информация:",
|
||||||
|
"baseUrl": "Базовый URL",
|
||||||
|
"identityServer": "Сервер профилей:",
|
||||||
|
"signIn": "Войти",
|
||||||
|
"searchOrEnterHomeserverAddress": "Поищите или введите адрес домашнего сервера",
|
||||||
|
"matrixId": "Matrix ID",
|
||||||
|
"setPowerLevel": "Установить уровень возможностей",
|
||||||
|
"makeModerator": "Назначить модератором",
|
||||||
|
"makeAdmin": "Назначить администратором",
|
||||||
|
"removeModeratorRights": "Удалить права модератора",
|
||||||
|
"removeAdminRights": "Удалить права администратора",
|
||||||
|
"powerLevel": "Уровень энергии",
|
||||||
|
"setPowerLevelDescription": "Уровни прав определяют, что участнику разрешено делать в этой комнате, и обычно варьируются от 0 до 100.",
|
||||||
|
"owner": "Владелец",
|
||||||
|
"mute": "Мут",
|
||||||
|
"@mute": {
|
||||||
|
"description": "This should be a very short string because there is not much space in the button!"
|
||||||
|
},
|
||||||
|
"createNewChat": "Создать новый чат",
|
||||||
|
"reset": "Сброс",
|
||||||
|
"supportFluffyChat": "Поддержите FluffyChat",
|
||||||
|
"support": "Поддержка",
|
||||||
|
"fluffyChatSupportBannerMessage": "FluffyChat нуждается в ВАШЕЙ помощи!\n❤️❤️❤️\nFluffyChat всегда будет бесплатным, но разработка и хостинг всё равно требуют затрат.\nБудущее проекта зависит от поддержки таких людей, как вы.",
|
||||||
|
"skipSupportingFluffyChat": "Пропустить помощь FluffyChat",
|
||||||
|
"iAlreadySupportFluffyChat": "Я уже поддерживаю FluffyChat",
|
||||||
|
"iDoNotWantToSupport": "Я не хочу поддерживать",
|
||||||
|
"setLowPriority": "Установить низкий приоритет",
|
||||||
|
"unsetLowPriority": "Неопределенный приоритет",
|
||||||
|
"removeCallFromChat": "Удалить сообщение из чата",
|
||||||
|
"removeCallFromChatDescription": "Вы хотите удалить это сообщение из чата для всех участников?",
|
||||||
|
"removeCallForEveryone": "Отменить вызов для всех",
|
||||||
|
"startVoiceCall": "Начать голосовой вызов",
|
||||||
|
"startVideoCall": "Начать видеозвонок",
|
||||||
|
"joinVoiceCall": "Присоединиться к голосовому звонку",
|
||||||
|
"joinVideoCall": "Присоединиться к видеозвонку",
|
||||||
|
"live": "Прямой эфир",
|
||||||
|
"playSoundOnNotification": "Воспроизвести звук при получении уведомления",
|
||||||
|
"addTag": "Добавить тег",
|
||||||
|
"removeTag": "Удалить тег",
|
||||||
|
"tagName": "Название тега",
|
||||||
|
"createNewTag": "Создать новый тег"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"areGuestsAllowedToJoin": "Får gästanvändare gå med",
|
"areGuestsAllowedToJoin": "Får gästanvändare gå med?",
|
||||||
"@areGuestsAllowedToJoin": {
|
"@areGuestsAllowedToJoin": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
|
@ -2040,7 +2040,7 @@
|
||||||
},
|
},
|
||||||
"screenSharingTitle": "skärmdelning",
|
"screenSharingTitle": "skärmdelning",
|
||||||
"noKeyForThisMessage": "Detta kan hända om meddelandet skickades innan du loggade in på ditt konto i den här enheten.\n\nDet kan också vara så att avsändaren har blockerat din enhet eller att något gick fel med internetanslutningen.\n\nKan du läsa meddelandet i en annan session? I sådana fall kan du överföra meddelandet från den sessionen! Gå till Inställningar > Enhet och säkerställ att dina enheter har verifierat varandra. När du öppnar rummet nästa gång och båda sessionerna är i förgrunden, så kommer nycklarna att överföras automatiskt.\n\nVill du inte förlora nycklarna vid utloggning eller när du byter enhet? Säkerställ att du har aktiverat säkerhetskopiering för chatten i inställningarna.",
|
"noKeyForThisMessage": "Detta kan hända om meddelandet skickades innan du loggade in på ditt konto i den här enheten.\n\nDet kan också vara så att avsändaren har blockerat din enhet eller att något gick fel med internetanslutningen.\n\nKan du läsa meddelandet i en annan session? I sådana fall kan du överföra meddelandet från den sessionen! Gå till Inställningar > Enhet och säkerställ att dina enheter har verifierat varandra. När du öppnar rummet nästa gång och båda sessionerna är i förgrunden, så kommer nycklarna att överföras automatiskt.\n\nVill du inte förlora nycklarna vid utloggning eller när du byter enhet? Säkerställ att du har aktiverat säkerhetskopiering för chatten i inställningarna.",
|
||||||
"fileIsTooBigForServer": "Gick inte att skicka! Servern stödjer endast bilagor upp till{max}.",
|
"fileIsTooBigForServer": "Gick inte att skicka! Servern stödjer endast bilagor upp till {max}.",
|
||||||
"deviceKeys": "Enhetsnycklar:",
|
"deviceKeys": "Enhetsnycklar:",
|
||||||
"commandHint_googly": "Skicka några googly ögon",
|
"commandHint_googly": "Skicka några googly ögon",
|
||||||
"commandHint_cuddle": "Skicka en omfamning",
|
"commandHint_cuddle": "Skicka en omfamning",
|
||||||
|
|
@ -2333,7 +2333,7 @@
|
||||||
"stickers": "Klistermärken",
|
"stickers": "Klistermärken",
|
||||||
"discover": "Upptäck",
|
"discover": "Upptäck",
|
||||||
"ignoreUser": "Ignorera användare",
|
"ignoreUser": "Ignorera användare",
|
||||||
"aboutHomeserver": "Om{homeserver}",
|
"aboutHomeserver": "Om {homeserver}",
|
||||||
"@aboutHomeserver": {
|
"@aboutHomeserver": {
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
|
@ -2395,7 +2395,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"invitedBy": "📩Inbjuden av{user}",
|
"invitedBy": "📩Inbjuden av {user}",
|
||||||
"@invitedBy": {
|
"@invitedBy": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"user": {
|
"user": {
|
||||||
|
|
@ -2644,4 +2644,4 @@
|
||||||
"logs": "Loggar",
|
"logs": "Loggar",
|
||||||
"signIn": "Logga in",
|
"signIn": "Logga in",
|
||||||
"createNewAccount": "Skapa nytt konto"
|
"createNewAccount": "Skapa nytt konto"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2775,5 +2775,6 @@
|
||||||
"signUpGreeting": "FluffyChat децентралізований! Виберіть сервер, на якому ви хочете створити свій обліковий запис, і почнімо!",
|
"signUpGreeting": "FluffyChat децентралізований! Виберіть сервер, на якому ви хочете створити свій обліковий запис, і почнімо!",
|
||||||
"signInGreeting": "Ви вже маєте обліковий запис у Matrix? Ласкаво просимо! Виберіть свій домашній сервер і ввійдіть.",
|
"signInGreeting": "Ви вже маєте обліковий запис у Matrix? Ласкаво просимо! Виберіть свій домашній сервер і ввійдіть.",
|
||||||
"appIntro": "За допомогою FluffyChat ви можете спілкуватися зі своїми друзями. Це безпечний децентралізований месенджер [matrix]! Дізнайтеся більше на сайті https://matrix.org або просто зареєструйтеся.",
|
"appIntro": "За допомогою FluffyChat ви можете спілкуватися зі своїми друзями. Це безпечний децентралізований месенджер [matrix]! Дізнайтеся більше на сайті https://matrix.org або просто зареєструйтеся.",
|
||||||
"theProcessWasCanceled": "Процес скасовано."
|
"theProcessWasCanceled": "Процес скасовано.",
|
||||||
}
|
"join": "Приєднатись"
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2808,5 +2808,10 @@
|
||||||
"startVideoCall": "开始视频通话",
|
"startVideoCall": "开始视频通话",
|
||||||
"joinVoiceCall": "加入语音通话",
|
"joinVoiceCall": "加入语音通话",
|
||||||
"joinVideoCall": "加入视频通话",
|
"joinVideoCall": "加入视频通话",
|
||||||
"live": "实时"
|
"live": "实时",
|
||||||
|
"playSoundOnNotification": "播放通知声音",
|
||||||
|
"addTag": "添加标签",
|
||||||
|
"removeTag": "删除标签",
|
||||||
|
"tagName": "标签名",
|
||||||
|
"createNewTag": "创建新标签"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ class ArchiveController extends State<Archive> {
|
||||||
OkCancelResult.ok) {
|
OkCancelResult.ok) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
futureWithProgress: (onProgress) async {
|
futureWithProgress: (onProgress) async {
|
||||||
|
|
|
||||||
|
|
@ -382,6 +382,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
).wrongRecoveryKey,
|
).wrongRecoveryKey,
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
if (!context.mounted) return;
|
||||||
ErrorReporter(
|
ErrorReporter(
|
||||||
context,
|
context,
|
||||||
'Unable to open SSSS with recovery key',
|
'Unable to open SSSS with recovery key',
|
||||||
|
|
@ -425,6 +426,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: L10n.of(context).cancel,
|
||||||
);
|
);
|
||||||
if (consent != OkCancelResult.ok) return;
|
if (consent != OkCancelResult.ok) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
final req = await showFutureLoadingDialog(
|
final req = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
delay: false,
|
delay: false,
|
||||||
|
|
@ -435,11 +437,12 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (req.error != null) return;
|
if (req.error != null) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
final success = await KeyVerificationDialog(
|
final success = await KeyVerificationDialog(
|
||||||
request: req.result!,
|
request: req.result!,
|
||||||
).show(context);
|
).show(context);
|
||||||
if (success != true) return;
|
if (success != true) return;
|
||||||
if (!mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
final result = await showFutureLoadingDialog(
|
final result = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
context: context,
|
context: context,
|
||||||
future: room.leave,
|
future: room.leave,
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
if (success.error != null) return;
|
if (success.error != null) return;
|
||||||
context.go('/rooms');
|
context.go('/rooms');
|
||||||
}
|
}
|
||||||
|
|
@ -463,21 +464,25 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
scrollUpBannerEventId = eventId;
|
scrollUpBannerEventId = eventId;
|
||||||
});
|
});
|
||||||
|
|
||||||
bool firstUpdateReceived = false;
|
String? animateInEventId;
|
||||||
|
|
||||||
|
void _insert(int index) {
|
||||||
|
if (index > 0) return;
|
||||||
|
animateInEventId = timeline?.events.firstOrNull?.eventId;
|
||||||
|
}
|
||||||
|
|
||||||
void updateView() {
|
void updateView() {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setReadMarker();
|
setReadMarker();
|
||||||
setState(() {
|
setState(() {});
|
||||||
firstUpdateReceived = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void>? loadTimelineFuture;
|
Future<void>? loadTimelineFuture;
|
||||||
|
|
||||||
Future<void> _getTimeline({String? eventContextId}) async {
|
Future<void> _getTimeline({String? eventContextId}) async {
|
||||||
await Matrix.of(context).client.roomsLoading;
|
final matrix = Matrix.of(context);
|
||||||
await Matrix.of(context).client.accountDataLoading;
|
await matrix.client.roomsLoading;
|
||||||
|
await matrix.client.accountDataLoading;
|
||||||
if (eventContextId != null &&
|
if (eventContextId != null &&
|
||||||
(!eventContextId.isValidMatrixId || eventContextId.sigil != '\$')) {
|
(!eventContextId.isValidMatrixId || eventContextId.sigil != '\$')) {
|
||||||
eventContextId = null;
|
eventContextId = null;
|
||||||
|
|
@ -486,6 +491,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
timeline?.cancelSubscriptions();
|
timeline?.cancelSubscriptions();
|
||||||
timeline = await room.getTimeline(
|
timeline = await room.getTimeline(
|
||||||
onUpdate: updateView,
|
onUpdate: updateView,
|
||||||
|
onInsert: _insert,
|
||||||
eventContextId: eventContextId,
|
eventContextId: eventContextId,
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
|
@ -633,6 +639,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
Future<void> sendFileAction({FileType type = FileType.any}) async {
|
Future<void> sendFileAction({FileType type = FileType.any}) async {
|
||||||
final files = await selectFiles(context, allowMultiple: true, type: type);
|
final files = await selectFiles(context, allowMultiple: true, type: type);
|
||||||
if (files.isEmpty) return;
|
if (files.isEmpty) return;
|
||||||
|
if (!mounted) return;
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
|
|
@ -669,6 +676,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
FocusScope.of(context).requestFocus(FocusNode());
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
final file = await ImagePicker().pickImage(source: ImageSource.camera);
|
final file = await ImagePicker().pickImage(source: ImageSource.camera);
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -690,6 +698,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
maxDuration: const Duration(minutes: 1),
|
maxDuration: const Duration(minutes: 1),
|
||||||
);
|
);
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -732,26 +741,27 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
mimeType: mimeType,
|
mimeType: mimeType,
|
||||||
);
|
);
|
||||||
|
|
||||||
room
|
try {
|
||||||
.sendFileEvent(
|
await room.sendFileEvent(
|
||||||
file,
|
file,
|
||||||
inReplyTo: replyEvent,
|
inReplyTo: replyEvent,
|
||||||
threadRootEventId: activeThreadId,
|
threadRootEventId: activeThreadId,
|
||||||
extraContent: {
|
extraContent: {
|
||||||
'info': {...file.info, 'duration': duration},
|
'info': {...file.info, 'duration': duration},
|
||||||
'org.matrix.msc3245.voice': {},
|
'org.matrix.msc3245.voice': {},
|
||||||
'org.matrix.msc1767.audio': {
|
'org.matrix.msc1767.audio': {
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'waveform': waveform,
|
'waveform': waveform,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
},
|
||||||
.catchError((e) {
|
);
|
||||||
scaffoldMessenger.showSnackBar(
|
} catch (e) {
|
||||||
SnackBar(content: Text((e as Object).toLocalizedString(context))),
|
if (!mounted) return;
|
||||||
);
|
scaffoldMessenger.showSnackBar(
|
||||||
return null;
|
SnackBar(content: Text(e.toLocalizedString(context))),
|
||||||
});
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
replyEvent = null;
|
replyEvent = null;
|
||||||
});
|
});
|
||||||
|
|
@ -813,29 +823,30 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
|
|
||||||
Future<void> reportEventAction() async {
|
Future<void> reportEventAction() async {
|
||||||
final event = selectedEvents.single;
|
final event = selectedEvents.single;
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
final score = await showModalActionPopup<int>(
|
final score = await showModalActionPopup<int>(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).reportMessage,
|
title: l10n.reportMessage,
|
||||||
message: L10n.of(context).howOffensiveIsThisContent,
|
message: l10n.howOffensiveIsThisContent,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
actions: [
|
actions: [
|
||||||
AdaptiveModalAction(
|
AdaptiveModalAction(value: -100, label: l10n.extremeOffensive),
|
||||||
value: -100,
|
AdaptiveModalAction(value: -50, label: l10n.offensive),
|
||||||
label: L10n.of(context).extremeOffensive,
|
AdaptiveModalAction(value: 0, label: l10n.inoffensive),
|
||||||
),
|
|
||||||
AdaptiveModalAction(value: -50, label: L10n.of(context).offensive),
|
|
||||||
AdaptiveModalAction(value: 0, label: L10n.of(context).inoffensive),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (score == null) return;
|
if (score == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
final reason = await showTextInputDialog(
|
final reason = await showTextInputDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).whyDoYouWantToReportThis,
|
title: l10n.whyDoYouWantToReportThis,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
hintText: L10n.of(context).reason,
|
hintText: l10n.reason,
|
||||||
);
|
);
|
||||||
if (reason == null || reason.isEmpty) return;
|
if (reason == null || reason.isEmpty) return;
|
||||||
|
if (!mounted) return;
|
||||||
final result = await showFutureLoadingDialog(
|
final result = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(context).client.reportEvent(
|
future: () => Matrix.of(context).client.reportEvent(
|
||||||
|
|
@ -846,12 +857,13 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (result.error != null) return;
|
if (result.error != null) return;
|
||||||
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
showEmojiPicker = false;
|
showEmojiPicker = false;
|
||||||
selectedEvents.clear();
|
selectedEvents.clear();
|
||||||
});
|
});
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(content: Text(L10n.of(context).contentHasBeenReported)),
|
SnackBar(content: Text(l10n.contentHasBeenReported)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -867,6 +879,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
}
|
}
|
||||||
setState(selectedEvents.clear);
|
setState(selectedEvents.clear);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
if (!mounted) return;
|
||||||
ErrorReporter(
|
ErrorReporter(
|
||||||
context,
|
context,
|
||||||
'Error while delete error events action',
|
'Error while delete error events action',
|
||||||
|
|
@ -891,6 +904,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
: null;
|
: null;
|
||||||
if (reasonInput == null) return;
|
if (reasonInput == null) return;
|
||||||
final reason = reasonInput.isEmpty ? null : reasonInput;
|
final reason = reasonInput.isEmpty ? null : reasonInput;
|
||||||
|
if (!mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
futureWithProgress: (onProgress) async {
|
futureWithProgress: (onProgress) async {
|
||||||
|
|
@ -1147,7 +1161,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
users.sort((a, b) => a.powerLevel.compareTo(b.powerLevel));
|
users.sort((a, b) => a.powerLevel.level.compareTo(b.powerLevel.level));
|
||||||
final via = users
|
final via = users
|
||||||
.map((user) => user.id.domain)
|
.map((user) => user.id.domain)
|
||||||
.whereType<String>()
|
.whereType<String>()
|
||||||
|
|
@ -1248,6 +1262,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
okLabel: L10n.of(context).unpin,
|
okLabel: L10n.of(context).unpin,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: L10n.of(context).cancel,
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
if (response == OkCancelResult.ok) {
|
if (response == OkCancelResult.ok) {
|
||||||
final events = room.pinnedEventIds
|
final events = room.pinnedEventIds
|
||||||
..removeWhere((oldEvent) => oldEvent == eventId);
|
..removeWhere((oldEvent) => oldEvent == eventId);
|
||||||
|
|
@ -1337,17 +1352,18 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
Future<void> onPhoneButtonTap() async {
|
Future<void> onPhoneButtonTap() async {
|
||||||
// VoIP required Android SDK 21
|
// VoIP required Android SDK 21
|
||||||
if (PlatformInfos.isAndroid) {
|
if (PlatformInfos.isAndroid) {
|
||||||
DeviceInfoPlugin().androidInfo.then((value) {
|
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||||
if (value.version.sdkInt < 21) {
|
if (!mounted) return;
|
||||||
Navigator.pop(context);
|
if (androidInfo.version.sdkInt < 21) {
|
||||||
showOkAlertDialog(
|
Navigator.pop(context);
|
||||||
context: context,
|
await showOkAlertDialog(
|
||||||
title: L10n.of(context).unsupportedAndroidVersion,
|
context: context,
|
||||||
message: L10n.of(context).unsupportedAndroidVersionLong,
|
title: L10n.of(context).unsupportedAndroidVersion,
|
||||||
okLabel: L10n.of(context).close,
|
message: L10n.of(context).unsupportedAndroidVersionLong,
|
||||||
);
|
okLabel: L10n.of(context).close,
|
||||||
}
|
);
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final callType = await showModalActionPopup<CallType>(
|
final callType = await showModalActionPopup<CallType>(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -1368,11 +1384,13 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (callType == null) return;
|
if (callType == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
final voipPlugin = Matrix.of(context).voipPlugin;
|
final voipPlugin = Matrix.of(context).voipPlugin;
|
||||||
try {
|
try {
|
||||||
await voipPlugin!.voip.inviteToCall(room, callType);
|
await voipPlugin!.voip.inviteToCall(room, callType);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ class ChatEventList extends StatelessWidget {
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: .min,
|
mainAxisSize: .min,
|
||||||
children: [
|
children: [
|
||||||
SeenByRow(event: events.first),
|
if (events.isNotEmpty) SeenByRow(event: events.first),
|
||||||
TypingIndicators(controller),
|
TypingIndicators(controller),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
@ -117,9 +117,7 @@ class ChatEventList extends StatelessWidget {
|
||||||
|
|
||||||
// The message at this index:
|
// The message at this index:
|
||||||
final event = events[i];
|
final event = events[i];
|
||||||
final animateIn =
|
final animateIn = event.eventId == controller.animateInEventId;
|
||||||
event.eventId == timeline.events.first.eventId &&
|
|
||||||
controller.firstUpdateReceived;
|
|
||||||
|
|
||||||
final nextEvent = i + 1 < events.length ? events[i + 1] : null;
|
final nextEvent = i + 1 < events.length ? events[i + 1] : null;
|
||||||
final previousEvent = i > 0 ? events[i - 1] : null;
|
final previousEvent = i > 0 ? events[i - 1] : null;
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
});
|
});
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().v('Could not download audio file', e, s);
|
Logs().v('Could not download audio file', e, s);
|
||||||
|
if (!mounted) rethrow;
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
|
|
@ -208,6 +209,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
audioPlayer.play().onError(
|
audioPlayer.play().onError(
|
||||||
ErrorReporter(context, 'Unable to play audio message').onErrorCallback,
|
ErrorReporter(context, 'Unable to play audio message').onErrorCallback,
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ class _CuteContentState extends State<CuteContent> {
|
||||||
Future<void> addOverlay() async {
|
Future<void> addOverlay() async {
|
||||||
_isOverlayShown = true;
|
_isOverlayShown = true;
|
||||||
await Future.delayed(const Duration(milliseconds: 50));
|
await Future.delayed(const Duration(milliseconds: 50));
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
OverlayEntry? overlay;
|
OverlayEntry? overlay;
|
||||||
overlay = OverlayEntry(
|
overlay = OverlayEntry(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:fluffychat/config/setting_keys.dart';
|
||||||
import 'package:fluffychat/utils/code_highlight_theme.dart';
|
import 'package:fluffychat/utils/code_highlight_theme.dart';
|
||||||
import 'package:fluffychat/utils/event_checkbox_extension.dart';
|
import 'package:fluffychat/utils/event_checkbox_extension.dart';
|
||||||
import 'package:fluffychat/widgets/avatar.dart';
|
import 'package:fluffychat/widgets/avatar.dart';
|
||||||
|
|
@ -509,11 +510,15 @@ class HtmlMessage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final element = parser.parse(html).body ?? dom.Element.html('');
|
final element = parser.parse(html).body ?? dom.Element.html('');
|
||||||
|
final configuredMaxLines = AppSettings.messagePreviewMaxLines.value;
|
||||||
|
final maxLines = !limitHeight || configuredMaxLines <= 0
|
||||||
|
? null
|
||||||
|
: configuredMaxLines;
|
||||||
return Text.rich(
|
return Text.rich(
|
||||||
_renderHtml(element, context),
|
_renderHtml(element, context),
|
||||||
style: TextStyle(fontSize: fontSize, color: textColor),
|
style: TextStyle(fontSize: fontSize, color: textColor),
|
||||||
maxLines: limitHeight ? 64 : null,
|
maxLines: maxLines,
|
||||||
overflow: TextOverflow.fade,
|
overflow: maxLines == null ? TextOverflow.visible : TextOverflow.fade,
|
||||||
selectionColor: textColor.withAlpha(128),
|
selectionColor: textColor.withAlpha(128),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,8 @@ class Message extends StatelessWidget {
|
||||||
final enterThread = this.enterThread;
|
final enterThread = this.enterThread;
|
||||||
final sender = event.senderFromMemoryOrFallback;
|
final sender = event.senderFromMemoryOrFallback;
|
||||||
|
|
||||||
|
final fileSendingStatus = event.fileSendingStatus;
|
||||||
|
|
||||||
return _AnimateIn(
|
return _AnimateIn(
|
||||||
animateIn: animateIn,
|
animateIn: animateIn,
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|
@ -318,9 +320,33 @@ class Message extends StatelessWidget {
|
||||||
height: 16,
|
height: 16,
|
||||||
child: event.status == EventStatus.error
|
child: event.status == EventStatus.error
|
||||||
? const Icon(Icons.error, color: Colors.red)
|
? const Icon(Icons.error, color: Colors.red)
|
||||||
: event.fileSendingStatus != null
|
: fileSendingStatus != null
|
||||||
? const CircularProgressIndicator.adaptive(
|
? Stack(
|
||||||
strokeWidth: 1,
|
children: [
|
||||||
|
Center(
|
||||||
|
child: switch (fileSendingStatus) {
|
||||||
|
FileSendingStatus
|
||||||
|
.generatingThumbnail =>
|
||||||
|
Icon(
|
||||||
|
Icons.compress_outlined,
|
||||||
|
size: 14,
|
||||||
|
),
|
||||||
|
FileSendingStatus.encrypting =>
|
||||||
|
Icon(
|
||||||
|
Icons.lock_outlined,
|
||||||
|
size: 14,
|
||||||
|
),
|
||||||
|
FileSendingStatus.uploading =>
|
||||||
|
Icon(
|
||||||
|
Icons.upload_outlined,
|
||||||
|
size: 14,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const CircularProgressIndicator.adaptive(
|
||||||
|
strokeWidth: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
|
|
@ -361,17 +387,20 @@ class Message extends StatelessWidget {
|
||||||
? const SizedBox(height: 12)
|
? const SizedBox(height: 12)
|
||||||
: Row(
|
: Row(
|
||||||
children: [
|
children: [
|
||||||
if (sender.powerLevel >= 50)
|
if (sender.powerLevel.role !=
|
||||||
|
PowerLevelRole.user)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
right: 2.0,
|
right: 2.0,
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
sender.powerLevel >= 100
|
sender.powerLevel.role ==
|
||||||
|
PowerLevelRole
|
||||||
|
.moderator
|
||||||
? Icons
|
? Icons
|
||||||
.admin_panel_settings
|
.add_moderator_outlined
|
||||||
: Icons
|
: Icons
|
||||||
.add_moderator_outlined,
|
.admin_panel_settings,
|
||||||
size: 14,
|
size: 14,
|
||||||
color: theme
|
color: theme
|
||||||
.colorScheme
|
.colorScheme
|
||||||
|
|
@ -432,147 +461,161 @@ class Message extends StatelessWidget {
|
||||||
HapticFeedback.heavyImpact();
|
HapticFeedback.heavyImpact();
|
||||||
onSelect(event);
|
onSelect(event);
|
||||||
},
|
},
|
||||||
child: Container(
|
child: AnimatedOpacity(
|
||||||
decoration: BoxDecoration(
|
duration: FluffyThemes.animationDuration,
|
||||||
color: noBubble
|
curve: FluffyThemes.animationCurve,
|
||||||
? Colors.transparent
|
opacity:
|
||||||
: color,
|
event.status.isSending ||
|
||||||
borderRadius: borderRadius,
|
event.type == EventTypes.Encrypted
|
||||||
),
|
? 0.5
|
||||||
clipBehavior: Clip.antiAlias,
|
: 1,
|
||||||
child: BubbleBackground(
|
child: Container(
|
||||||
colors: colors,
|
decoration: BoxDecoration(
|
||||||
ignore:
|
color: noBubble
|
||||||
noBubble ||
|
? Colors.transparent
|
||||||
!ownMessage ||
|
: color,
|
||||||
MediaQuery.highContrastOf(context),
|
borderRadius: borderRadius,
|
||||||
scrollController: scrollController,
|
),
|
||||||
child: Container(
|
clipBehavior: Clip.antiAlias,
|
||||||
decoration: BoxDecoration(
|
child: BubbleBackground(
|
||||||
borderRadius: BorderRadius.circular(
|
colors: colors,
|
||||||
AppConfig.borderRadius,
|
ignore:
|
||||||
|
noBubble ||
|
||||||
|
!ownMessage ||
|
||||||
|
MediaQuery.highContrastOf(context),
|
||||||
|
scrollController: scrollController,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
constraints: const BoxConstraints(
|
||||||
constraints: const BoxConstraints(
|
maxWidth:
|
||||||
maxWidth:
|
FluffyThemes.columnWidth * 1.5,
|
||||||
FluffyThemes.columnWidth * 1.5,
|
),
|
||||||
),
|
child: Column(
|
||||||
child: Column(
|
mainAxisSize: .min,
|
||||||
mainAxisSize: .min,
|
crossAxisAlignment:
|
||||||
crossAxisAlignment:
|
CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment.start,
|
children: <Widget>[
|
||||||
children: <Widget>[
|
if (event.inReplyToEventId(
|
||||||
if (event.inReplyToEventId(
|
includingFallback: false,
|
||||||
includingFallback: false,
|
) !=
|
||||||
) !=
|
null)
|
||||||
null)
|
FutureBuilder<Event?>(
|
||||||
FutureBuilder<Event?>(
|
future: event.getReplyEvent(
|
||||||
future: event.getReplyEvent(
|
timeline,
|
||||||
timeline,
|
),
|
||||||
),
|
builder: (BuildContext context, snapshot) {
|
||||||
builder: (BuildContext context, snapshot) {
|
final replyEvent =
|
||||||
final replyEvent =
|
snapshot.hasData
|
||||||
snapshot.hasData
|
? snapshot.data!
|
||||||
? snapshot.data!
|
: Event(
|
||||||
: Event(
|
eventId:
|
||||||
eventId:
|
event
|
||||||
event
|
.inReplyToEventId() ??
|
||||||
.inReplyToEventId() ??
|
'\$fake_event_id',
|
||||||
'\$fake_event_id',
|
content: {
|
||||||
content: {
|
'msgtype':
|
||||||
'msgtype': 'm.text',
|
'm.text',
|
||||||
'body': '...',
|
'body': '...',
|
||||||
},
|
},
|
||||||
senderId:
|
senderId:
|
||||||
event.senderId,
|
event.senderId,
|
||||||
type:
|
type:
|
||||||
'm.room.message',
|
'm.room.message',
|
||||||
room: event.room,
|
room: event.room,
|
||||||
status:
|
status: EventStatus
|
||||||
EventStatus.sent,
|
.sent,
|
||||||
originServerTs:
|
originServerTs:
|
||||||
DateTime.now(),
|
DateTime.now(),
|
||||||
);
|
);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.only(
|
const EdgeInsets.only(
|
||||||
left: 16,
|
left: 16,
|
||||||
right: 16,
|
right: 16,
|
||||||
top: 8,
|
top: 8,
|
||||||
),
|
),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Colors.transparent,
|
color:
|
||||||
borderRadius: ReplyContent
|
Colors.transparent,
|
||||||
.borderRadius,
|
|
||||||
child: InkWell(
|
|
||||||
borderRadius:
|
borderRadius:
|
||||||
ReplyContent
|
ReplyContent
|
||||||
.borderRadius,
|
.borderRadius,
|
||||||
onTap: () =>
|
child: InkWell(
|
||||||
scrollToEventId(
|
borderRadius:
|
||||||
replyEvent
|
ReplyContent
|
||||||
.eventId,
|
.borderRadius,
|
||||||
|
onTap: () =>
|
||||||
|
scrollToEventId(
|
||||||
|
replyEvent
|
||||||
|
.eventId,
|
||||||
|
),
|
||||||
|
child: AbsorbPointer(
|
||||||
|
child: ReplyContent(
|
||||||
|
replyEvent,
|
||||||
|
ownMessage:
|
||||||
|
ownMessage,
|
||||||
|
timeline:
|
||||||
|
timeline,
|
||||||
),
|
),
|
||||||
child: AbsorbPointer(
|
|
||||||
child: ReplyContent(
|
|
||||||
replyEvent,
|
|
||||||
ownMessage:
|
|
||||||
ownMessage,
|
|
||||||
timeline: timeline,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
|
||||||
),
|
|
||||||
MessageContent(
|
|
||||||
displayEvent,
|
|
||||||
textColor: textColor,
|
|
||||||
linkColor: linkColor,
|
|
||||||
onInfoTab: onInfoTab,
|
|
||||||
borderRadius: borderRadius,
|
|
||||||
timeline: timeline,
|
|
||||||
selected: selected,
|
|
||||||
bigEmojis: bigEmojis,
|
|
||||||
),
|
|
||||||
if (event.hasAggregatedEvents(
|
|
||||||
timeline,
|
|
||||||
RelationshipTypes.edit,
|
|
||||||
))
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
bottom: 8.0,
|
|
||||||
left: 16.0,
|
|
||||||
right: 16.0,
|
|
||||||
),
|
),
|
||||||
child: Row(
|
MessageContent(
|
||||||
mainAxisSize:
|
displayEvent,
|
||||||
MainAxisSize.min,
|
textColor: textColor,
|
||||||
spacing: 4.0,
|
linkColor: linkColor,
|
||||||
children: [
|
onInfoTab: onInfoTab,
|
||||||
Icon(
|
borderRadius: borderRadius,
|
||||||
Icons.edit_outlined,
|
timeline: timeline,
|
||||||
color: textColor
|
selected: selected,
|
||||||
.withAlpha(164),
|
bigEmojis: bigEmojis,
|
||||||
size: 14,
|
),
|
||||||
),
|
if (event.hasAggregatedEvents(
|
||||||
Text(
|
timeline,
|
||||||
displayEvent
|
RelationshipTypes.edit,
|
||||||
.originServerTs
|
))
|
||||||
.localizedTimeShort(
|
Padding(
|
||||||
context,
|
padding:
|
||||||
),
|
const EdgeInsets.only(
|
||||||
style: TextStyle(
|
bottom: 8.0,
|
||||||
|
left: 16.0,
|
||||||
|
right: 16.0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize:
|
||||||
|
MainAxisSize.min,
|
||||||
|
spacing: 4.0,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.edit_outlined,
|
||||||
color: textColor
|
color: textColor
|
||||||
.withAlpha(164),
|
.withAlpha(164),
|
||||||
fontSize: 11,
|
size: 14,
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
],
|
displayEvent
|
||||||
|
.originServerTs
|
||||||
|
.localizedTimeShort(
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
color: textColor
|
||||||
|
.withAlpha(164),
|
||||||
|
fontSize: 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -959,15 +1002,10 @@ class __AnimateInState extends State<_AnimateIn> {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return AnimatedOpacity(
|
return AnimatedSize(
|
||||||
duration: FluffyThemes.animationDuration,
|
duration: FluffyThemes.animationDuration,
|
||||||
curve: FluffyThemes.animationCurve,
|
curve: FluffyThemes.animationCurve,
|
||||||
opacity: _animationFinished ? 1 : 0,
|
child: _animationFinished ? widget.child : const SizedBox.shrink(),
|
||||||
child: AnimatedSize(
|
|
||||||
duration: FluffyThemes.animationDuration,
|
|
||||||
curve: FluffyThemes.animationCurve,
|
|
||||||
child: _animationFinished ? widget.child : const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,11 @@ class MessageContent extends StatelessWidget {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final state = await client.getCryptoIdentityState();
|
final state = await client.getCryptoIdentityState();
|
||||||
if (!state.connected) {
|
if (!state.connected) {
|
||||||
|
if (!context.mounted) return;
|
||||||
final success = await context.push('/backup');
|
final success = await context.push('/backup');
|
||||||
if (success != true) return;
|
if (success != true) return;
|
||||||
}
|
}
|
||||||
|
if (!context.mounted) return;
|
||||||
event.requestKey();
|
event.requestKey();
|
||||||
final sender = event.senderFromMemoryOrFallback;
|
final sender = event.senderFromMemoryOrFallback;
|
||||||
await showAdaptiveBottomSheet(
|
await showAdaptiveBottomSheet(
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,10 @@ class InputBar extends StatelessWidget {
|
||||||
controller: controller,
|
controller: controller,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
readOnly: readOnly,
|
readOnly: readOnly,
|
||||||
|
onEditingComplete: () {
|
||||||
|
// To not lose focus on iOS:
|
||||||
|
// https://github.com/krille-chan/fluffychat/issues/2784
|
||||||
|
},
|
||||||
contextMenuBuilder: (c, e) => MarkdownContextBuilder(
|
contextMenuBuilder: (c, e) => MarkdownContextBuilder(
|
||||||
editableTextState: e,
|
editableTextState: e,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class PinnedEvents extends StatelessWidget {
|
||||||
const PinnedEvents(this.controller, {super.key});
|
const PinnedEvents(this.controller, {super.key});
|
||||||
|
|
||||||
Future<void> _displayPinnedEventsDialog(BuildContext context) async {
|
Future<void> _displayPinnedEventsDialog(BuildContext context) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final eventsResult = await showFutureLoadingDialog(
|
final eventsResult = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Future.wait(
|
future: () => Future.wait(
|
||||||
|
|
@ -25,13 +26,14 @@ class PinnedEvents extends StatelessWidget {
|
||||||
);
|
);
|
||||||
final events = eventsResult.result;
|
final events = eventsResult.result;
|
||||||
if (events == null) return;
|
if (events == null) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
final eventId = events.length == 1
|
final eventId = events.length == 1
|
||||||
? events.single?.eventId
|
? events.single?.eventId
|
||||||
: await showModalActionPopup<String>(
|
: await showModalActionPopup<String>(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).pin,
|
title: l10n.pin,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
actions: events
|
actions: events
|
||||||
.map(
|
.map(
|
||||||
(event) => AdaptiveModalAction(
|
(event) => AdaptiveModalAction(
|
||||||
|
|
@ -39,7 +41,7 @@ class PinnedEvents extends StatelessWidget {
|
||||||
icon: const Icon(Icons.push_pin_outlined),
|
icon: const Icon(Icons.push_pin_outlined),
|
||||||
label:
|
label:
|
||||||
event?.calcLocalizedBodyFallback(
|
event?.calcLocalizedBodyFallback(
|
||||||
MatrixLocals(L10n.of(context)),
|
MatrixLocals(l10n),
|
||||||
withSenderNamePrefix: true,
|
withSenderNamePrefix: true,
|
||||||
hideReply: true,
|
hideReply: true,
|
||||||
) ??
|
) ??
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class RecordingViewModelState extends State<RecordingViewModel> {
|
||||||
room.client.getConfig(); // Preload server file configuration.
|
room.client.getConfig(); // Preload server file configuration.
|
||||||
if (PlatformInfos.isAndroid) {
|
if (PlatformInfos.isAndroid) {
|
||||||
final info = await DeviceInfoPlugin().androidInfo;
|
final info = await DeviceInfoPlugin().androidInfo;
|
||||||
|
if (!mounted) return;
|
||||||
if (info.version.sdkInt < 19) {
|
if (info.version.sdkInt < 19) {
|
||||||
showOkAlertDialog(
|
showOkAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -76,6 +77,7 @@ class RecordingViewModelState extends State<RecordingViewModel> {
|
||||||
|
|
||||||
final result = await audioRecorder.hasPermission();
|
final result = await audioRecorder.hasPermission();
|
||||||
if (result != true) {
|
if (result != true) {
|
||||||
|
if (!mounted) return;
|
||||||
showOkAlertDialog(
|
showOkAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).oopsSomethingWentWrong,
|
title: L10n.of(context).oopsSomethingWentWrong,
|
||||||
|
|
@ -97,10 +99,12 @@ class RecordingViewModelState extends State<RecordingViewModel> {
|
||||||
),
|
),
|
||||||
path: path ?? '',
|
path: path ?? '',
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
setState(() => duration = Duration.zero);
|
setState(() => duration = Duration.zero);
|
||||||
_subscribe();
|
_subscribe();
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to start voice message recording', e, s);
|
Logs().w('Unable to start voice message recording', e, s);
|
||||||
|
if (!mounted) return;
|
||||||
showOkAlertDialog(
|
showOkAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).oopsSomethingWentWrong,
|
title: L10n.of(context).oopsSomethingWentWrong,
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,7 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
scaffoldMessenger.clearSnackBars();
|
scaffoldMessenger.clearSnackBars();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
scaffoldMessenger.clearSnackBars();
|
scaffoldMessenger.clearSnackBars();
|
||||||
|
if (!mounted || !widget.outerContext.mounted) rethrow;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
scaffoldMessenger.showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ class SendLocationDialogState extends State<SendLocationDialog> {
|
||||||
context: context,
|
context: context,
|
||||||
future: () => widget.room.sendLocation(body, uri),
|
future: () => widget.room.sendLocation(body, uri),
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
Navigator.of(context, rootNavigator: false).pop();
|
Navigator.of(context, rootNavigator: false).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class _StartPollBottomSheetState extends State<StartPollBottomSheet> {
|
||||||
maxSelections: _allowMultipleAnswers ? _answers.length : 1,
|
maxSelections: _allowMultipleAnswers ? _answers.length : 1,
|
||||||
txid: _txid,
|
txid: _txid,
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to create poll', e, s);
|
Logs().w('Unable to create poll', e, s);
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateRoomAction() async {
|
Future<void> updateRoomAction() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final roomVersion = room
|
final roomVersion = room
|
||||||
.getState(EventTypes.RoomCreate)!
|
.getState(EventTypes.RoomCreate)!
|
||||||
.content
|
.content
|
||||||
|
|
@ -170,10 +171,11 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
);
|
);
|
||||||
final capabilities = capabilitiesResult.result;
|
final capabilities = capabilitiesResult.result;
|
||||||
if (capabilities == null) return;
|
if (capabilities == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
final newVersion = await showModalActionPopup<String>(
|
final newVersion = await showModalActionPopup<String>(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).replaceRoomWithNewerVersion,
|
title: l10n.replaceRoomWithNewerVersion,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
actions: capabilities.mRoomVersions!.available.entries
|
actions: capabilities.mRoomVersions!.available.entries
|
||||||
.where((r) => r.key != roomVersion)
|
.where((r) => r.key != roomVersion)
|
||||||
.map(
|
.map(
|
||||||
|
|
@ -185,18 +187,20 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
if (newVersion == null ||
|
if (newVersion == null) return;
|
||||||
OkCancelResult.cancel ==
|
if (!mounted) return;
|
||||||
await showOkCancelAlertDialog(
|
final confirmUpgrade = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: l10n.yes,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
title: L10n.of(context).areYouSure,
|
title: l10n.areYouSure,
|
||||||
message: L10n.of(context).roomUpgradeDescription,
|
message: l10n.roomUpgradeDescription,
|
||||||
isDestructive: true,
|
isDestructive: true,
|
||||||
)) {
|
);
|
||||||
|
if (confirmUpgrade == OkCancelResult.cancel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!mounted) return;
|
||||||
final result = await showFutureLoadingDialog(
|
final result = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
futureWithProgress: (onProgress) async {
|
futureWithProgress: (onProgress) async {
|
||||||
|
|
@ -243,6 +247,7 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addAlias() async {
|
Future<void> addAlias() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final domain = room.client.userID?.domain;
|
final domain = room.client.userID?.domain;
|
||||||
if (domain == null) {
|
if (domain == null) {
|
||||||
throw Exception('userID or domain is null! This should never happen.');
|
throw Exception('userID or domain is null! This should never happen.');
|
||||||
|
|
@ -250,11 +255,12 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
|
|
||||||
final input = await showTextInputDialog(
|
final input = await showTextInputDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).editRoomAliases,
|
title: l10n.editRoomAliases,
|
||||||
prefixText: '#',
|
prefixText: '#',
|
||||||
suffixText: domain,
|
suffixText: domain,
|
||||||
hintText: L10n.of(context).alias,
|
hintText: l10n.alias,
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
final aliasLocalpart = input?.trim();
|
final aliasLocalpart = input?.trim();
|
||||||
if (aliasLocalpart == null || aliasLocalpart.isEmpty) return;
|
if (aliasLocalpart == null || aliasLocalpart.isEmpty) return;
|
||||||
final alias = '#$aliasLocalpart:$domain';
|
final alias = '#$aliasLocalpart:$domain';
|
||||||
|
|
@ -264,17 +270,19 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
||||||
future: () => room.client.setRoomAlias(alias, room.id),
|
future: () => room.client.setRoomAlias(alias, room.id),
|
||||||
);
|
);
|
||||||
if (result.error != null) return;
|
if (result.error != null) return;
|
||||||
|
if (!mounted) return;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
||||||
if (!room.canChangeStateEvent(EventTypes.RoomCanonicalAlias)) return;
|
if (!room.canChangeStateEvent(EventTypes.RoomCanonicalAlias)) return;
|
||||||
|
|
||||||
final canonicalAliasConsent = await showOkCancelAlertDialog(
|
final canonicalAliasConsent = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).setAsCanonicalAlias,
|
title: l10n.setAsCanonicalAlias,
|
||||||
message: alias,
|
message: alias,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: l10n.yes,
|
||||||
cancelLabel: L10n.of(context).no,
|
cancelLabel: l10n.no,
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
final altAliases =
|
final altAliases =
|
||||||
room
|
room
|
||||||
|
|
|
||||||
|
|
@ -37,69 +37,78 @@ class ChatDetailsController extends State<ChatDetails> {
|
||||||
String? get roomId => widget.roomId;
|
String? get roomId => widget.roomId;
|
||||||
|
|
||||||
Future<void> setDisplaynameAction() async {
|
Future<void> setDisplaynameAction() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
final room = Matrix.of(context).client.getRoomById(roomId!)!;
|
final room = Matrix.of(context).client.getRoomById(roomId!)!;
|
||||||
final input = await showTextInputDialog(
|
final input = await showTextInputDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).changeTheNameOfTheGroup,
|
title: l10n.changeTheNameOfTheGroup,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
initialText: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
initialText: room.getLocalizedDisplayname(MatrixLocals(l10n)),
|
||||||
);
|
);
|
||||||
if (input == null) return;
|
if (input == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => room.setName(input),
|
future: () => room.setName(input),
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
if (success.error == null) {
|
if (success.error == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(content: Text(L10n.of(context).displaynameHasBeenChanged)),
|
SnackBar(content: Text(l10n.displaynameHasBeenChanged)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setTopicAction() async {
|
Future<void> setTopicAction() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
final room = Matrix.of(context).client.getRoomById(roomId!)!;
|
final room = Matrix.of(context).client.getRoomById(roomId!)!;
|
||||||
final input = await showTextInputDialog(
|
final input = await showTextInputDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).setChatDescription,
|
title: l10n.setChatDescription,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
hintText: L10n.of(context).noChatDescriptionYet,
|
hintText: l10n.noChatDescriptionYet,
|
||||||
initialText: room.topic,
|
initialText: room.topic,
|
||||||
minLines: 4,
|
minLines: 4,
|
||||||
maxLines: 8,
|
maxLines: 8,
|
||||||
);
|
);
|
||||||
if (input == null) return;
|
if (input == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => room.setDescription(input),
|
future: () => room.setDescription(input),
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
if (success.error == null) {
|
if (success.error == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(content: Text(L10n.of(context).chatDescriptionHasBeenChanged)),
|
SnackBar(content: Text(l10n.chatDescriptionHasBeenChanged)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setAvatarAction() async {
|
Future<void> setAvatarAction() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final room = Matrix.of(context).client.getRoomById(roomId!);
|
final room = Matrix.of(context).client.getRoomById(roomId!);
|
||||||
final actions = [
|
final actions = [
|
||||||
if (PlatformInfos.isMobile)
|
if (PlatformInfos.isMobile)
|
||||||
AdaptiveModalAction(
|
AdaptiveModalAction(
|
||||||
value: AvatarAction.camera,
|
value: AvatarAction.camera,
|
||||||
label: L10n.of(context).openCamera,
|
label: l10n.openCamera,
|
||||||
isDefaultAction: true,
|
isDefaultAction: true,
|
||||||
icon: const Icon(Icons.camera_alt_outlined),
|
icon: const Icon(Icons.camera_alt_outlined),
|
||||||
),
|
),
|
||||||
AdaptiveModalAction(
|
AdaptiveModalAction(
|
||||||
value: AvatarAction.file,
|
value: AvatarAction.file,
|
||||||
label: L10n.of(context).openGallery,
|
label: l10n.openGallery,
|
||||||
icon: const Icon(Icons.photo_outlined),
|
icon: const Icon(Icons.photo_outlined),
|
||||||
),
|
),
|
||||||
if (room?.avatar != null)
|
if (room?.avatar != null)
|
||||||
AdaptiveModalAction(
|
AdaptiveModalAction(
|
||||||
value: AvatarAction.remove,
|
value: AvatarAction.remove,
|
||||||
label: L10n.of(context).delete,
|
label: l10n.delete,
|
||||||
isDestructive: true,
|
isDestructive: true,
|
||||||
icon: const Icon(Icons.delete_outlined),
|
icon: const Icon(Icons.delete_outlined),
|
||||||
),
|
),
|
||||||
|
|
@ -108,11 +117,12 @@ class ChatDetailsController extends State<ChatDetails> {
|
||||||
? actions.single.value
|
? actions.single.value
|
||||||
: await showModalActionPopup<AvatarAction>(
|
: await showModalActionPopup<AvatarAction>(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).editRoomAvatar,
|
title: l10n.editRoomAvatar,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
if (action == null) return;
|
if (action == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
if (action == AvatarAction.remove) {
|
if (action == AvatarAction.remove) {
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -131,6 +141,7 @@ class ChatDetailsController extends State<ChatDetails> {
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
file = MatrixFile(bytes: await result.readAsBytes(), name: result.path);
|
file = MatrixFile(bytes: await result.readAsBytes(), name: result.path);
|
||||||
} else {
|
} else {
|
||||||
|
if (!mounted) return;
|
||||||
final picked = await selectFiles(
|
final picked = await selectFiles(
|
||||||
context,
|
context,
|
||||||
allowMultiple: false,
|
allowMultiple: false,
|
||||||
|
|
@ -143,6 +154,7 @@ class ChatDetailsController extends State<ChatDetails> {
|
||||||
name: pickedFile.name,
|
name: pickedFile.name,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => room!.setAvatar(file),
|
future: () => room!.setAvatar(file),
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ class ChatDetailsView extends StatelessWidget {
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
var members = room.getParticipants().toList()
|
var members = room.getParticipants().toList()
|
||||||
..sort((b, a) => a.powerLevel.compareTo(b.powerLevel));
|
..sort((b, a) => a.powerLevel.level.compareTo(b.powerLevel.level));
|
||||||
members = members.take(10).toList();
|
members = members.take(10).toList();
|
||||||
final actualMembersCount =
|
final actualMembersCount =
|
||||||
(room.summary.mInvitedMemberCount ?? 0) +
|
(room.summary.mInvitedMemberCount ?? 0) +
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,16 @@ class ParticipantListItem extends StatelessWidget {
|
||||||
Membership.leave => L10n.of(context).leftTheChat,
|
Membership.leave => L10n.of(context).leftTheChat,
|
||||||
};
|
};
|
||||||
|
|
||||||
final permissionBatch = user.room.creatorUserIds.contains(user.id)
|
final permissionBatch = switch (user.powerLevel.role) {
|
||||||
? L10n.of(context).owner
|
PowerLevelRole.user => '',
|
||||||
: user.powerLevel >= 100
|
PowerLevelRole.moderator => L10n.of(context).moderator,
|
||||||
? L10n.of(context).admin
|
PowerLevelRole.admin => L10n.of(context).admin,
|
||||||
: user.powerLevel >= 50
|
PowerLevelRole.owner => L10n.of(context).owner,
|
||||||
? L10n.of(context).moderator
|
};
|
||||||
: '';
|
|
||||||
|
final isAdminOrOwner =
|
||||||
|
user.powerLevel.role == PowerLevelRole.admin ||
|
||||||
|
user.powerLevel.role == PowerLevelRole.owner;
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
onTap: () => showMemberActionsPopupMenu(context: context, user: user),
|
onTap: () => showMemberActionsPopupMenu(context: context, user: user),
|
||||||
|
|
@ -45,7 +48,7 @@ class ParticipantListItem extends StatelessWidget {
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: user.powerLevel >= 100
|
color: isAdminOrOwner
|
||||||
? theme.colorScheme.tertiary
|
? theme.colorScheme.tertiary
|
||||||
: theme.colorScheme.tertiaryContainer,
|
: theme.colorScheme.tertiaryContainer,
|
||||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||||
|
|
@ -53,7 +56,7 @@ class ParticipantListItem extends StatelessWidget {
|
||||||
child: Text(
|
child: Text(
|
||||||
permissionBatch,
|
permissionBatch,
|
||||||
style: theme.textTheme.labelSmall?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
color: user.powerLevel >= 100
|
color: isAdminOrOwner
|
||||||
? theme.colorScheme.onTertiary
|
? theme.colorScheme.onTertiary
|
||||||
: theme.colorScheme.onTertiaryContainer,
|
: theme.colorScheme.onTertiaryContainer,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -30,38 +30,40 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> enableEncryption(_) async {
|
Future<void> enableEncryption(_) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
if (room.encrypted) {
|
if (room.encrypted) {
|
||||||
showOkAlertDialog(
|
showOkAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).sorryThatsNotPossible,
|
title: l10n.sorryThatsNotPossible,
|
||||||
message: L10n.of(context).disableEncryptionWarning,
|
message: l10n.disableEncryptionWarning,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (room.joinRules == JoinRules.public) {
|
if (room.joinRules == JoinRules.public) {
|
||||||
showOkAlertDialog(
|
showOkAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).sorryThatsNotPossible,
|
title: l10n.sorryThatsNotPossible,
|
||||||
message: L10n.of(context).noEncryptionForPublicRooms,
|
message: l10n.noEncryptionForPublicRooms,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!room.canChangeStateEvent(EventTypes.Encryption)) {
|
if (!room.canChangeStateEvent(EventTypes.Encryption)) {
|
||||||
showOkAlertDialog(
|
showOkAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).sorryThatsNotPossible,
|
title: l10n.sorryThatsNotPossible,
|
||||||
message: L10n.of(context).noPermission,
|
message: l10n.noPermission,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final consent = await showOkCancelAlertDialog(
|
final consent = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: l10n.areYouSure,
|
||||||
message: L10n.of(context).enableEncryptionWarning,
|
message: l10n.enableEncryptionWarning,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: l10n.yes,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
);
|
);
|
||||||
if (consent != OkCancelResult.ok) return;
|
if (consent != OkCancelResult.ok) return;
|
||||||
|
if (!mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => room.enableEncryption(),
|
future: () => room.enableEncryption(),
|
||||||
|
|
@ -69,14 +71,16 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> startVerification() async {
|
Future<void> startVerification() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final consent = await showOkCancelAlertDialog(
|
final consent = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).verifyOtherUser,
|
title: l10n.verifyOtherUser,
|
||||||
message: L10n.of(context).verifyOtherUserDescription,
|
message: l10n.verifyOtherUserDescription,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
);
|
);
|
||||||
if (consent != OkCancelResult.ok) return;
|
if (consent != OkCancelResult.ok) return;
|
||||||
|
if (!mounted) return;
|
||||||
final req = await room.client.userDeviceKeys[room.directChatMatrixID]!
|
final req = await room.client.userDeviceKeys[room.directChatMatrixID]!
|
||||||
.startVerification();
|
.startVerification();
|
||||||
req.onUpdate = () {
|
req.onUpdate = () {
|
||||||
|
|
@ -84,6 +88,7 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (!mounted) return;
|
||||||
await KeyVerificationDialog(request: req).show(context);
|
await KeyVerificationDialog(request: req).show(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:cross_file/cross_file.dart';
|
import 'package:cross_file/cross_file.dart';
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
import 'package:fluffychat/l10n/l10n.dart';
|
import 'package:fluffychat/l10n/l10n.dart';
|
||||||
|
|
@ -30,7 +30,7 @@ import '../../config/setting_keys.dart';
|
||||||
import '../../utils/url_launcher.dart';
|
import '../../utils/url_launcher.dart';
|
||||||
import '../../widgets/matrix.dart';
|
import '../../widgets/matrix.dart';
|
||||||
|
|
||||||
enum ActiveFilter { allChats, messages, groups, unread, spaces }
|
enum ActiveFilter { allChats, messages, groups, unread, spaces, tag }
|
||||||
|
|
||||||
extension LocalizedActiveFilter on ActiveFilter {
|
extension LocalizedActiveFilter on ActiveFilter {
|
||||||
String toLocalizedString(BuildContext context) {
|
String toLocalizedString(BuildContext context) {
|
||||||
|
|
@ -45,6 +45,8 @@ extension LocalizedActiveFilter on ActiveFilter {
|
||||||
return L10n.of(context).groups;
|
return L10n.of(context).groups;
|
||||||
case ActiveFilter.spaces:
|
case ActiveFilter.spaces:
|
||||||
return L10n.of(context).spaces;
|
return L10n.of(context).spaces;
|
||||||
|
case ActiveFilter.tag:
|
||||||
|
throw 'Tags should not directly be displayed!';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -73,6 +75,7 @@ class ChatListController extends State<ChatList>
|
||||||
StreamSubscription? _intentFileStreamSubscription;
|
StreamSubscription? _intentFileStreamSubscription;
|
||||||
|
|
||||||
late ActiveFilter activeFilter;
|
late ActiveFilter activeFilter;
|
||||||
|
String? activeTag;
|
||||||
|
|
||||||
String? _activeSpaceId;
|
String? _activeSpaceId;
|
||||||
String? get activeSpaceId => _activeSpaceId;
|
String? get activeSpaceId => _activeSpaceId;
|
||||||
|
|
@ -90,6 +93,8 @@ class ChatListController extends State<ChatList>
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> onChatTap(Room room) async {
|
Future<void> onChatTap(Room room) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
if (room.membership == Membership.invite) {
|
if (room.membership == Membership.invite) {
|
||||||
final joinResult = await showFutureLoadingDialog(
|
final joinResult = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -105,10 +110,11 @@ class ChatListController extends State<ChatList>
|
||||||
);
|
);
|
||||||
if (joinResult.error != null) return;
|
if (joinResult.error != null) return;
|
||||||
}
|
}
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
if (room.membership == Membership.ban) {
|
if (room.membership == Membership.ban) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(content: Text(L10n.of(context).youHaveBeenBannedFromThisChat)),
|
SnackBar(content: Text(l10n.youHaveBeenBannedFromThisChat)),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +144,8 @@ class ChatListController extends State<ChatList>
|
||||||
return (room) => room.isUnreadOrInvited;
|
return (room) => room.isUnreadOrInvited;
|
||||||
case ActiveFilter.spaces:
|
case ActiveFilter.spaces:
|
||||||
return (room) => room.isSpace;
|
return (room) => room.isSpace;
|
||||||
|
case ActiveFilter.tag:
|
||||||
|
return (room) => room.tags.keys.contains(activeTag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,23 +164,25 @@ class ChatListController extends State<ChatList>
|
||||||
static const String _serverStoreNamespace = 'im.fluffychat.search.server';
|
static const String _serverStoreNamespace = 'im.fluffychat.search.server';
|
||||||
|
|
||||||
Future<void> setServer() async {
|
Future<void> setServer() async {
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final newServer = await showTextInputDialog(
|
final newServer = await showTextInputDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
title: L10n.of(context).changeTheHomeserver,
|
title: l10n.changeTheHomeserver,
|
||||||
context: context,
|
context: context,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
prefixText: 'https://',
|
prefixText: 'https://',
|
||||||
hintText: Matrix.of(context).client.homeserver?.host,
|
hintText: matrix.client.homeserver?.host,
|
||||||
initialText: searchServer,
|
initialText: searchServer,
|
||||||
keyboardType: TextInputType.url,
|
keyboardType: TextInputType.url,
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
validator: (server) => server.contains('.') == true
|
validator: (server) =>
|
||||||
? null
|
server.contains('.') == true ? null : l10n.invalidServerName,
|
||||||
: L10n.of(context).invalidServerName,
|
|
||||||
);
|
);
|
||||||
if (newServer == null) return;
|
if (newServer == null) return;
|
||||||
Matrix.of(context).store.setString(_serverStoreNamespace, newServer);
|
if (!mounted) return;
|
||||||
|
matrix.store.setString(_serverStoreNamespace, newServer);
|
||||||
setState(() {
|
setState(() {
|
||||||
searchServer = newServer;
|
searchServer = newServer;
|
||||||
});
|
});
|
||||||
|
|
@ -185,6 +195,7 @@ class ChatListController extends State<ChatList>
|
||||||
|
|
||||||
Future<void> _search() async {
|
Future<void> _search() async {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
if (!isSearching) {
|
if (!isSearching) {
|
||||||
setState(() {
|
setState(() {
|
||||||
isSearching = true;
|
isSearching = true;
|
||||||
|
|
@ -227,9 +238,10 @@ class ChatListController extends State<ChatList>
|
||||||
);
|
);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Searching has crashed', e, s);
|
Logs().w('Searching has crashed', e, s);
|
||||||
ScaffoldMessenger.of(
|
if (!mounted) return;
|
||||||
context,
|
scaffoldMessenger.showSnackBar(
|
||||||
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
SnackBar(content: Text(e.toLocalizedString(context))),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (!isSearchMode) return;
|
if (!isSearchMode) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -293,9 +305,8 @@ class ChatListController extends State<ChatList>
|
||||||
|
|
||||||
Future<void> editSpace(BuildContext context, String spaceId) async {
|
Future<void> editSpace(BuildContext context, String spaceId) async {
|
||||||
await Matrix.of(context).client.getRoomById(spaceId)!.postLoad();
|
await Matrix.of(context).client.getRoomById(spaceId)!.postLoad();
|
||||||
if (mounted) {
|
if (!context.mounted) return;
|
||||||
context.push('/rooms/$spaceId/details');
|
context.push('/rooms/$spaceId/details');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Needs to match GroupsSpacesEntry for 'separate group' checking.
|
// Needs to match GroupsSpacesEntry for 'separate group' checking.
|
||||||
|
|
@ -305,11 +316,10 @@ class ChatListController extends State<ChatList>
|
||||||
String? get activeChat => widget.activeChat;
|
String? get activeChat => widget.activeChat;
|
||||||
|
|
||||||
void _processIncomingSharedMedia(List<SharedMediaFile> files) {
|
void _processIncomingSharedMedia(List<SharedMediaFile> files) {
|
||||||
|
files.removeWhere(
|
||||||
|
(file) => file.path.startsWith(AppConfig.deepLinkPrefix) == true,
|
||||||
|
);
|
||||||
if (files.isEmpty) return;
|
if (files.isEmpty) return;
|
||||||
inspect(files);
|
|
||||||
if (files.singleOrNull?.path.startsWith(AppConfig.deepLinkPrefix) == true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showScaffoldDialog(
|
showScaffoldDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -353,9 +363,10 @@ class ChatListController extends State<ChatList>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StreamSubscription? _onRoomTagUpdate;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
activeFilter = ActiveFilter.allChats;
|
|
||||||
_initReceiveSharingIntent();
|
_initReceiveSharingIntent();
|
||||||
_activeSpaceId = widget.activeSpace;
|
_activeSpaceId = widget.activeSpace;
|
||||||
|
|
||||||
|
|
@ -378,6 +389,32 @@ class ChatListController extends State<ChatList>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_updateRoomTags();
|
||||||
|
_onRoomTagUpdate = Matrix.of(context).client.onSync.stream
|
||||||
|
.where(
|
||||||
|
(syncUpdate) =>
|
||||||
|
syncUpdate.rooms?.join?.values.any(
|
||||||
|
(roomUpdate) =>
|
||||||
|
roomUpdate.accountData?.any(
|
||||||
|
(accountData) => accountData.type == 'm.tag',
|
||||||
|
) ??
|
||||||
|
false,
|
||||||
|
) ??
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.listen(_updateRoomTags);
|
||||||
|
|
||||||
|
if (roomTags.containsKey(AppSettings.chatFilter.value)) {
|
||||||
|
activeFilter = ActiveFilter.tag;
|
||||||
|
activeTag = AppSettings.chatFilter.value;
|
||||||
|
} else {
|
||||||
|
activeFilter =
|
||||||
|
ActiveFilter.values.singleWhereOrNull(
|
||||||
|
(filter) => AppSettings.chatFilter.value == filter.name,
|
||||||
|
) ??
|
||||||
|
ActiveFilter.allChats;
|
||||||
|
}
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -385,6 +422,7 @@ class ChatListController extends State<ChatList>
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_intentDataStreamSubscription?.cancel();
|
_intentDataStreamSubscription?.cancel();
|
||||||
_intentFileStreamSubscription?.cancel();
|
_intentFileStreamSubscription?.cancel();
|
||||||
|
_onRoomTagUpdate?.cancel();
|
||||||
scrollController.removeListener(_onScroll);
|
scrollController.removeListener(_onScroll);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -613,6 +651,30 @@ class ChatListController extends State<ChatList>
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (activeTag == null)
|
||||||
|
PopupMenuItem(
|
||||||
|
value: ChatContextAction.addTag,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: .min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.bookmark_add_outlined),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(L10n.of(context).addTag),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
PopupMenuItem(
|
||||||
|
value: ChatContextAction.removeTag,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: .min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.bookmark_remove_outlined),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(L10n.of(context).removeTag),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
if (spacesWithPowerLevels.isNotEmpty)
|
if (spacesWithPowerLevels.isNotEmpty)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: ChatContextAction.addToSpace,
|
value: ChatContextAction.addToSpace,
|
||||||
|
|
@ -742,6 +804,7 @@ class ChatListController extends State<ChatList>
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
if (space == null) return;
|
if (space == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => space.setSpaceChild(room.id),
|
future: () => space.setSpaceChild(room.id),
|
||||||
|
|
@ -752,9 +815,68 @@ class ChatListController extends State<ChatList>
|
||||||
future: () => room.setLowPriority(!room.isLowPriority),
|
future: () => room.setLowPriority(!room.isLowPriority),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
case ChatContextAction.addTag:
|
||||||
|
final existingTags = List.of(roomTags.keys);
|
||||||
|
existingTags.removeWhere(room.tags.containsKey);
|
||||||
|
String? tag;
|
||||||
|
if (existingTags.isNotEmpty) {
|
||||||
|
tag = await showModalActionPopup<String?>(
|
||||||
|
context: context,
|
||||||
|
actions: [
|
||||||
|
...existingTags.map((tag) {
|
||||||
|
final displayTag = tag.replaceFirst('u.', '');
|
||||||
|
return AdaptiveModalAction(
|
||||||
|
label: displayTag,
|
||||||
|
value: displayTag,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
AdaptiveModalAction(
|
||||||
|
label: L10n.of(context).createNewTag,
|
||||||
|
value: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
}
|
||||||
|
tag ??= await showTextInputDialog(
|
||||||
|
context: context,
|
||||||
|
title: L10n.of(context).addTag,
|
||||||
|
hintText: L10n.of(context).tagName,
|
||||||
|
);
|
||||||
|
final newTag = tag;
|
||||||
|
if (!mounted) return;
|
||||||
|
if (newTag == null) return;
|
||||||
|
await showFutureLoadingDialog(
|
||||||
|
context: context,
|
||||||
|
future: () => room.addTag('u.$newTag'),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
case ChatContextAction.removeTag:
|
||||||
|
await showFutureLoadingDialog(
|
||||||
|
context: context,
|
||||||
|
future: () => room.removeTag(activeTag!),
|
||||||
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, int> roomTags = {};
|
||||||
|
|
||||||
|
void _updateRoomTags([_]) {
|
||||||
|
roomTags.clear();
|
||||||
|
for (final room in Matrix.of(context).client.rooms) {
|
||||||
|
for (final tag in room.tags.keys) {
|
||||||
|
if (tag.startsWith('u.')) roomTags[tag] = (roomTags[tag] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
if (activeTag != null && !roomTags.keys.contains(activeTag)) {
|
||||||
|
activeTag = null;
|
||||||
|
activeFilter = ActiveFilter.allChats;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> dismissStatusList() async {
|
Future<void> dismissStatusList() async {
|
||||||
final result = await showOkCancelAlertDialog(
|
final result = await showOkCancelAlertDialog(
|
||||||
title: L10n.of(context).hidePresences,
|
title: L10n.of(context).hidePresences,
|
||||||
|
|
@ -767,16 +889,18 @@ class ChatListController extends State<ChatList>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setStatus() async {
|
Future<void> setStatus() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final currentPresence = await client.fetchCurrentPresence(client.userID!);
|
final currentPresence = await client.fetchCurrentPresence(client.userID!);
|
||||||
|
if (!mounted) return;
|
||||||
final input = await showTextInputDialog(
|
final input = await showTextInputDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).setStatus,
|
title: l10n.setStatus,
|
||||||
message: L10n.of(context).leaveEmptyToClearStatus,
|
message: l10n.leaveEmptyToClearStatus,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
hintText: L10n.of(context).statusExampleMessage,
|
hintText: l10n.statusExampleMessage,
|
||||||
maxLines: 6,
|
maxLines: 6,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLength: 255,
|
maxLength: 255,
|
||||||
|
|
@ -846,10 +970,17 @@ class ChatListController extends State<ChatList>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setActiveFilter(ActiveFilter filter) {
|
void setActiveFilter(ActiveFilter filter, String? tag) {
|
||||||
|
if (filter == ActiveFilter.tag && tag == null) {
|
||||||
|
throw ('Must set a tag when setting filter to tags!');
|
||||||
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
|
activeTag = tag;
|
||||||
activeFilter = filter;
|
activeFilter = filter;
|
||||||
});
|
});
|
||||||
|
AppSettings.chatFilter.setItem(
|
||||||
|
filter == ActiveFilter.tag ? tag! : filter.name,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setActiveClient(Client client) {
|
void setActiveClient(Client client) {
|
||||||
|
|
@ -904,18 +1035,21 @@ class ChatListController extends State<ChatList>
|
||||||
if (action == null) return;
|
if (action == null) return;
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case EditBundleAction.addToBundle:
|
case EditBundleAction.addToBundle:
|
||||||
|
if (!mounted) return;
|
||||||
final bundle = await showTextInputDialog(
|
final bundle = await showTextInputDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: l10n.bundleName,
|
title: l10n.bundleName,
|
||||||
hintText: l10n.bundleName,
|
hintText: l10n.bundleName,
|
||||||
);
|
);
|
||||||
if (bundle == null || bundle.isEmpty || bundle.isEmpty) return;
|
if (bundle == null || bundle.isEmpty || bundle.isEmpty) return;
|
||||||
|
if (!mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => client.setAccountBundle(bundle),
|
future: () => client.setAccountBundle(bundle),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case EditBundleAction.removeFromBundle:
|
case EditBundleAction.removeFromBundle:
|
||||||
|
if (!mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => client.removeFromAccountBundle(activeBundle!),
|
future: () => client.removeFromAccountBundle(activeBundle!),
|
||||||
|
|
@ -962,6 +1096,8 @@ enum ChatContextAction {
|
||||||
goToSpace,
|
goToSpace,
|
||||||
favorite,
|
favorite,
|
||||||
lowPriority,
|
lowPriority,
|
||||||
|
addTag,
|
||||||
|
removeTag,
|
||||||
markUnread,
|
markUnread,
|
||||||
mute,
|
mute,
|
||||||
leave,
|
leave,
|
||||||
|
|
|
||||||
|
|
@ -134,37 +134,40 @@ class ChatListViewBody extends StatelessWidget {
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
children:
|
children: [
|
||||||
[
|
...ActiveFilter.values
|
||||||
ActiveFilter.allChats,
|
.where((filter) => filter != ActiveFilter.tag)
|
||||||
|
.map(
|
||||||
if (spaces.isNotEmpty &&
|
(filter) => Padding(
|
||||||
!AppSettings
|
padding: const EdgeInsets.symmetric(
|
||||||
.displayNavigationRail
|
horizontal: 4.0,
|
||||||
.value &&
|
),
|
||||||
!FluffyThemes.isColumnMode(context))
|
child: FilterChip(
|
||||||
ActiveFilter.spaces,
|
selected: filter == controller.activeFilter,
|
||||||
ActiveFilter.unread,
|
onSelected: (_) => controller
|
||||||
ActiveFilter.groups,
|
.setActiveFilter(filter, null),
|
||||||
ActiveFilter.messages,
|
label: Text(
|
||||||
]
|
filter.toLocalizedString(context),
|
||||||
.map(
|
|
||||||
(filter) => Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 4.0,
|
|
||||||
),
|
|
||||||
child: FilterChip(
|
|
||||||
selected:
|
|
||||||
filter == controller.activeFilter,
|
|
||||||
onSelected: (_) =>
|
|
||||||
controller.setActiveFilter(filter),
|
|
||||||
label: Text(
|
|
||||||
filter.toLocalizedString(context),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
.toList(),
|
),
|
||||||
|
...controller.roomTags.entries.map(
|
||||||
|
(entry) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 4.0,
|
||||||
|
),
|
||||||
|
child: FilterChip(
|
||||||
|
selected: entry.key == controller.activeTag,
|
||||||
|
onSelected: (_) => controller.setActiveFilter(
|
||||||
|
ActiveFilter.tag,
|
||||||
|
entry.key,
|
||||||
|
),
|
||||||
|
label: Text(entry.key.replaceFirst('u.', '')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (controller.isSearchMode)
|
if (controller.isSearchMode)
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,15 @@ class ChatListItem extends StatelessWidget {
|
||||||
room.latestEventReceivedTime.localizedTimeShort(
|
room.latestEventReceivedTime.localizedTimeShort(
|
||||||
context,
|
context,
|
||||||
),
|
),
|
||||||
style: TextStyle(fontSize: 11),
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: room.hasNewMessages
|
||||||
|
? FontWeight.bold
|
||||||
|
: null,
|
||||||
|
color: hasNotifications
|
||||||
|
? theme.colorScheme.primary
|
||||||
|
: null,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,7 @@ class ClientChooserButton extends StatelessWidget {
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: L10n.of(context).cancel,
|
||||||
);
|
);
|
||||||
if (consent != OkCancelResult.ok) return;
|
if (consent != OkCancelResult.ok) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
context.go('/rooms/settings/addaccount');
|
context.go('/rooms/settings/addaccount');
|
||||||
break;
|
break;
|
||||||
case SettingsAction.newGroup:
|
case SettingsAction.newGroup:
|
||||||
|
|
|
||||||
|
|
@ -170,14 +170,17 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case SpaceActions.settings:
|
case SpaceActions.settings:
|
||||||
await space?.postLoad();
|
await space?.postLoad();
|
||||||
|
if (!mounted) return;
|
||||||
context.push('/rooms/${widget.spaceId}/details');
|
context.push('/rooms/${widget.spaceId}/details');
|
||||||
break;
|
break;
|
||||||
case SpaceActions.invite:
|
case SpaceActions.invite:
|
||||||
await space?.postLoad();
|
await space?.postLoad();
|
||||||
|
if (!mounted) return;
|
||||||
context.push('/rooms/${widget.spaceId}/invite');
|
context.push('/rooms/${widget.spaceId}/invite');
|
||||||
break;
|
break;
|
||||||
case SpaceActions.members:
|
case SpaceActions.members:
|
||||||
await space?.postLoad();
|
await space?.postLoad();
|
||||||
|
if (!mounted) return;
|
||||||
context.push('/rooms/${widget.spaceId}/details/members');
|
context.push('/rooms/${widget.spaceId}/details/members');
|
||||||
break;
|
break;
|
||||||
case SpaceActions.leave:
|
case SpaceActions.leave:
|
||||||
|
|
@ -524,14 +527,16 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final item = _discoveredChildren[i];
|
final item = _discoveredChildren[i];
|
||||||
|
var joinedRoom = room.client.getRoomById(item.roomId);
|
||||||
final displayname =
|
final displayname =
|
||||||
item.name ??
|
item.name ??
|
||||||
item.canonicalAlias ??
|
item.canonicalAlias ??
|
||||||
|
joinedRoom?.getLocalizedDisplayname() ??
|
||||||
L10n.of(context).emptyChat;
|
L10n.of(context).emptyChat;
|
||||||
|
final avatarUrl = item.avatarUrl ?? joinedRoom?.avatar;
|
||||||
if (!displayname.toLowerCase().contains(filter)) {
|
if (!displayname.toLowerCase().contains(filter)) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
var joinedRoom = room.client.getRoomById(item.roomId);
|
|
||||||
if (joinedRoom?.membership == Membership.leave) {
|
if (joinedRoom?.membership == Membership.leave) {
|
||||||
joinedRoom = null;
|
joinedRoom = null;
|
||||||
}
|
}
|
||||||
|
|
@ -595,7 +600,7 @@ class _SpaceViewState extends State<SpaceView> {
|
||||||
)
|
)
|
||||||
: Avatar(
|
: Avatar(
|
||||||
size: avatarSize,
|
size: avatarSize,
|
||||||
mxContent: item.avatarUrl,
|
mxContent: avatarUrl,
|
||||||
name: '#',
|
name: '#',
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.colorScheme.surfaceContainer,
|
theme.colorScheme.surfaceContainer,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
import '../../widgets/matrix.dart';
|
import '../../widgets/matrix.dart';
|
||||||
|
|
@ -39,7 +38,7 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
if (filter.isEmpty) {
|
if (filter.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
filteredMembers = members
|
filteredMembers = members
|
||||||
?..sort((b, a) => a.powerLevel.compareTo(b.powerLevel));
|
?..sort((b, a) => a.powerLevel.level.compareTo(b.powerLevel.level));
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +51,7 @@ class ChatMembersController extends State<ChatMembersPage> {
|
||||||
user.id.toLowerCase().contains(filter),
|
user.id.toLowerCase().contains(filter),
|
||||||
)
|
)
|
||||||
.toList()
|
.toList()
|
||||||
?..sort((b, a) => a.powerLevel.compareTo(b.powerLevel));
|
?..sort((b, a) => a.powerLevel.level.compareTo(b.powerLevel.level));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
|
||||||
currentLevel: currentLevel,
|
currentLevel: currentLevel,
|
||||||
);
|
);
|
||||||
if (newLevel == null) return;
|
if (newLevel == null) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
final content = Map<String, dynamic>.from(
|
final content = Map<String, dynamic>.from(
|
||||||
room.getState(EventTypes.RoomPowerLevels)!.content,
|
room.getState(EventTypes.RoomPowerLevels)!.content,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,15 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
||||||
Future<void> _checkChatBackup() async {
|
Future<void> _checkChatBackup() async {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final state = await client.getCryptoIdentityState();
|
final state = await client.getCryptoIdentityState();
|
||||||
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
chatBackupEnabled = state.initialized && !state.connected;
|
chatBackupEnabled = state.initialized && !state.connected;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeDevicesAction(List<Device> devices) async {
|
Future<void> removeDevicesAction(List<Device> devices) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
|
|
||||||
final wellKnown = await Result.capture(client.getWellknown());
|
final wellKnown = await Result.capture(client.getWellknown());
|
||||||
|
|
@ -57,18 +60,19 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
||||||
launchUrlString(accountManageUrl, mode: LaunchMode.inAppBrowserView);
|
launchUrlString(accountManageUrl, mode: LaunchMode.inAppBrowserView);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!mounted) return;
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: l10n.areYouSure,
|
||||||
okLabel: L10n.of(context).remove,
|
okLabel: l10n.remove,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
message: L10n.of(context).removeDevicesDescription,
|
message: l10n.removeDevicesDescription,
|
||||||
isDestructive: true,
|
isDestructive: true,
|
||||||
) ==
|
) ==
|
||||||
OkCancelResult.cancel) {
|
OkCancelResult.cancel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final matrix = Matrix.of(context);
|
if (!mounted) return;
|
||||||
final deviceIds = <String>[];
|
final deviceIds = <String>[];
|
||||||
for (final userDevice in devices) {
|
for (final userDevice in devices) {
|
||||||
deviceIds.add(userDevice.deviceId);
|
deviceIds.add(userDevice.deviceId);
|
||||||
|
|
@ -85,19 +89,21 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> renameDeviceAction(Device device) async {
|
Future<void> renameDeviceAction(Device device) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
final displayName = await showTextInputDialog(
|
final displayName = await showTextInputDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).changeDeviceName,
|
title: l10n.changeDeviceName,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
hintText: device.displayName,
|
hintText: device.displayName,
|
||||||
);
|
);
|
||||||
if (displayName == null) return;
|
if (displayName == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(
|
future: () =>
|
||||||
context,
|
matrix.client.updateDevice(device.deviceId, displayName: displayName),
|
||||||
).client.updateDevice(device.deviceId, displayName: displayName),
|
|
||||||
);
|
);
|
||||||
if (success.error == null) {
|
if (success.error == null) {
|
||||||
reload();
|
reload();
|
||||||
|
|
@ -105,17 +111,20 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> verifyDeviceAction(Device device) async {
|
Future<void> verifyDeviceAction(Device device) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
final consent = await showOkCancelAlertDialog(
|
final consent = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).verifyOtherDevice,
|
title: l10n.verifyOtherDevice,
|
||||||
message: L10n.of(context).verifyOtherDeviceDescription,
|
message: l10n.verifyOtherDeviceDescription,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
);
|
);
|
||||||
if (consent != OkCancelResult.ok) return;
|
if (consent != OkCancelResult.ok) return;
|
||||||
final req = await Matrix.of(context)
|
if (!mounted) return;
|
||||||
|
final req = await matrix
|
||||||
.client
|
.client
|
||||||
.userDeviceKeys[Matrix.of(context).client.userID!]!
|
.userDeviceKeys[matrix.client.userID!]!
|
||||||
.deviceKeys[device.deviceId]!
|
.deviceKeys[device.deviceId]!
|
||||||
.startVerification();
|
.startVerification();
|
||||||
req.onUpdate = () {
|
req.onUpdate = () {
|
||||||
|
|
@ -126,6 +135,7 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (!mounted) return;
|
||||||
await KeyVerificationDialog(request: req).show(context);
|
await KeyVerificationDialog(request: req).show(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,10 +81,12 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} on IOException catch (e) {
|
} on IOException catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
).showSnackBar(SnackBar(content: Text(e.toLocalizedString(context))));
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
if (!mounted) return;
|
||||||
ErrorReporter(context, 'Unable to play video').onErrorCallback(e, s);
|
ErrorReporter(context, 'Unable to play video').onErrorCallback(e, s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
Future<void> restoreBackupFlow(BuildContext context) async {
|
Future<void> restoreBackupFlow(BuildContext context) async {
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
final picked = await selectFiles(context);
|
final picked = await selectFiles(context);
|
||||||
final file = picked.firstOrNull;
|
final file = picked.firstOrNull;
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
|
|
@ -12,9 +13,9 @@ Future<void> restoreBackupFlow(BuildContext context) async {
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () async {
|
future: () async {
|
||||||
final client = await Matrix.of(context).getLoginClient();
|
final client = await matrix.getLoginClient();
|
||||||
await client.importDump(String.fromCharCodes(await file.readAsBytes()));
|
await client.importDump(String.fromCharCodes(await file.readAsBytes()));
|
||||||
Matrix.of(context).initMatrix();
|
matrix.initMatrix();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,7 @@ class IntroPage extends StatelessWidget {
|
||||||
final client = await Matrix.of(
|
final client = await Matrix.of(
|
||||||
context,
|
context,
|
||||||
).getLoginClient();
|
).getLoginClient();
|
||||||
|
if (!context.mounted) return;
|
||||||
context.go(
|
context.go(
|
||||||
'${GoRouterState.of(context).uri.path}/login',
|
'${GoRouterState.of(context).uri.path}/login',
|
||||||
extra: client,
|
extra: client,
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,8 @@ class _IntroPagePresenterState extends State<IntroPagePresenter> {
|
||||||
final client = await Matrix.of(context).getLoginClient();
|
final client = await Matrix.of(context).getLoginClient();
|
||||||
await client.checkHomeserver(homeserverUrl);
|
await client.checkHomeserver(homeserverUrl);
|
||||||
await client.oidcLogin(session: session, code: code, state: state);
|
await client.oidcLogin(session: session, code: code, state: state);
|
||||||
if (context.mounted) context.go('/backup');
|
if (!mounted) return;
|
||||||
|
context.go('/backup');
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Unable to login via OIDC', e, s);
|
Logs().w('Unable to login via OIDC', e, s);
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,8 @@ class InvitationSelectionController extends State<InvitationSelection> {
|
||||||
String id,
|
String id,
|
||||||
String displayname,
|
String displayname,
|
||||||
) async {
|
) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
final room = Matrix.of(context).client.getRoomById(roomId!)!;
|
final room = Matrix.of(context).client.getRoomById(roomId!)!;
|
||||||
|
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
|
|
@ -61,10 +63,9 @@ class InvitationSelectionController extends State<InvitationSelection> {
|
||||||
future: () => room.invite(id),
|
future: () => room.invite(id),
|
||||||
);
|
);
|
||||||
if (success.error == null) {
|
if (success.error == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
if (!context.mounted) return;
|
||||||
SnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
content: Text(L10n.of(context).contactHasBeenInvitedToTheGroup),
|
SnackBar(content: Text(l10n.contactHasBeenInvitedToTheGroup)),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,6 +92,7 @@ class InvitationSelectionController extends State<InvitationSelection> {
|
||||||
try {
|
try {
|
||||||
response = await matrix.client.searchUserDirectory(text, limit: 10);
|
response = await matrix.client.searchUserDirectory(text, limit: 10);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(SnackBar(content: Text((e).toLocalizedString(context))));
|
).showSnackBar(SnackBar(content: Text((e).toLocalizedString(context))));
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (valid.error != null) {
|
if (valid.error != null) {
|
||||||
|
if (!mounted) return;
|
||||||
await showOkAlertDialog(
|
await showOkAlertDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -178,9 +179,10 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
||||||
);
|
);
|
||||||
buttons.add(
|
buttons.add(
|
||||||
AdaptiveDialogAction(
|
AdaptiveDialogAction(
|
||||||
onPressed: () => widget.request.rejectVerification().then(
|
onPressed: () => widget.request.rejectVerification().then((_) {
|
||||||
(_) => Navigator.of(context, rootNavigator: false).pop(false),
|
if (!context.mounted) return;
|
||||||
),
|
Navigator.of(context, rootNavigator: false).pop(false);
|
||||||
|
}),
|
||||||
child: Text(
|
child: Text(
|
||||||
L10n.of(context).reject,
|
L10n.of(context).reject,
|
||||||
style: TextStyle(color: theme.colorScheme.error),
|
style: TextStyle(color: theme.colorScheme.error),
|
||||||
|
|
|
||||||
|
|
@ -130,15 +130,19 @@ class LoginController extends State<Login> {
|
||||||
Logs().v(
|
Logs().v(
|
||||||
'$newDomain is not running a homeserver, asking to use $oldHomeserver',
|
'$newDomain is not running a homeserver, asking to use $oldHomeserver',
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final dialogResult = await showOkCancelAlertDialog(
|
final dialogResult = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
title: L10n.of(
|
title: l10n.noMatrixServer(
|
||||||
context,
|
newDomain.toString(),
|
||||||
).noMatrixServer(newDomain.toString(), oldHomeserver.toString()),
|
oldHomeserver.toString(),
|
||||||
okLabel: L10n.of(context).ok,
|
),
|
||||||
cancelLabel: L10n.of(context).cancel,
|
okLabel: l10n.ok,
|
||||||
|
cancelLabel: l10n.cancel,
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
if (dialogResult == OkCancelResult.ok) {
|
if (dialogResult == OkCancelResult.ok) {
|
||||||
if (mounted) setState(() => usernameError = null);
|
if (mounted) setState(() => usernameError = null);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -156,26 +160,30 @@ class LoginController extends State<Login> {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
widget.client.homeserver = oldHomeserver;
|
widget.client.homeserver = oldHomeserver;
|
||||||
|
if (!mounted) return;
|
||||||
usernameError = e.toLocalizedString(context);
|
usernameError = e.toLocalizedString(context);
|
||||||
if (mounted) setState(() {});
|
if (mounted) setState(() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> passwordForgotten() async {
|
Future<void> passwordForgotten() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
final input = await showTextInputDialog(
|
final input = await showTextInputDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).passwordForgotten,
|
title: l10n.passwordForgotten,
|
||||||
message: L10n.of(context).enterAnEmailAddress,
|
message: l10n.enterAnEmailAddress,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
initialText: usernameController.text.isEmail
|
initialText: usernameController.text.isEmail
|
||||||
? usernameController.text
|
? usernameController.text
|
||||||
: '',
|
: '',
|
||||||
hintText: L10n.of(context).enterAnEmailAddress,
|
hintText: l10n.enterAnEmailAddress,
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
);
|
);
|
||||||
if (input == null) return;
|
if (input == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
|
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
final response = await showFutureLoadingDialog(
|
final response = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -186,27 +194,30 @@ class LoginController extends State<Login> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (response.error != null) return;
|
if (response.error != null) return;
|
||||||
|
if (!mounted) return;
|
||||||
final password = await showTextInputDialog(
|
final password = await showTextInputDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).passwordForgotten,
|
title: l10n.passwordForgotten,
|
||||||
message: L10n.of(context).chooseAStrongPassword,
|
message: l10n.chooseAStrongPassword,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
hintText: '******',
|
hintText: '******',
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
minLines: 1,
|
minLines: 1,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
);
|
);
|
||||||
if (password == null) return;
|
if (password == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
final ok = await showOkAlertDialog(
|
final ok = await showOkAlertDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).weSentYouAnEmail,
|
title: l10n.weSentYouAnEmail,
|
||||||
message: L10n.of(context).pleaseClickOnLink,
|
message: l10n.pleaseClickOnLink,
|
||||||
okLabel: L10n.of(context).iHaveClickedOnLink,
|
okLabel: l10n.iHaveClickedOnLink,
|
||||||
);
|
);
|
||||||
if (ok != OkCancelResult.ok) return;
|
if (ok != OkCancelResult.ok) return;
|
||||||
|
if (!mounted) return;
|
||||||
final data = <String, dynamic>{
|
final data = <String, dynamic>{
|
||||||
'new_password': password,
|
'new_password': password,
|
||||||
'logout_devices': false,
|
'logout_devices': false,
|
||||||
|
|
@ -226,9 +237,10 @@ class LoginController extends State<Login> {
|
||||||
data: data,
|
data: data,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
if (success.error == null) {
|
if (success.error == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)),
|
SnackBar(content: Text(l10n.passwordHasBeenChanged)),
|
||||||
);
|
);
|
||||||
usernameController.text = input;
|
usernameController.text = input;
|
||||||
passwordController.text = password;
|
passwordController.text = password;
|
||||||
|
|
|
||||||
|
|
@ -81,17 +81,19 @@ class NewPrivateChatController extends State<NewPrivateChat> {
|
||||||
void inviteAction() => FluffyShare.shareInviteLink(context);
|
void inviteAction() => FluffyShare.shareInviteLink(context);
|
||||||
|
|
||||||
Future<void> openScannerAction() async {
|
Future<void> openScannerAction() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
if (PlatformInfos.isAndroid) {
|
if (PlatformInfos.isAndroid) {
|
||||||
final info = await DeviceInfoPlugin().androidInfo;
|
final info = await DeviceInfoPlugin().androidInfo;
|
||||||
|
if (!mounted) return;
|
||||||
if (info.version.sdkInt < 21) {
|
if (info.version.sdkInt < 21) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(content: Text(l10n.unsupportedAndroidVersionLong)),
|
||||||
content: Text(L10n.of(context).unsupportedAndroidVersionLong),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!mounted) return;
|
||||||
await showAdaptiveBottomSheet(
|
await showAdaptiveBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => QrScannerModal(
|
builder: (_) => QrScannerModal(
|
||||||
|
|
@ -101,12 +103,15 @@ class NewPrivateChatController extends State<NewPrivateChat> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> copyUserId() async {
|
Future<void> copyUserId() async {
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
|
final l10n = L10n.of(context);
|
||||||
await Clipboard.setData(
|
await Clipboard.setData(
|
||||||
ClipboardData(text: Matrix.of(context).client.userID!),
|
ClipboardData(text: Matrix.of(context).client.userID!),
|
||||||
);
|
);
|
||||||
ScaffoldMessenger.of(
|
if (!mounted) return;
|
||||||
context,
|
scaffoldMessenger.showSnackBar(
|
||||||
).showSnackBar(SnackBar(content: Text(L10n.of(context).copiedToClipboard)));
|
SnackBar(content: Text(l10n.copiedToClipboard)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void openUserModal(Profile profile) =>
|
void openUserModal(Profile profile) =>
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ class QrScannerModalState extends State<QrScannerModal> {
|
||||||
late StreamSubscription sub;
|
late StreamSubscription sub;
|
||||||
sub = controller.scannedDataStream.listen((scanData) {
|
sub = controller.scannedDataStream.listen((scanData) {
|
||||||
sub.cancel();
|
sub.cancel();
|
||||||
|
if (!mounted) return;
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
final data = scanData.code;
|
final data = scanData.code;
|
||||||
if (data != null) widget.onScan(data);
|
if (data != null) widget.onScan(data);
|
||||||
|
|
|
||||||
|
|
@ -37,18 +37,20 @@ class SettingsController extends State<Settings> {
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> setDisplaynameAction() async {
|
Future<void> setDisplaynameAction() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
final profile = await profileFuture;
|
final profile = await profileFuture;
|
||||||
|
if (!mounted) return;
|
||||||
final input = await showTextInputDialog(
|
final input = await showTextInputDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).editDisplayname,
|
title: l10n.editDisplayname,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
initialText:
|
initialText: profile?.displayName ?? matrix.client.userID!.localpart,
|
||||||
profile?.displayName ?? Matrix.of(context).client.userID!.localpart,
|
|
||||||
);
|
);
|
||||||
if (input == null) return;
|
if (input == null) return;
|
||||||
final matrix = Matrix.of(context);
|
if (!mounted) return;
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => matrix.client.setProfileField(
|
future: () => matrix.client.setProfileField(
|
||||||
|
|
@ -63,19 +65,19 @@ class SettingsController extends State<Settings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> logoutAction() async {
|
Future<void> logoutAction() async {
|
||||||
if (await showOkCancelAlertDialog(
|
final l10n = L10n.of(context);
|
||||||
useRootNavigator: false,
|
|
||||||
context: context,
|
|
||||||
title: L10n.of(context).areYouSureYouWantToLogout,
|
|
||||||
message: L10n.of(context).noBackupWarning,
|
|
||||||
isDestructive: cryptoIdentityConnected == false,
|
|
||||||
okLabel: L10n.of(context).logout,
|
|
||||||
cancelLabel: L10n.of(context).cancel,
|
|
||||||
) ==
|
|
||||||
OkCancelResult.cancel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final matrix = Matrix.of(context);
|
final matrix = Matrix.of(context);
|
||||||
|
final consent = await showOkCancelAlertDialog(
|
||||||
|
useRootNavigator: false,
|
||||||
|
context: context,
|
||||||
|
title: l10n.areYouSureYouWantToLogout,
|
||||||
|
message: l10n.noBackupWarning,
|
||||||
|
isDestructive: cryptoIdentityConnected == false,
|
||||||
|
okLabel: l10n.logout,
|
||||||
|
cancelLabel: l10n.cancel,
|
||||||
|
);
|
||||||
|
if (consent != OkCancelResult.ok) return;
|
||||||
|
if (!mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => matrix.client.logout(),
|
future: () => matrix.client.logout(),
|
||||||
|
|
@ -83,24 +85,27 @@ class SettingsController extends State<Settings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setAvatarAction() async {
|
Future<void> setAvatarAction() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
final profile = await profileFuture;
|
final profile = await profileFuture;
|
||||||
|
if (!mounted) return;
|
||||||
final actions = [
|
final actions = [
|
||||||
if (PlatformInfos.isMobile)
|
if (PlatformInfos.isMobile)
|
||||||
AdaptiveModalAction(
|
AdaptiveModalAction(
|
||||||
value: AvatarAction.camera,
|
value: AvatarAction.camera,
|
||||||
label: L10n.of(context).openCamera,
|
label: l10n.openCamera,
|
||||||
isDefaultAction: true,
|
isDefaultAction: true,
|
||||||
icon: const Icon(Icons.camera_alt_outlined),
|
icon: const Icon(Icons.camera_alt_outlined),
|
||||||
),
|
),
|
||||||
AdaptiveModalAction(
|
AdaptiveModalAction(
|
||||||
value: AvatarAction.file,
|
value: AvatarAction.file,
|
||||||
label: L10n.of(context).openGallery,
|
label: l10n.openGallery,
|
||||||
icon: const Icon(Icons.photo_outlined),
|
icon: const Icon(Icons.photo_outlined),
|
||||||
),
|
),
|
||||||
if (profile?.avatarUrl != null)
|
if (profile?.avatarUrl != null)
|
||||||
AdaptiveModalAction(
|
AdaptiveModalAction(
|
||||||
value: AvatarAction.remove,
|
value: AvatarAction.remove,
|
||||||
label: L10n.of(context).removeYourAvatar,
|
label: l10n.removeYourAvatar,
|
||||||
isDestructive: true,
|
isDestructive: true,
|
||||||
icon: const Icon(Icons.delete_outlined),
|
icon: const Icon(Icons.delete_outlined),
|
||||||
),
|
),
|
||||||
|
|
@ -109,12 +114,12 @@ class SettingsController extends State<Settings> {
|
||||||
? actions.single.value
|
? actions.single.value
|
||||||
: await showModalActionPopup<AvatarAction>(
|
: await showModalActionPopup<AvatarAction>(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).changeYourAvatar,
|
title: l10n.changeYourAvatar,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
if (action == null) return;
|
if (action == null) return;
|
||||||
final matrix = Matrix.of(context);
|
if (!mounted) return;
|
||||||
if (action == AvatarAction.remove) {
|
if (action == AvatarAction.remove) {
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -139,12 +144,14 @@ class SettingsController extends State<Settings> {
|
||||||
bytes = await result.readAsBytes();
|
bytes = await result.readAsBytes();
|
||||||
name = result.path;
|
name = result.path;
|
||||||
} else {
|
} else {
|
||||||
|
if (!mounted) return;
|
||||||
final result = await selectFiles(context, type: FileType.image);
|
final result = await selectFiles(context, type: FileType.image);
|
||||||
final pickedFile = result.firstOrNull;
|
final pickedFile = result.firstOrNull;
|
||||||
if (pickedFile == null) return;
|
if (pickedFile == null) return;
|
||||||
bytes = await pickedFile.readAsBytes();
|
bytes = await pickedFile.readAsBytes();
|
||||||
name = pickedFile.name;
|
name = pickedFile.name;
|
||||||
}
|
}
|
||||||
|
if (!mounted) return;
|
||||||
final cropped = await showDialog<Uint8List>(
|
final cropped = await showDialog<Uint8List>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (contect) => AvatarCropDialog(image: bytes),
|
builder: (contect) => AvatarCropDialog(image: bytes),
|
||||||
|
|
@ -155,6 +162,7 @@ class SettingsController extends State<Settings> {
|
||||||
bytes: bytes,
|
bytes: bytes,
|
||||||
name: name,
|
name: name,
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => matrix.client.setAvatar(file),
|
future: () => matrix.client.setAvatar(file),
|
||||||
|
|
@ -181,6 +189,7 @@ class SettingsController extends State<Settings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final state = await client.getCryptoIdentityState();
|
final state = await client.getCryptoIdentityState();
|
||||||
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
cryptoIdentityConnected = state.initialized && state.connected;
|
cryptoIdentityConnected = state.initialized && state.connected;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -19,41 +19,48 @@ class Settings3Pid extends StatefulWidget {
|
||||||
|
|
||||||
class Settings3PidController extends State<Settings3Pid> {
|
class Settings3PidController extends State<Settings3Pid> {
|
||||||
Future<void> add3PidAction() async {
|
Future<void> add3PidAction() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
final input = await showTextInputDialog(
|
final input = await showTextInputDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).enterAnEmailAddress,
|
title: l10n.enterAnEmailAddress,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
hintText: L10n.of(context).enterAnEmailAddress,
|
hintText: l10n.enterAnEmailAddress,
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
);
|
);
|
||||||
if (input == null) return;
|
if (input == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
|
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
final response = await showFutureLoadingDialog(
|
final response = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(context).client.requestTokenToRegisterEmail(
|
future: () => matrix.client.requestTokenToRegisterEmail(
|
||||||
clientSecret,
|
clientSecret,
|
||||||
input,
|
input,
|
||||||
Settings3Pid.sendAttempt++,
|
Settings3Pid.sendAttempt++,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (response.error != null) return;
|
if (response.error != null) return;
|
||||||
|
if (!mounted) return;
|
||||||
final ok = await showOkAlertDialog(
|
final ok = await showOkAlertDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).weSentYouAnEmail,
|
title: l10n.weSentYouAnEmail,
|
||||||
message: L10n.of(context).pleaseClickOnLink,
|
message: l10n.pleaseClickOnLink,
|
||||||
okLabel: L10n.of(context).iHaveClickedOnLink,
|
okLabel: l10n.iHaveClickedOnLink,
|
||||||
);
|
);
|
||||||
if (ok != OkCancelResult.ok) return;
|
if (ok != OkCancelResult.ok) return;
|
||||||
|
if (!mounted) return;
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
delay: false,
|
delay: false,
|
||||||
future: () => Matrix.of(context).client.uiaRequestBackground(
|
future: () => matrix.client.uiaRequestBackground(
|
||||||
(auth) => Matrix.of(
|
(auth) => matrix.client.add3PID(
|
||||||
context,
|
clientSecret,
|
||||||
).client.add3PID(clientSecret, response.result!.sid, auth: auth),
|
response.result!.sid,
|
||||||
|
auth: auth,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (success.error != null) return;
|
if (success.error != null) return;
|
||||||
|
|
@ -63,21 +70,25 @@ class Settings3PidController extends State<Settings3Pid> {
|
||||||
Future<List<ThirdPartyIdentifier>?>? request;
|
Future<List<ThirdPartyIdentifier>?>? request;
|
||||||
|
|
||||||
Future<void> delete3Pid(ThirdPartyIdentifier identifier) async {
|
Future<void> delete3Pid(ThirdPartyIdentifier identifier) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: l10n.areYouSure,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: l10n.yes,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
) !=
|
) !=
|
||||||
OkCancelResult.ok) {
|
OkCancelResult.ok) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!mounted) return;
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(
|
future: () => matrix.client.delete3pidFromAccount(
|
||||||
context,
|
identifier.address,
|
||||||
).client.delete3pidFromAccount(identifier.address, identifier.medium),
|
identifier.medium,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
if (success.error != null) return;
|
if (success.error != null) return;
|
||||||
setState(() => request = null);
|
setState(() => request = null);
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _addEmotePack() async {
|
Future<void> _addEmotePack() async {
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
setState(() {
|
setState(() {
|
||||||
_loading = true;
|
_loading = true;
|
||||||
_progress = 0;
|
_progress = 0;
|
||||||
|
|
@ -148,7 +149,7 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
|
||||||
} else {
|
} else {
|
||||||
mxcFile = thumbnail;
|
mxcFile = thumbnail;
|
||||||
}
|
}
|
||||||
final uri = await Matrix.of(context).client.uploadContent(
|
final uri = await matrix.client.uploadContent(
|
||||||
mxcFile.bytes,
|
mxcFile.bytes,
|
||||||
filename: mxcFile.name,
|
filename: mxcFile.name,
|
||||||
contentType: mxcFile.mimeType,
|
contentType: mxcFile.mimeType,
|
||||||
|
|
@ -178,6 +179,7 @@ class _ImportEmoteArchiveDialogState extends State<ImportEmoteArchiveDialog> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
await widget.controller.save(context);
|
await widget.controller.save(context);
|
||||||
_importMap.removeWhere(
|
_importMap.removeWhere(
|
||||||
(key, value) => successfulUploads.contains(key.name),
|
(key, value) => successfulUploads.contains(key.name),
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> createStickers() async {
|
Future<void> createStickers() async {
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
final pickedFiles = await selectFiles(
|
final pickedFiles = await selectFiles(
|
||||||
context,
|
context,
|
||||||
type: FileType.image,
|
type: FileType.image,
|
||||||
|
|
@ -315,7 +316,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
nativeImplementations: ClientManager.nativeImplementations,
|
nativeImplementations: ClientManager.nativeImplementations,
|
||||||
) ??
|
) ??
|
||||||
file;
|
file;
|
||||||
final uri = await Matrix.of(context).client.uploadContent(
|
final uri = await matrix.client.uploadContent(
|
||||||
file.bytes,
|
file.bytes,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
contentType: file.mimeType,
|
contentType: file.mimeType,
|
||||||
|
|
@ -361,6 +362,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
final buffer = InputMemoryStream(await result.single.readAsBytes());
|
final buffer = InputMemoryStream(await result.single.readAsBytes());
|
||||||
|
|
||||||
final archive = ZipDecoder().decodeStream(buffer);
|
final archive = ZipDecoder().decodeStream(buffer);
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -375,7 +377,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
Future<void> exportAsZip() async {
|
Future<void> exportAsZip() async {
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
|
|
||||||
await showFutureLoadingDialog(
|
final result = await showFutureLoadingDialog<MatrixFile>(
|
||||||
context: context,
|
context: context,
|
||||||
future: () async {
|
future: () async {
|
||||||
final pack = _getPack();
|
final pack = _getPack();
|
||||||
|
|
@ -397,11 +399,12 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
||||||
'${pack.pack.displayName ?? client.userID?.localpart ?? 'emotes'}.zip';
|
'${pack.pack.displayName ?? client.userID?.localpart ?? 'emotes'}.zip';
|
||||||
final output = ZipEncoder().encode(archive);
|
final output = ZipEncoder().encode(archive);
|
||||||
|
|
||||||
MatrixFile(
|
return MatrixFile(name: fileName, bytes: Uint8List.fromList(output));
|
||||||
name: fileName,
|
|
||||||
bytes: Uint8List.fromList(output),
|
|
||||||
).save(context);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
final file = result.result;
|
||||||
|
if (file == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
file.save(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (delete != true) return;
|
if (delete != true) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
final success = await showFutureLoadingDialog(
|
final success = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
import 'package:fluffychat/config/setting_keys.dart';
|
||||||
import 'package:fluffychat/config/themes.dart';
|
import 'package:fluffychat/config/themes.dart';
|
||||||
import 'package:fluffychat/l10n/l10n.dart';
|
import 'package:fluffychat/l10n/l10n.dart';
|
||||||
import 'package:fluffychat/pages/settings_notifications/push_rule_extensions.dart';
|
import 'package:fluffychat/pages/settings_notifications/push_rule_extensions.dart';
|
||||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||||
|
import 'package:fluffychat/widgets/settings_switch_list_tile.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
|
@ -47,6 +50,11 @@ class SettingsNotificationsView extends StatelessWidget {
|
||||||
return SelectionArea(
|
return SelectionArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
if (kIsWeb)
|
||||||
|
SettingsSwitchListTile.adaptive(
|
||||||
|
title: L10n.of(context).playSoundOnNotification,
|
||||||
|
setting: AppSettings.webNotificationSound,
|
||||||
|
),
|
||||||
if (pushRules != null)
|
if (pushRules != null)
|
||||||
for (final category in pushCategories) ...[
|
for (final category in pushCategories) ...[
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ class SettingsPasswordController extends State<SettingsPassword> {
|
||||||
bool loading = false;
|
bool loading = false;
|
||||||
|
|
||||||
Future<void> changePassword() async {
|
Future<void> changePassword() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
setState(() {
|
setState(() {
|
||||||
oldPasswordError = newPassword1Error = newPassword2Error = null;
|
oldPasswordError = newPassword1Error = newPassword2Error = null;
|
||||||
});
|
});
|
||||||
|
|
@ -51,13 +53,13 @@ class SettingsPasswordController extends State<SettingsPassword> {
|
||||||
loading = true;
|
loading = true;
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
|
||||||
await Matrix.of(context).client.changePassword(
|
await Matrix.of(context).client.changePassword(
|
||||||
newPassword1Controller.text,
|
newPassword1Controller.text,
|
||||||
oldPassword: oldPasswordController.text,
|
oldPassword: oldPasswordController.text,
|
||||||
);
|
);
|
||||||
|
if (!mounted) return;
|
||||||
scaffoldMessenger.showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)),
|
SnackBar(content: Text(l10n.passwordHasBeenChanged)),
|
||||||
);
|
);
|
||||||
if (mounted) context.pop();
|
if (mounted) context.pop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -19,20 +19,21 @@ class SettingsSecurity extends StatefulWidget {
|
||||||
|
|
||||||
class SettingsSecurityController extends State<SettingsSecurity> {
|
class SettingsSecurityController extends State<SettingsSecurity> {
|
||||||
Future<void> setAppLockAction() async {
|
Future<void> setAppLockAction() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
if (AppLock.of(context).isActive) {
|
if (AppLock.of(context).isActive) {
|
||||||
AppLock.of(context).showLockScreen();
|
AppLock.of(context).showLockScreen();
|
||||||
}
|
}
|
||||||
final newLock = await showTextInputDialog(
|
final newLock = await showTextInputDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).pleaseChooseAPasscode,
|
title: l10n.pleaseChooseAPasscode,
|
||||||
message: L10n.of(context).pleaseEnter4Digits,
|
message: l10n.pleaseEnter4Digits,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
validator: (text) {
|
validator: (text) {
|
||||||
if (text.isEmpty || (text.length == 4 && int.tryParse(text)! >= 0)) {
|
if (text.isEmpty || (text.length == 4 && int.tryParse(text)! >= 0)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return L10n.of(context).pleaseEnter4Digits;
|
return l10n.pleaseEnter4Digits;
|
||||||
},
|
},
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
obscureText: true,
|
obscureText: true,
|
||||||
|
|
@ -41,53 +42,55 @@ class SettingsSecurityController extends State<SettingsSecurity> {
|
||||||
maxLength: 4,
|
maxLength: 4,
|
||||||
);
|
);
|
||||||
if (newLock != null) {
|
if (newLock != null) {
|
||||||
|
if (!mounted) return;
|
||||||
await AppLock.of(context).changePincode(newLock);
|
await AppLock.of(context).changePincode(newLock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteAccountAction() async {
|
Future<void> deleteAccountAction() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final matrix = Matrix.of(context);
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).warning,
|
title: l10n.warning,
|
||||||
message: L10n.of(context).deactivateAccountWarning,
|
message: l10n.deactivateAccountWarning,
|
||||||
okLabel: L10n.of(context).ok,
|
okLabel: l10n.ok,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
isDestructive: true,
|
isDestructive: true,
|
||||||
) ==
|
) ==
|
||||||
OkCancelResult.cancel) {
|
OkCancelResult.cancel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final supposedMxid = Matrix.of(context).client.userID!;
|
if (!mounted) return;
|
||||||
|
final supposedMxid = matrix.client.userID!;
|
||||||
final mxid = await showTextInputDialog(
|
final mxid = await showTextInputDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).confirmMatrixId,
|
title: l10n.confirmMatrixId,
|
||||||
validator: (text) => text == supposedMxid
|
validator: (text) =>
|
||||||
? null
|
text == supposedMxid ? null : l10n.supposedMxid(supposedMxid),
|
||||||
: L10n.of(context).supposedMxid(supposedMxid),
|
|
||||||
isDestructive: true,
|
isDestructive: true,
|
||||||
okLabel: L10n.of(context).delete,
|
okLabel: l10n.delete,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
);
|
);
|
||||||
if (mxid == null || mxid.isEmpty || mxid != supposedMxid) {
|
if (mxid == null || mxid.isEmpty || mxid != supposedMxid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!mounted) return;
|
||||||
final resp = await showFutureLoadingDialog(
|
final resp = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
delay: false,
|
delay: false,
|
||||||
future: () =>
|
future: () => matrix.client.uiaRequestBackground<IdServerUnbindResult?>(
|
||||||
Matrix.of(context).client.uiaRequestBackground<IdServerUnbindResult?>(
|
(auth) => matrix.client.deactivateAccount(auth: auth, erase: true),
|
||||||
(auth) => Matrix.of(
|
),
|
||||||
context,
|
|
||||||
).client.deactivateAccount(auth: auth, erase: true),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!resp.isError) {
|
if (!resp.isError) {
|
||||||
|
if (!mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(context).client.logout(),
|
future: () => matrix.client.logout(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class SettingsStyleController extends State<SettingsStyle> {
|
||||||
final picked = await selectFiles(context, type: FileType.image);
|
final picked = await selectFiles(context, type: FileType.image);
|
||||||
final pickedFile = picked.firstOrNull;
|
final pickedFile = picked.firstOrNull;
|
||||||
if (pickedFile == null) return;
|
if (pickedFile == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ class SignInPage extends StatelessWidget {
|
||||||
SizedBox.square(
|
SizedBox.square(
|
||||||
dimension: 32,
|
dimension: 32,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
tooltip: website,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.open_in_new_outlined,
|
Icons.open_in_new_outlined,
|
||||||
size: 16,
|
size: 16,
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ class BackgroundPush {
|
||||||
//<GOOGLE_SERVICES>firebase.setListeners(
|
//<GOOGLE_SERVICES>firebase.setListeners(
|
||||||
//<GOOGLE_SERVICES> onMessage: (message) => pushHelper(
|
//<GOOGLE_SERVICES> onMessage: (message) => pushHelper(
|
||||||
//<GOOGLE_SERVICES> PushNotification.fromJson(
|
//<GOOGLE_SERVICES> PushNotification.fromJson(
|
||||||
//<GOOGLE_SERVICES> Map<String, dynamic>.from(message['data'] ?? message),
|
//<GOOGLE_SERVICES> message.tryGetMap<String, Object>('data') ?? message,
|
||||||
//<GOOGLE_SERVICES> ),
|
//<GOOGLE_SERVICES> ),
|
||||||
//<GOOGLE_SERVICES> client: client,
|
//<GOOGLE_SERVICES> client: client,
|
||||||
//<GOOGLE_SERVICES> l10n: l10n,
|
//<GOOGLE_SERVICES> l10n: l10n,
|
||||||
|
|
@ -351,6 +351,9 @@ class BackgroundPush {
|
||||||
Future<void> setupFirebase() async {
|
Future<void> setupFirebase() async {
|
||||||
Logs().v('Setup firebase');
|
Logs().v('Setup firebase');
|
||||||
if (_fcmToken?.isEmpty ?? true) {
|
if (_fcmToken?.isEmpty ?? true) {
|
||||||
|
if (PlatformInfos.isIOS) {
|
||||||
|
//<GOOGLE_SERVICES>await firebase.requestPermission();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
//<GOOGLE_SERVICES>_fcmToken = await firebase.getToken();
|
//<GOOGLE_SERVICES>_fcmToken = await firebase.getToken();
|
||||||
if (_fcmToken == null) throw ('PushToken is null');
|
if (_fcmToken == null) throw ('PushToken is null');
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ Future<List<XFile>> selectFiles(
|
||||||
final result = await AppLock.of(context).pauseWhile(
|
final result = await AppLock.of(context).pauseWhile(
|
||||||
showFutureLoadingDialog(
|
showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => FilePicker.platform.pickFiles(
|
future: () => FilePicker.pickFiles(
|
||||||
compressionQuality: 0,
|
compressionQuality: 0,
|
||||||
allowMultiple: allowMultiple,
|
allowMultiple: allowMultiple,
|
||||||
type: type,
|
type: type,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ abstract class FluffyShare {
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
bool copyOnly = false,
|
bool copyOnly = false,
|
||||||
}) async {
|
}) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
if (PlatformInfos.isMobile && !copyOnly) {
|
if (PlatformInfos.isMobile && !copyOnly) {
|
||||||
final box = context.findRenderObject() as RenderBox;
|
final box = context.findRenderObject() as RenderBox;
|
||||||
await SharePlus.instance.share(
|
await SharePlus.instance.share(
|
||||||
|
|
@ -24,21 +26,20 @@ abstract class FluffyShare {
|
||||||
}
|
}
|
||||||
await Clipboard.setData(ClipboardData(text: text));
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
if (!PlatformInfos.isMobile) {
|
if (!PlatformInfos.isMobile) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(showCloseIcon: true, content: Text(l10n.copiedToClipboard)),
|
||||||
showCloseIcon: true,
|
|
||||||
content: Text(L10n.of(context).copiedToClipboard),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> shareInviteLink(BuildContext context) async {
|
static Future<void> shareInviteLink(BuildContext context) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final ownProfile = await client.fetchOwnProfile();
|
final ownProfile = await client.fetchOwnProfile();
|
||||||
|
if (!context.mounted) return;
|
||||||
await FluffyShare.share(
|
await FluffyShare.share(
|
||||||
L10n.of(context).inviteText(
|
l10n.inviteText(
|
||||||
ownProfile.displayName ?? client.userID!,
|
ownProfile.displayName ?? client.userID!,
|
||||||
'https://matrix.to/#/${client.userID}?client=im.fluffychat',
|
'https://matrix.to/#/${client.userID}?client=im.fluffychat',
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -25,12 +25,14 @@ extension LocalizedBody on Event {
|
||||||
|
|
||||||
Future<void> saveFile(BuildContext context) async {
|
Future<void> saveFile(BuildContext context) async {
|
||||||
final matrixFile = await _getFile(context);
|
final matrixFile = await _getFile(context);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
matrixFile.result?.save(context);
|
matrixFile.result?.save(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> shareFile(BuildContext context) async {
|
Future<void> shareFile(BuildContext context) async {
|
||||||
final matrixFile = await _getFile(context);
|
final matrixFile = await _getFile(context);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
matrixFile.result?.share(context);
|
matrixFile.result?.share(context);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ extension MatrixFileExtension on MatrixFile {
|
||||||
Future<void> save(BuildContext context) async {
|
Future<void> save(BuildContext context) async {
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
final l10n = L10n.of(context);
|
final l10n = L10n.of(context);
|
||||||
final downloadPath = await FilePicker.platform.saveFile(
|
final downloadPath = await FilePicker.saveFile(
|
||||||
dialogTitle: l10n.saveFile,
|
dialogTitle: l10n.saveFile,
|
||||||
fileName: name,
|
fileName: name,
|
||||||
type: filePickerFileType,
|
type: filePickerFileType,
|
||||||
|
|
|
||||||
|
|
@ -47,15 +47,17 @@ abstract class PlatformInfos {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> showDialog(BuildContext context) async {
|
static Future<void> showDialog(BuildContext context) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final version = await PlatformInfos.getVersion();
|
final version = await PlatformInfos.getVersion();
|
||||||
|
if (!context.mounted) return;
|
||||||
showAboutDialog(
|
showAboutDialog(
|
||||||
context: context,
|
context: context,
|
||||||
children: [
|
children: [
|
||||||
Text(L10n.of(context).versionWithNumber(version)),
|
Text(l10n.versionWithNumber(version)),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () => launchUrlString(AppConfig.sourceCodeUrl),
|
onPressed: () => launchUrlString(AppConfig.sourceCodeUrl),
|
||||||
icon: const Icon(Icons.source_outlined),
|
icon: const Icon(Icons.source_outlined),
|
||||||
label: Text(L10n.of(context).sourceCode),
|
label: Text(l10n.sourceCode),
|
||||||
),
|
),
|
||||||
Builder(
|
Builder(
|
||||||
builder: (innerContext) {
|
builder: (innerContext) {
|
||||||
|
|
@ -65,7 +67,7 @@ abstract class PlatformInfos {
|
||||||
Navigator.of(innerContext).pop();
|
Navigator.of(innerContext).pop();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.list_outlined),
|
icon: const Icon(Icons.list_outlined),
|
||||||
label: Text(L10n.of(context).logs),
|
label: Text(l10n.logs),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -77,7 +79,7 @@ abstract class PlatformInfos {
|
||||||
Navigator.of(innerContext).pop();
|
Navigator.of(innerContext).pop();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.settings_applications_outlined),
|
icon: const Icon(Icons.settings_applications_outlined),
|
||||||
label: Text(L10n.of(context).advancedConfigs),
|
label: Text(l10n.advancedConfigs),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ import 'package:flutter_shortcuts_new/flutter_shortcuts_new.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
const notificationAvatarDimension = 128;
|
const notificationAvatarDimension = 128;
|
||||||
|
const String groupKey = 'im.fluffychat.messages';
|
||||||
|
const int summaryId = -1;
|
||||||
|
|
||||||
Future<void> pushHelper(
|
Future<void> pushHelper(
|
||||||
PushNotification notification, {
|
PushNotification notification, {
|
||||||
|
|
@ -53,6 +55,7 @@ Future<void> pushHelper(
|
||||||
AppSettings.applicationName.value,
|
AppSettings.applicationName.value,
|
||||||
(notification.counts?.unread ?? 0).toString(),
|
(notification.counts?.unread ?? 0).toString(),
|
||||||
),
|
),
|
||||||
|
groupKey: groupKey,
|
||||||
importance: Importance.high,
|
importance: Importance.high,
|
||||||
priority: Priority.max,
|
priority: Priority.max,
|
||||||
shortcutId: notification.roomId,
|
shortcutId: notification.roomId,
|
||||||
|
|
@ -252,7 +255,7 @@ Future<void> _tryPushHelper(
|
||||||
),
|
),
|
||||||
importance: Importance.high,
|
importance: Importance.high,
|
||||||
priority: Priority.max,
|
priority: Priority.max,
|
||||||
groupKey: event.room.spaceParents.firstOrNull?.roomId ?? 'rooms',
|
groupKey: groupKey,
|
||||||
actions: event.type == EventTypes.RoomMember || !useNotificationActions
|
actions: event.type == EventTypes.RoomMember || !useNotificationActions
|
||||||
? null
|
? null
|
||||||
: <AndroidNotificationAction>[
|
: <AndroidNotificationAction>[
|
||||||
|
|
@ -300,6 +303,41 @@ Future<void> _tryPushHelper(
|
||||||
event.eventId,
|
event.eventId,
|
||||||
).toString(),
|
).toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Send summary notification on Android
|
||||||
|
if (PlatformInfos.isAndroid) {
|
||||||
|
final activeNotifications =
|
||||||
|
(await flutterLocalNotificationsPlugin.getActiveNotifications())
|
||||||
|
.where((n) => n.groupKey == groupKey)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (activeNotifications.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final title = l10n.unreadChatsInApp(
|
||||||
|
AppSettings.applicationName.value,
|
||||||
|
activeNotifications.length.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.show(
|
||||||
|
id: summaryId,
|
||||||
|
notificationDetails: NotificationDetails(
|
||||||
|
android: AndroidNotificationDetails(
|
||||||
|
AppConfig.pushNotificationsChannelId,
|
||||||
|
l10n.incomingMessages,
|
||||||
|
groupKey: groupKey,
|
||||||
|
setAsGroupSummary: true,
|
||||||
|
styleInformation: InboxStyleInformation(
|
||||||
|
activeNotifications.map((n) => n.body ?? '').toList(),
|
||||||
|
contentTitle: title,
|
||||||
|
summaryText: title,
|
||||||
|
),
|
||||||
|
autoCancel: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
Logs().v('Push helper has been completed!');
|
Logs().v('Push helper has been completed!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ abstract class UpdateNotifier {
|
||||||
|
|
||||||
static Future<void> showUpdateSnackBar(BuildContext context) async {
|
static Future<void> showUpdateSnackBar(BuildContext context) async {
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final currentVersion = await PlatformInfos.getVersion();
|
final currentVersion = await PlatformInfos.getVersion();
|
||||||
final store = await SharedPreferences.getInstance();
|
final store = await SharedPreferences.getInstance();
|
||||||
final storedVersion = store.getString(versionStoreKey);
|
final storedVersion = store.getString(versionStoreKey);
|
||||||
|
|
@ -20,9 +21,9 @@ abstract class UpdateNotifier {
|
||||||
SnackBar(
|
SnackBar(
|
||||||
duration: const Duration(seconds: 30),
|
duration: const Duration(seconds: 30),
|
||||||
showCloseIcon: true,
|
showCloseIcon: true,
|
||||||
content: Text(L10n.of(context).updateInstalled(currentVersion)),
|
content: Text(l10n.updateInstalled(currentVersion)),
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: L10n.of(context).changelog,
|
label: l10n.changelog,
|
||||||
onPressed: () => launchUrlString(AppConfig.changelogUrl),
|
onPressed: () => launchUrlString(AppConfig.changelogUrl),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ Future<void> connectToHomeserverFlow(
|
||||||
|
|
||||||
if ((kIsWeb || PlatformInfos.isLinux) &&
|
if ((kIsWeb || PlatformInfos.isLinux) &&
|
||||||
(supportsSso || authMetadata != null || (signUp && regLink != null))) {
|
(supportsSso || authMetadata != null || (signUp && regLink != null))) {
|
||||||
|
if (!context.mounted) return;
|
||||||
final consent = await showOkCancelAlertDialog(
|
final consent = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: l10n.appWantsToUseForLogin(homeserverInput),
|
title: l10n.appWantsToUseForLogin(homeserverInput),
|
||||||
|
|
@ -45,7 +46,9 @@ Future<void> connectToHomeserverFlow(
|
||||||
okLabel: l10n.continueText,
|
okLabel: l10n.continueText,
|
||||||
);
|
);
|
||||||
if (consent != OkCancelResult.ok) return;
|
if (consent != OkCancelResult.ok) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
}
|
}
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
if (authMetadata != null && AppSettings.enableMatrixNativeOIDC.value) {
|
if (authMetadata != null && AppSettings.enableMatrixNativeOIDC.value) {
|
||||||
await oidcLoginFlow(client, context, signUp);
|
await oidcLoginFlow(client, context, signUp);
|
||||||
|
|
@ -55,6 +58,7 @@ Future<void> connectToHomeserverFlow(
|
||||||
if (signUp && regLink != null) {
|
if (signUp && regLink != null) {
|
||||||
await launchUrlString(regLink);
|
await launchUrlString(regLink);
|
||||||
}
|
}
|
||||||
|
if (!context.mounted) return;
|
||||||
final pathSegments = List.of(
|
final pathSegments = List.of(
|
||||||
GoRouter.of(context).routeInformationProvider.value.uri.pathSegments,
|
GoRouter.of(context).routeInformationProvider.value.uri.pathSegments,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ class UrlLauncher {
|
||||||
const UrlLauncher(this.context, this.url, [this.name]);
|
const UrlLauncher(this.context, this.url, [this.name]);
|
||||||
|
|
||||||
Future<void> launchUrl() async {
|
Future<void> launchUrl() async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
if (url!.toLowerCase().startsWith(AppConfig.deepLinkPrefix) ||
|
if (url!.toLowerCase().startsWith(AppConfig.deepLinkPrefix) ||
|
||||||
url!.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) ||
|
url!.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) ||
|
||||||
{'#', '@', '!', '+', '\$'}.contains(url![0]) ||
|
{'#', '@', '!', '+', '\$'}.contains(url![0]) ||
|
||||||
|
|
@ -36,8 +38,8 @@ class UrlLauncher {
|
||||||
final uri = Uri.tryParse(url!);
|
final uri = Uri.tryParse(url!);
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
// we can't open this thing
|
// we can't open this thing
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(content: Text(L10n.of(context).cantOpenUri(url!))),
|
SnackBar(content: Text(l10n.cantOpenUri(url!))),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -47,10 +49,10 @@ class UrlLauncher {
|
||||||
// that the user can see the actual url before opening the browser.
|
// that the user can see the actual url before opening the browser.
|
||||||
final consent = await showOkCancelAlertDialog(
|
final consent = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).openLinkInBrowser,
|
title: l10n.openLinkInBrowser,
|
||||||
message: url,
|
message: url,
|
||||||
okLabel: L10n.of(context).open,
|
okLabel: l10n.open,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
);
|
);
|
||||||
if (consent != OkCancelResult.ok) return;
|
if (consent != OkCancelResult.ok) return;
|
||||||
}
|
}
|
||||||
|
|
@ -90,8 +92,8 @@ class UrlLauncher {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (uri.host.isEmpty) {
|
if (uri.host.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(content: Text(L10n.of(context).cantOpenUri(url!))),
|
SnackBar(content: Text(l10n.cantOpenUri(url!))),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -161,6 +163,7 @@ class UrlLauncher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
servers.addAll(identityParts.via);
|
servers.addAll(identityParts.via);
|
||||||
|
if (!context.mounted) return;
|
||||||
if (room != null) {
|
if (room != null) {
|
||||||
if (room.isSpace) {
|
if (room.isSpace) {
|
||||||
// TODO: Implement navigate to space
|
// TODO: Implement navigate to space
|
||||||
|
|
@ -178,6 +181,7 @@ class UrlLauncher {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
if (!context.mounted) return;
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) =>
|
builder: (c) =>
|
||||||
|
|
@ -185,6 +189,7 @@ class UrlLauncher {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (roomIdOrAlias.sigil == '!') {
|
if (roomIdOrAlias.sigil == '!') {
|
||||||
|
if (!context.mounted) return;
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -192,6 +197,7 @@ class UrlLauncher {
|
||||||
) ==
|
) ==
|
||||||
OkCancelResult.ok) {
|
OkCancelResult.ok) {
|
||||||
roomId = roomIdOrAlias;
|
roomId = roomIdOrAlias;
|
||||||
|
if (!context.mounted) return;
|
||||||
final response = await showFutureLoadingDialog(
|
final response = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => matrix.client.joinRoom(
|
future: () => matrix.client.joinRoom(
|
||||||
|
|
@ -200,11 +206,13 @@ class UrlLauncher {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (response.error != null) return;
|
if (response.error != null) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
// wait for two seconds so that it probably came down /sync
|
// wait for two seconds so that it probably came down /sync
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Future.delayed(const Duration(seconds: 2)),
|
future: () => Future.delayed(const Duration(seconds: 2)),
|
||||||
);
|
);
|
||||||
|
if (!context.mounted) return;
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
context.go(
|
context.go(
|
||||||
Uri(
|
Uri(
|
||||||
|
|
@ -228,6 +236,7 @@ class UrlLauncher {
|
||||||
return Profile(userId: userId);
|
return Profile(userId: userId);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
if (!context.mounted) return;
|
||||||
await UserDialog.show(
|
await UserDialog.show(
|
||||||
context: context,
|
context: context,
|
||||||
profile: profileResult.result!,
|
profile: profileResult.result!,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ class PublicRoomDialog extends StatelessWidget {
|
||||||
const PublicRoomDialog({super.key, this.roomAlias, this.chunk, this.via});
|
const PublicRoomDialog({super.key, this.roomAlias, this.chunk, this.via});
|
||||||
|
|
||||||
Future<void> _joinRoom(BuildContext context) async {
|
Future<void> _joinRoom(BuildContext context) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final client = Matrix.of(context).client;
|
final client = Matrix.of(context).client;
|
||||||
final chunk = this.chunk;
|
final chunk = this.chunk;
|
||||||
final knock = chunk?.joinRule == 'knock';
|
final knock = chunk?.joinRule == 'knock';
|
||||||
|
|
@ -48,12 +49,13 @@ class PublicRoomDialog extends StatelessWidget {
|
||||||
);
|
);
|
||||||
final roomId = result.result;
|
final roomId = result.result;
|
||||||
if (roomId == null) return;
|
if (roomId == null) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
if (knock && client.getRoomById(roomId) == null) {
|
if (knock && client.getRoomById(roomId) == null) {
|
||||||
Navigator.of(context).pop<bool>(true);
|
Navigator.of(context).pop<bool>(true);
|
||||||
await showOkAlertDialog(
|
await showOkAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).youHaveKnocked,
|
title: l10n.youHaveKnocked,
|
||||||
message: L10n.of(context).pleaseWaitUntilInvited,
|
message: l10n.pleaseWaitUntilInvited,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -73,6 +75,7 @@ class PublicRoomDialog extends StatelessWidget {
|
||||||
bool _testRoom(PublishedRoomsChunk r) => r.canonicalAlias == roomAlias;
|
bool _testRoom(PublishedRoomsChunk r) => r.canonicalAlias == roomAlias;
|
||||||
|
|
||||||
Future<PublishedRoomsChunk> _search(BuildContext context) async {
|
Future<PublishedRoomsChunk> _search(BuildContext context) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final chunk = this.chunk;
|
final chunk = this.chunk;
|
||||||
if (chunk != null) return chunk;
|
if (chunk != null) return chunk;
|
||||||
final query = await Matrix.of(context).client.queryPublicRooms(
|
final query = await Matrix.of(context).client.queryPublicRooms(
|
||||||
|
|
@ -80,7 +83,7 @@ class PublicRoomDialog extends StatelessWidget {
|
||||||
filter: PublicRoomQueryFilter(genericSearchTerm: roomAlias),
|
filter: PublicRoomQueryFilter(genericSearchTerm: roomAlias),
|
||||||
);
|
);
|
||||||
if (!query.chunk.any(_testRoom)) {
|
if (!query.chunk.any(_testRoom)) {
|
||||||
throw (L10n.of(context).noRoomsFound);
|
throw (l10n.noRoomsFound);
|
||||||
}
|
}
|
||||||
return query.chunk.firstWhere(_testRoom);
|
return query.chunk.firstWhere(_testRoom);
|
||||||
}
|
}
|
||||||
|
|
@ -248,6 +251,7 @@ class PublicRoomDialog extends StatelessWidget {
|
||||||
hintText: L10n.of(context).reason,
|
hintText: L10n.of(context).reason,
|
||||||
);
|
);
|
||||||
if (reason == null || reason.isEmpty) return;
|
if (reason == null || reason.isEmpty) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(context).client.reportRoom(
|
future: () => Matrix.of(context).client.reportRoom(
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,7 @@ class UserDialog extends StatelessWidget {
|
||||||
hintText: L10n.of(context).reason,
|
hintText: L10n.of(context).reason,
|
||||||
);
|
);
|
||||||
if (reason == null || reason.isEmpty) return;
|
if (reason == null || reason.isEmpty) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => Matrix.of(
|
future: () => Matrix.of(
|
||||||
|
|
|
||||||
|
|
@ -53,16 +53,18 @@ class ChatSettingsPopupMenuState extends State<ChatSettingsPopupMenu> {
|
||||||
onSelected: (choice) async {
|
onSelected: (choice) async {
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
case ChatPopupMenuActions.leave:
|
case ChatPopupMenuActions.leave:
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final router = GoRouter.of(context);
|
final router = GoRouter.of(context);
|
||||||
final confirmed = await showOkCancelAlertDialog(
|
final confirmed = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: l10n.areYouSure,
|
||||||
message: L10n.of(context).archiveRoomDescription,
|
message: l10n.archiveRoomDescription,
|
||||||
okLabel: L10n.of(context).leave,
|
okLabel: l10n.leave,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
isDestructive: true,
|
isDestructive: true,
|
||||||
);
|
);
|
||||||
if (confirmed != OkCancelResult.ok) return;
|
if (confirmed != OkCancelResult.ok) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
final result = await showFutureLoadingDialog(
|
final result = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => widget.room.leave(),
|
future: () => widget.room.leave(),
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class FluffyChatApp extends StatelessWidget {
|
||||||
|
|
||||||
// Pass deep links to app:
|
// Pass deep links to app:
|
||||||
if (state.uri.toString().startsWith(AppConfig.deepLinkPrefix)) {
|
if (state.uri.toString().startsWith(AppConfig.deepLinkPrefix)) {
|
||||||
return '/rooms/newprivatechat?deeplink=${state.uri}';
|
return '/rooms/newprivatechat#${state.uri}';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:fluffychat/l10n/l10n.dart';
|
||||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||||
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:matrix/matrix_api_lite/utils/logs.dart';
|
||||||
|
|
||||||
/// Displays a loading dialog which reacts to the given [future]. The dialog
|
/// Displays a loading dialog which reacts to the given [future]. The dialog
|
||||||
/// will be dismissed and the value will be returned when the future completes.
|
/// will be dismissed and the value will be returned when the future completes.
|
||||||
|
|
@ -40,6 +41,15 @@ Future<Result<T>> showFutureLoadingDialog<T>({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context.mounted) {
|
||||||
|
Logs().e(
|
||||||
|
'Unable to show loading dialog!',
|
||||||
|
Exception('The BuildContext is not mounted!'),
|
||||||
|
StackTrace.current,
|
||||||
|
);
|
||||||
|
return Result.capture(futureExec);
|
||||||
|
}
|
||||||
|
|
||||||
final result = await showAdaptiveDialog<Result<T>>(
|
final result = await showAdaptiveDialog<Result<T>>(
|
||||||
context: context,
|
context: context,
|
||||||
barrierDismissible: barrierDismissible,
|
barrierDismissible: barrierDismissible,
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,12 @@ import 'package:matrix/matrix.dart';
|
||||||
import 'package:universal_html/html.dart' as html;
|
import 'package:universal_html/html.dart' as html;
|
||||||
|
|
||||||
extension LocalNotificationsExtension on MatrixState {
|
extension LocalNotificationsExtension on MatrixState {
|
||||||
|
static final html.AudioElement _audioPlayer = html.AudioElement()
|
||||||
|
..src = 'assets/assets/sounds/notification.ogg'
|
||||||
|
..load();
|
||||||
|
|
||||||
Future<void> showLocalNotification(Event event) async {
|
Future<void> showLocalNotification(Event event) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final roomId = event.room.id;
|
final roomId = event.room.id;
|
||||||
if (activeRoomId == roomId) {
|
if (activeRoomId == roomId) {
|
||||||
if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
|
if (WidgetsBinding.instance.lifecycleState == AppLifecycleState.resumed) {
|
||||||
|
|
@ -68,6 +73,8 @@ extension LocalNotificationsExtension on MatrixState {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (AppSettings.webNotificationSound.value) _audioPlayer.play();
|
||||||
|
|
||||||
html.Notification(
|
html.Notification(
|
||||||
title,
|
title,
|
||||||
body: body,
|
body: body,
|
||||||
|
|
@ -114,11 +121,11 @@ extension LocalNotificationsExtension on MatrixState {
|
||||||
actions: [
|
actions: [
|
||||||
NotificationAction(
|
NotificationAction(
|
||||||
DesktopNotificationActions.openChat.name,
|
DesktopNotificationActions.openChat.name,
|
||||||
L10n.of(context).openChat,
|
l10n.openChat,
|
||||||
),
|
),
|
||||||
NotificationAction(
|
NotificationAction(
|
||||||
DesktopNotificationActions.seen.name,
|
DesktopNotificationActions.seen.name,
|
||||||
L10n.of(context).markAsRead,
|
l10n.markAsRead,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
hints: hints,
|
hints: hints,
|
||||||
|
|
|
||||||
|
|
@ -265,12 +265,19 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
||||||
InitWithRestoreExtension.deleteSessionBackup(name);
|
InitWithRestoreExtension.deleteSessionBackup(name);
|
||||||
|
|
||||||
if (loggedInWithMultipleClients) {
|
if (loggedInWithMultipleClients) {
|
||||||
|
final snackbarContext =
|
||||||
|
FluffyChatApp
|
||||||
|
.router
|
||||||
|
.routerDelegate
|
||||||
|
.navigatorKey
|
||||||
|
.currentContext ??
|
||||||
|
context;
|
||||||
|
|
||||||
|
if (!snackbarContext.mounted) return;
|
||||||
|
final l10n = L10n.of(snackbarContext);
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
FluffyChatApp.router.routerDelegate.navigatorKey.currentContext ??
|
snackbarContext,
|
||||||
context,
|
).showSnackBar(SnackBar(content: Text(l10n.oneClientLoggedOut)));
|
||||||
).showSnackBar(
|
|
||||||
SnackBar(content: Text(L10n.of(context).oneClientLoggedOut)),
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FluffyChatApp.router.go('/');
|
FluffyChatApp.router.go('/');
|
||||||
|
|
@ -382,15 +389,17 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dehydrateAction(BuildContext context) async {
|
Future<void> dehydrateAction(BuildContext context) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
final response = await showOkCancelAlertDialog(
|
final response = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
isDestructive: true,
|
isDestructive: true,
|
||||||
title: L10n.of(context).dehydrate,
|
title: l10n.dehydrate,
|
||||||
message: L10n.of(context).dehydrateWarning,
|
message: l10n.dehydrateWarning,
|
||||||
);
|
);
|
||||||
if (response != OkCancelResult.ok) {
|
if (response != OkCancelResult.ok) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!context.mounted) return;
|
||||||
final result = await showFutureLoadingDialog(
|
final result = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: client.exportDump,
|
future: client.exportDump,
|
||||||
|
|
@ -404,6 +413,7 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
||||||
'fluffychat-export-${DateFormat(DateFormat.YEAR_MONTH_DAY).format(DateTime.now())}.fluffybackup';
|
'fluffychat-export-${DateFormat(DateFormat.YEAR_MONTH_DAY).format(DateTime.now())}.fluffybackup';
|
||||||
|
|
||||||
final file = MatrixFile(bytes: exportBytes, name: exportFileName);
|
final file = MatrixFile(bytes: exportBytes, name: exportFileName);
|
||||||
|
if (!context.mounted) return;
|
||||||
file.save(context);
|
file.save(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
required User user,
|
required User user,
|
||||||
void Function()? onMention,
|
void Function()? onMention,
|
||||||
}) async {
|
}) async {
|
||||||
|
final l10n = L10n.of(context);
|
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final displayname = user.calcDisplayname();
|
final displayname = user.calcDisplayname();
|
||||||
final isMe = user.room.client.userID == user.id;
|
final isMe = user.room.client.userID == user.id;
|
||||||
|
|
@ -79,7 +81,7 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (user.canChangeUserPowerLevel) ...[
|
if (user.canChangeUserPowerLevel) ...[
|
||||||
if (user.powerLevel < 100)
|
if (user.powerLevel.level < 100)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: _MemberActions.makeAdmin,
|
value: _MemberActions.makeAdmin,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -90,7 +92,7 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (user.powerLevel < 50)
|
if (user.powerLevel.level < 50)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: _MemberActions.makeModerator,
|
value: _MemberActions.makeModerator,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -101,7 +103,7 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (user.powerLevel >= 100)
|
if (user.powerLevel.role == PowerLevelRole.admin)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: _MemberActions.removeAdmin,
|
value: _MemberActions.removeAdmin,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -112,7 +114,7 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else if (user.powerLevel >= 50)
|
else if (user.powerLevel.role == PowerLevelRole.moderator)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: _MemberActions.removeModerator,
|
value: _MemberActions.removeModerator,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|
@ -125,7 +127,7 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
if (user.canChangeUserPowerLevel ||
|
if (user.canChangeUserPowerLevel ||
|
||||||
!defaultPowerLevels.contains(user.powerLevel))
|
!defaultPowerLevels.contains(user.powerLevel.level))
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: _MemberActions.setPowerLevel,
|
value: _MemberActions.setPowerLevel,
|
||||||
enabled: user.canChangeUserPowerLevel,
|
enabled: user.canChangeUserPowerLevel,
|
||||||
|
|
@ -138,7 +140,7 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
? L10n.of(context).setPowerLevel
|
? L10n.of(context).setPowerLevel
|
||||||
: L10n.of(context).powerLevel,
|
: L10n.of(context).powerLevel,
|
||||||
),
|
),
|
||||||
if (!defaultPowerLevels.contains(user.powerLevel))
|
if (!defaultPowerLevels.contains(user.powerLevel.level))
|
||||||
Text(' (${user.powerLevel})'),
|
Text(' (${user.powerLevel})'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
@ -217,8 +219,8 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
case _MemberActions.setPowerLevel:
|
case _MemberActions.setPowerLevel:
|
||||||
final power = await showPermissionChooser(
|
final power = await showPermissionChooser(
|
||||||
context,
|
context,
|
||||||
currentLevel: user.powerLevel,
|
currentLevel: user.powerLevel.level,
|
||||||
maxLevel: user.room.ownPowerLevel,
|
maxLevel: user.room.ownPowerLevel.level,
|
||||||
);
|
);
|
||||||
if (power == null) return;
|
if (power == null) return;
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
@ -245,12 +247,13 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
case _MemberActions.kick:
|
case _MemberActions.kick:
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: l10n.areYouSure,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: l10n.yes,
|
||||||
cancelLabel: L10n.of(context).no,
|
cancelLabel: l10n.no,
|
||||||
message: L10n.of(context).kickUserDescription,
|
message: l10n.kickUserDescription,
|
||||||
) ==
|
) ==
|
||||||
OkCancelResult.ok) {
|
OkCancelResult.ok) {
|
||||||
|
if (!context.mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => user.kick(),
|
future: () => user.kick(),
|
||||||
|
|
@ -260,12 +263,13 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
case _MemberActions.ban:
|
case _MemberActions.ban:
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: l10n.areYouSure,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: l10n.yes,
|
||||||
cancelLabel: L10n.of(context).no,
|
cancelLabel: l10n.no,
|
||||||
message: L10n.of(context).banUserDescription,
|
message: l10n.banUserDescription,
|
||||||
) ==
|
) ==
|
||||||
OkCancelResult.ok) {
|
OkCancelResult.ok) {
|
||||||
|
if (!context.mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => user.ban(),
|
future: () => user.ban(),
|
||||||
|
|
@ -275,20 +279,22 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
case _MemberActions.report:
|
case _MemberActions.report:
|
||||||
final reason = await showTextInputDialog(
|
final reason = await showTextInputDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).whyDoYouWantToReportThis,
|
title: l10n.whyDoYouWantToReportThis,
|
||||||
okLabel: L10n.of(context).report,
|
okLabel: l10n.report,
|
||||||
cancelLabel: L10n.of(context).cancel,
|
cancelLabel: l10n.cancel,
|
||||||
hintText: L10n.of(context).reason,
|
hintText: l10n.reason,
|
||||||
);
|
);
|
||||||
if (reason == null || reason.isEmpty) return;
|
if (reason == null || reason.isEmpty) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
final result = await showFutureLoadingDialog(
|
final result = await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => user.room.client.reportUser(user.id, reason),
|
future: () => user.room.client.reportUser(user.id, reason),
|
||||||
);
|
);
|
||||||
if (result.error != null) return;
|
if (result.error != null) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
if (!context.mounted) return;
|
||||||
SnackBar(content: Text(L10n.of(context).contentHasBeenReported)),
|
scaffoldMessenger.showSnackBar(
|
||||||
|
SnackBar(content: Text(l10n.contentHasBeenReported)),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
case _MemberActions.info:
|
case _MemberActions.info:
|
||||||
|
|
@ -304,19 +310,20 @@ Future<void> showMemberActionsPopupMenu({
|
||||||
case _MemberActions.unban:
|
case _MemberActions.unban:
|
||||||
if (await showOkCancelAlertDialog(
|
if (await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: l10n.areYouSure,
|
||||||
okLabel: L10n.of(context).yes,
|
okLabel: l10n.yes,
|
||||||
cancelLabel: L10n.of(context).no,
|
cancelLabel: l10n.no,
|
||||||
message: L10n.of(context).unbanUserDescription,
|
message: l10n.unbanUserDescription,
|
||||||
) ==
|
) ==
|
||||||
OkCancelResult.ok) {
|
OkCancelResult.ok) {
|
||||||
|
if (!context.mounted) return;
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () => user.unban(),
|
future: () => user.unban(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case _MemberActions.makeAdmin:
|
case _MemberActions.makeAdmin:
|
||||||
if (user.room.ownPowerLevel <= 100) {
|
if (user.room.ownPowerLevel.level <= 100) {
|
||||||
final consent = await showOkCancelAlertDialog(
|
final consent = await showOkCancelAlertDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context).areYouSure,
|
title: L10n.of(context).areYouSure,
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,11 @@ class SpacesNavigationRail extends StatelessWidget {
|
||||||
isSelected: activeSpaceId == null,
|
isSelected: activeSpaceId == null,
|
||||||
onTap: onGoToChats,
|
onTap: onGoToChats,
|
||||||
icon: const Padding(
|
icon: const Padding(
|
||||||
padding: EdgeInsets.all(10.0),
|
padding: EdgeInsets.all(8.0),
|
||||||
child: Icon(Icons.forum_outlined),
|
child: Icon(Icons.forum_outlined),
|
||||||
),
|
),
|
||||||
selectedIcon: const Padding(
|
selectedIcon: const Padding(
|
||||||
padding: EdgeInsets.all(10.0),
|
padding: EdgeInsets.all(8.0),
|
||||||
child: Icon(Icons.forum),
|
child: Icon(Icons.forum),
|
||||||
),
|
),
|
||||||
toolTip: L10n.of(context).chats,
|
toolTip: L10n.of(context).chats,
|
||||||
|
|
@ -71,7 +71,7 @@ class SpacesNavigationRail extends StatelessWidget {
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
onTap: () => context.go('/rooms/newspace'),
|
onTap: () => context.go('/rooms/newspace'),
|
||||||
icon: const Padding(
|
icon: const Padding(
|
||||||
padding: EdgeInsets.all(8.0),
|
padding: EdgeInsets.all(6.0),
|
||||||
child: Icon(Icons.add),
|
child: Icon(Icons.add),
|
||||||
),
|
),
|
||||||
toolTip: L10n.of(context).createNewSpace,
|
toolTip: L10n.of(context).createNewSpace,
|
||||||
|
|
@ -94,6 +94,7 @@ class SpacesNavigationRail extends StatelessWidget {
|
||||||
icon: Avatar(
|
icon: Avatar(
|
||||||
mxContent: allSpaces[i].avatar,
|
mxContent: allSpaces[i].avatar,
|
||||||
name: displayname,
|
name: displayname,
|
||||||
|
size: 36,
|
||||||
shapeBorder: RoundedSuperellipseBorder(
|
shapeBorder: RoundedSuperellipseBorder(
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
width: 1,
|
width: 1,
|
||||||
|
|
|
||||||
88
pubspec.lock
88
pubspec.lock
|
|
@ -5,26 +5,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d
|
sha256: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "91.0.0"
|
version: "93.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08
|
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.4.1"
|
version: "10.0.1"
|
||||||
analyzer_plugin:
|
analyzer_plugin:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer_plugin
|
name: analyzer_plugin
|
||||||
sha256: "825071d553c4aef2252196d46a665fbd8e0cb06de07725f25d1b29bd18d65fff"
|
sha256: "7df504f0c9d6891bacc9f73a5a8c5f6fe4fc49c90ec8e3379916372906ba0b32"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.6"
|
version: "0.14.1"
|
||||||
ansicolor:
|
ansicolor:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -53,10 +53,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: async
|
name: async
|
||||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.0"
|
version: "2.13.1"
|
||||||
audio_session:
|
audio_session:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -69,10 +69,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: badges
|
name: badges
|
||||||
sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84
|
sha256: cf1c88fb3777df69ccd630b80de5267f54efa4a39381b0404a7c03d56cb7c041
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.2.0"
|
||||||
barbecue:
|
barbecue:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -149,10 +149,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: chewie
|
name: chewie
|
||||||
sha256: "44bcfc5f0dfd1de290c87c9d86a61308b3282a70b63435d5557cfd60f54a69ca"
|
sha256: "53dadd2c5b6748742d7744072b38a417ad22691ca55715850300ee793dc7cb27"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.13.0"
|
version: "1.13.1"
|
||||||
cli_config:
|
cli_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -261,10 +261,10 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: dart_code_linter
|
name: dart_code_linter
|
||||||
sha256: "1b53722d9933a5f5d4580acc29c7f16b1fde66d21d1ecf7bb2a811caf3a42b42"
|
sha256: "8ece88f710621ca1c40b6c344b316d78bb2269d728d37d2a44f19a81d9d2cb93"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.1"
|
version: "4.0.2"
|
||||||
dart_earcut:
|
dart_earcut:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -285,18 +285,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b
|
sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.7"
|
||||||
dart_webrtc:
|
dart_webrtc:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_webrtc
|
name: dart_webrtc
|
||||||
sha256: "4ed7b9fa9924e5a81eb39271e2c2356739dd1039d60a13b86ba6c5f448625086"
|
sha256: f6d615bddea5e458ce180a914f3055c234ffb52fb7397a51b3491e76d6d7edb2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.8.1"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -309,10 +309,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: desktop_drop
|
name: desktop_drop
|
||||||
sha256: e70b46b2d61f1af7a81a40d1f79b43c28a879e30a4ef31e87e9c27bea4d784e8
|
sha256: aa1e797255bfbc76f9eb5aa4f61e5b68dbf69962ab1be6495816d2f251bc0d1f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.1"
|
||||||
desktop_notifications:
|
desktop_notifications:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -389,10 +389,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343"
|
sha256: f13a03000d942e476bc1ff0a736d2e9de711d2f89a95cd4c1d88f861c3348387
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.3.10"
|
version: "11.0.2"
|
||||||
file_selector:
|
file_selector:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -479,10 +479,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_foreground_task
|
name: flutter_foreground_task
|
||||||
sha256: "1903697944a31f596622e51a6af55e3a9dfb27762f9763ab2841184098c6b0ba"
|
sha256: fc5c01a5e1b8f7bb51d0c737714f0c50440dbdf1aeddc5f8cbba313aa6fd4856
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.2.1"
|
version: "9.2.2"
|
||||||
flutter_linkify:
|
flutter_linkify:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -540,10 +540,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_map
|
name: flutter_map
|
||||||
sha256: "391e7dc95cc3f5190748210a69d4cfeb5d8f84dcdfa9c3235d0a9d7742ccb3f8"
|
sha256: "03b71c02806ff20c3718d108cbbb3638142ebafe368d8ce2dd22a33344bcb02b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.2"
|
version: "8.3.0"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|
@ -649,10 +649,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_web_auth_2
|
name: flutter_web_auth_2
|
||||||
sha256: "432ff8c7b2834eaeec3378d99e24a0210b9ac2f453b3f7a7d739a5c09069fba3"
|
sha256: d354998934ddc338e69b999b2abaeb33c6fd09999d3a5f92ead1a6b49b49712e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.1"
|
version: "5.0.2"
|
||||||
flutter_web_auth_2_platform_interface:
|
flutter_web_auth_2_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -670,10 +670,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_webrtc
|
name: flutter_webrtc
|
||||||
sha256: c549ea8ffb20167110ad0a28e5f17a2650b5bea8837d984898cd9b0ffd5fa78b
|
sha256: c7b0a67ca2c878575fc5c146d801cd874f58f5f1ef5fa6e8eb0c93d413beb948
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.4.1"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -763,10 +763,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: go_router
|
name: go_router
|
||||||
sha256: "7974313e217a7771557add6ff2238acb63f635317c35fa590d348fb238f00896"
|
sha256: "08b742eef4f71c9df5af543751cd0b7f1c679c4088488f4223ecaddc1a813b79"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "17.1.0"
|
version: "17.2.2"
|
||||||
gsettings:
|
gsettings:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1088,10 +1088,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: matrix
|
name: matrix
|
||||||
sha256: "5bb38e98212bc4c3244c762a1af787f7239a38d2cfdf44488258283ff899f77c"
|
sha256: "0da5f65016c704bda81eae807cdadc18a046a444fa7e5cec83e60e5d04006d17"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.0"
|
version: "7.0.0"
|
||||||
media_kit:
|
media_kit:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1208,10 +1208,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d
|
sha256: "468c26b4254ab01979fa5e4a98cb343ea3631b9acee6f21028997419a80e1a20"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.0"
|
version: "9.0.1"
|
||||||
package_info_plus_platform_interface:
|
package_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1600,10 +1600,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: share_plus
|
name: share_plus
|
||||||
sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840"
|
sha256: "223873d106614442ea6f20db5a038685cc5b32a2fba81cdecaefbbae0523f7fa"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "12.0.1"
|
version: "12.0.2"
|
||||||
share_plus_platform_interface:
|
share_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1616,10 +1616,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
|
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.4"
|
version: "2.5.5"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -2133,10 +2133,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: wakelock_plus
|
name: wakelock_plus
|
||||||
sha256: e4e125b7c1a2f0e491e5452afdc0e25ab77b2d2775a7caa231fcc1c1f2162c47
|
sha256: ddf3db70eaa10c37558ff817519b85d527dbd21034fd5d8e1c2e85f31588f1c1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.5.2"
|
||||||
wakelock_plus_platform_interface:
|
wakelock_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -2214,10 +2214,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: webrtc_interface
|
name: webrtc_interface
|
||||||
sha256: ad0e5786b2acd3be72a3219ef1dde9e1cac071cf4604c685f11b61d63cdd6eb3
|
sha256: c6f100eac5057d9a817a60473126f9828c796d42884d498af4f339c97b21014f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.5.1"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
38
pubspec.yaml
38
pubspec.yaml
|
|
@ -4,43 +4,43 @@ publish_to: none
|
||||||
# On version bump please also increase:
|
# On version bump please also increase:
|
||||||
# 1. The build number (for F-Droid)
|
# 1. The build number (for F-Droid)
|
||||||
# 2. The version in /snap/snapcraft.yaml
|
# 2. The version in /snap/snapcraft.yaml
|
||||||
version: 2.5.0+3550
|
version: 2.5.1+3551
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.11.1 <4.0.0"
|
sdk: ">=3.11.1 <4.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
archive: ^4.0.7
|
archive: ^4.0.7
|
||||||
async: ^2.11.0
|
async: ^2.13.1
|
||||||
badges: ^3.1.2
|
badges: ^3.2.0
|
||||||
blurhash_dart: ^1.2.1
|
blurhash_dart: ^1.2.1
|
||||||
chewie: ^1.13.0
|
chewie: ^1.13.1
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
crop_image: ^1.0.17
|
crop_image: ^1.0.17
|
||||||
cross_file: ^0.3.5
|
cross_file: ^0.3.5
|
||||||
desktop_drop: ^0.7.0
|
desktop_drop: ^0.7.1
|
||||||
desktop_notifications: ^0.6.3
|
desktop_notifications: ^0.6.3
|
||||||
device_info_plus: ^12.3.0
|
device_info_plus: ^12.3.0
|
||||||
dynamic_color: ^1.8.1
|
dynamic_color: ^1.8.1
|
||||||
emoji_picker_flutter: ^4.4.0
|
emoji_picker_flutter: ^4.4.0
|
||||||
file_picker: ^10.3.10
|
file_picker: ^11.0.2
|
||||||
file_selector: ^1.1.0
|
file_selector: ^1.1.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_foreground_task: ^9.2.1
|
flutter_foreground_task: ^9.2.2
|
||||||
flutter_linkify: ^6.0.0
|
flutter_linkify: ^6.0.0
|
||||||
flutter_local_notifications: ^21.0.0
|
flutter_local_notifications: ^21.0.0
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_map: ^8.2.2
|
flutter_map: ^8.3.0
|
||||||
flutter_new_badger: ^1.1.1
|
flutter_new_badger: ^1.1.1
|
||||||
flutter_secure_storage: ^10.0.0
|
flutter_secure_storage: ^10.0.0
|
||||||
flutter_shortcuts_new: ^2.0.0
|
flutter_shortcuts_new: ^2.0.0
|
||||||
flutter_vodozemac: ^0.5.0
|
flutter_vodozemac: ^0.5.0
|
||||||
flutter_web_auth_2: ^5.0.1
|
flutter_web_auth_2: ^5.0.2
|
||||||
flutter_webrtc: ^1.3.1
|
flutter_webrtc: ^1.4.1
|
||||||
geolocator: ^14.0.2
|
geolocator: ^14.0.2
|
||||||
go_router: ^17.1.0
|
go_router: ^17.2.2
|
||||||
handy_window: ^0.4.2
|
handy_window: ^0.4.2
|
||||||
highlight: ^0.7.0
|
highlight: ^0.7.0
|
||||||
html: ^0.15.4
|
html: ^0.15.4
|
||||||
|
|
@ -52,13 +52,13 @@ dependencies:
|
||||||
just_audio_media_kit: ^2.1.0
|
just_audio_media_kit: ^2.1.0
|
||||||
latlong2: ^0.9.1
|
latlong2: ^0.9.1
|
||||||
linkify: ^5.0.0
|
linkify: ^5.0.0
|
||||||
matrix: ^6.2.0
|
matrix: ^7.0.0
|
||||||
media_kit_libs_linux: ^1.2.1
|
media_kit_libs_linux: ^1.2.1
|
||||||
media_kit_libs_windows_video: ^1.0.11
|
media_kit_libs_windows_video: ^1.0.11
|
||||||
mime: ^2.0.0
|
mime: ^2.0.0
|
||||||
native_imaging: ^0.4.0
|
native_imaging: ^0.4.0
|
||||||
opus_caf_converter_dart: ^1.0.1
|
opus_caf_converter_dart: ^1.0.1
|
||||||
package_info_plus: ^9.0.0
|
package_info_plus: ^9.0.1
|
||||||
particles_network: ^1.9.3
|
particles_network: ^1.9.3
|
||||||
pasteboard: ^0.5.0
|
pasteboard: ^0.5.0
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
|
|
@ -69,10 +69,10 @@ dependencies:
|
||||||
qr_code_scanner_plus: ^2.1.1
|
qr_code_scanner_plus: ^2.1.1
|
||||||
qr_image: ^1.0.0
|
qr_image: ^1.0.0
|
||||||
receive_sharing_intent: ^1.8.1
|
receive_sharing_intent: ^1.8.1
|
||||||
record: ^6.1.2
|
record: ^6.2.0
|
||||||
scroll_to_index: ^3.0.1
|
scroll_to_index: ^3.0.1
|
||||||
share_plus: ^12.0.1
|
share_plus: ^12.0.2
|
||||||
shared_preferences: ^2.5.4 # Pinned because https://github.com/flutter/flutter/issues/118401
|
shared_preferences: ^2.5.5 # Pinned because https://github.com/flutter/flutter/issues/118401
|
||||||
slugify: ^2.0.0
|
slugify: ^2.0.0
|
||||||
sqflite_common_ffi: ^2.3.7+1
|
sqflite_common_ffi: ^2.3.7+1
|
||||||
sqlcipher_flutter_libs: ^0.6.8
|
sqlcipher_flutter_libs: ^0.6.8
|
||||||
|
|
@ -84,11 +84,11 @@ dependencies:
|
||||||
video_compress: ^3.1.4
|
video_compress: ^3.1.4
|
||||||
video_player: ^2.11.1
|
video_player: ^2.11.1
|
||||||
video_player_media_kit: ^2.0.0
|
video_player_media_kit: ^2.0.0
|
||||||
wakelock_plus: ^1.5.0
|
wakelock_plus: ^1.5.2
|
||||||
webrtc_interface: ^1.3.0
|
webrtc_interface: ^1.5.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
dart_code_linter: ^3.2.1
|
dart_code_linter: ^4.0.2
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
flutter_native_splash: ^2.4.7
|
flutter_native_splash: ^2.4.7
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
flutter pub add fcm_shared_isolate:0.2.0
|
flutter pub add fcm_shared_isolate
|
||||||
flutter pub get
|
flutter pub get
|
||||||
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
#!/bin/sh -ve
|
#!/bin/sh -ve
|
||||||
|
|
||||||
# Compile Vodozemac for web
|
# Compile Vodozemac for web
|
||||||
version=$(yq ".dependencies.flutter_vodozemac" < pubspec.yaml | tr -d '"')
|
version=$(yq ".dependencies.flutter_vodozemac" < pubspec.yaml)
|
||||||
version=$(expr "$version" : '\^*\(.*\)')
|
version=$(printf "%s" "$version" | tr -d '"^')
|
||||||
git clone https://github.com/famedly/dart-vodozemac.git -b ${version} .vodozemac
|
git clone https://github.com/famedly/dart-vodozemac.git -b ${version} .vodozemac
|
||||||
cd .vodozemac
|
cd .vodozemac
|
||||||
cargo install flutter_rust_bridge_codegen
|
cargo install flutter_rust_bridge_codegen
|
||||||
|
|
@ -15,8 +15,8 @@ flutter pub get
|
||||||
dart compile js ./web/native_executor.dart -o ./web/native_executor.js -m
|
dart compile js ./web/native_executor.dart -o ./web/native_executor.js -m
|
||||||
|
|
||||||
# Download native_imaging for web:
|
# Download native_imaging for web:
|
||||||
version=$(yq ".dependencies.native_imaging" < pubspec.yaml | tr -d '"')
|
version=$(yq ".dependencies.native_imaging" < pubspec.yaml)
|
||||||
version=$(expr "$version" : '\^*\(.*\)')
|
version=$(printf "%s" "$version" | tr -d '"^')
|
||||||
curl -L "https://github.com/famedly/dart_native_imaging/releases/download/v${version}/native_imaging.zip" > native_imaging.zip
|
curl -L "https://github.com/famedly/dart_native_imaging/releases/download/v${version}/native_imaging.zip" > native_imaging.zip
|
||||||
unzip native_imaging.zip
|
unzip native_imaging.zip
|
||||||
mv js/* web/
|
mv js/* web/
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/sh -ve
|
#!/bin/sh -ve
|
||||||
flutter pub add fcm_shared_isolate:0.2.0
|
flutter pub add fcm_shared_isolate
|
||||||
sed -i '' 's,//<GOOGLE_SERVICES>,,g' lib/utils/background_push.dart
|
sed -i '' 's,//<GOOGLE_SERVICES>,,g' lib/utils/background_push.dart
|
||||||
flutter clean
|
flutter clean
|
||||||
flutter pub get
|
flutter pub get
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
name: fluffychat
|
name: fluffychat
|
||||||
title: FluffyChat
|
title: FluffyChat
|
||||||
base: core24
|
base: core24
|
||||||
version: 2.5.0
|
version: 2.5.1
|
||||||
license: AGPL-3.0
|
license: AGPL-3.0
|
||||||
summary: The cutest messenger in the Matrix network
|
summary: The cutest messenger in the Matrix network
|
||||||
description: |
|
description: |
|
||||||
|
|
@ -53,7 +53,7 @@ platforms:
|
||||||
parts:
|
parts:
|
||||||
flutter-git:
|
flutter-git:
|
||||||
source: https://github.com/flutter/flutter.git
|
source: https://github.com/flutter/flutter.git
|
||||||
source-tag: 3.41.5
|
source-tag: 3.41.7
|
||||||
source-depth: 1
|
source-depth: 1
|
||||||
plugin: nil
|
plugin: nil
|
||||||
override-build: |
|
override-build: |
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue