Fluffychat merge (#1685)
chore: Merge upstream changes --------- Signed-off-by: Krille <c.kussowski@famedly.com> Co-authored-by: krille-chan <christian-kussowski@posteo.de> Co-authored-by: Krille <c.kussowski@famedly.com> Co-authored-by: Linerly <linerly@proton.me> Co-authored-by: Priit Jõerüüt <hwlate@joeruut.com> Co-authored-by: 大王叫我来巡山 <hamburger2048@users.noreply.hosted.weblate.org> Co-authored-by: fadelkon <fadelkon@posteo.net> Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com> Co-authored-by: Edgars Andersons <Edgars+Weblate@gaitenis.id.lv> Co-authored-by: josé m <correoxm@disroot.org> Co-authored-by: Bezruchenko Simon <worcposj44@gmail.com> Co-authored-by: Christian <christian-pauly@posteo.de> Co-authored-by: - <hitekex@yandex.ru> Co-authored-by: Angelo Schirinzi <Odi-3@users.noreply.hosted.weblate.org> Co-authored-by: xabirequejo <xabi.rn@gmail.com> Co-authored-by: Piotr Orzechowski <piotr@orzechowski.tech> Co-authored-by: Rex_sa <rex.sa@pm.me> Co-authored-by: Tewuzij <tenajeza@outlook.com> Co-authored-by: goknarbahceli <goknarbahceli@proton.me> Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com> Co-authored-by: Erin <erin@erindesu.cz> Co-authored-by: EpicKiwi <me@epickiwi.fr> Co-authored-by: Christian Tietze <me@christiantietze.de> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
6dd984a23b
commit
49e586a7ad
152 changed files with 7483 additions and 5214 deletions
44
CHANGELOG.md
44
CHANGELOG.md
|
|
@ -1,3 +1,47 @@
|
|||
## v1.24.0
|
||||
- build: Add missing libssl library (krille-chan)
|
||||
- build: Update dart_webrtc package (Krille)
|
||||
- build: Update matrix sdk and dependencies (Krille)
|
||||
- build: Update to flutter 3.27 (Krille)
|
||||
- chore: Better bottom sheets on desktop (krille-chan)
|
||||
- chore: Check file size before loading (krille-chan)
|
||||
- chore: Display normal Slider when no waveform provided in audioplayer (krille-chan)
|
||||
- chore: Do not display sender prefix for DM rooms in notification ticker (krille-chan)
|
||||
- chore: Enable share multiple files to app (krille-chan)
|
||||
- chore: Improve alias UX in chat settings (Krille)
|
||||
- chore: Improve join abandoned invite exception (Krille)
|
||||
- chore: Improve UserBottomSheet UX (Krille)
|
||||
- chore: Make message bubble color dark also in dark mode (krille-chan)
|
||||
- chore: Remove conversationTitle if room is dm room in android notifications (krille-chan)
|
||||
- feat: QR Code viewer for mxid sharing (Krille)
|
||||
- fix: Do not set public visibility for private groups (Krille)
|
||||
- fix: Use MB and KB instead of MiB and KiB for file sizes (Krille)
|
||||
- refactor: Adjust chat list item UX (Krille)
|
||||
- refactor: Better custom image resizer (Krille)
|
||||
- refactor: Clean up android manifest (Krille)
|
||||
- refactor: Implement own adaptive dialogs and remove package (krille-chan)
|
||||
- refactor: Improve UX of user role in UserBottomSheet (Krille)
|
||||
- refactor: Improved share / forward dialog (krille-chan)
|
||||
- Translated using Weblate (Arabic) (Rex_sa)
|
||||
- Translated using Weblate (Basque) (xabirequejo)
|
||||
- Translated using Weblate (Catalan) (fadelkon)
|
||||
- Translated using Weblate (Chinese (Simplified Han script)) (大王叫我来巡山)
|
||||
- Translated using Weblate (Czech) (Erin)
|
||||
- Translated using Weblate (Estonian) (Priit Jõerüüt)
|
||||
- Translated using Weblate (Galician) (josé m)
|
||||
- Translated using Weblate (German) (Christian)
|
||||
- Translated using Weblate (Indonesian) (Linerly)
|
||||
- Translated using Weblate (Irish) (Aindriú Mac Giolla Eoin)
|
||||
- Translated using Weblate (Italian) (Angelo Schirinzi)
|
||||
- Translated using Weblate (Latvian) (Edgars Andersons)
|
||||
- Translated using Weblate (Polish) (Piotr Orzechowski)
|
||||
- Translated using Weblate (Russian) (-)
|
||||
- Translated using Weblate (Tamil) (Christian)
|
||||
- Translated using Weblate (Tamil) (தமிழ்நேரம்)
|
||||
- Translated using Weblate (Turkish) (goknarbahceli)
|
||||
- Translated using Weblate (Ukrainian) (Bezruchenko Simon)
|
||||
- Translated using Weblate (Vietnamese) (Tewuzij)
|
||||
|
||||
## v1.23.0
|
||||
- design: Highlight emoji only messages (Krille)
|
||||
- design: New login design (Krille)
|
||||
|
|
|
|||
18
PRIVACY.md
18
PRIVACY.md
|
|
@ -92,3 +92,21 @@ A typical push notification could look like this:
|
|||
```
|
||||
|
||||
FluffyChat sets the `event_id_only` flag at the Matrix Server. This server is then responsible to send the correct data.
|
||||
|
||||
|
||||
# Explanation of FluffyChat's Compliance with Google Play Store's Safety Standards
|
||||
|
||||
FluffyChat is committed to promoting a safe and respectful environment for all users. As a Matrix client, FluffyChat connects users to various Matrix servers. Please note that FluffyChat does not host or manage any servers directly, and as such, we do not have the capability to enforce content moderation or deletion within the app itself.
|
||||
|
||||
To enhance user safety and help protect against the sexual abuse and exploitation of children, FluffyChat enables users to report inappropriate content directly to server administrators.
|
||||
|
||||
#### Reporting Content or Users:
|
||||
|
||||
1. Mark a message in the chat: Tap and hold the message you wish to report.
|
||||
2. Report the message: Select the "Report" option.
|
||||
3. Provide a reason and score: Enter the reason for reporting and assign a score from 1-100 to indicate how offensive the content is.
|
||||
4. Notification to admin: The server administrator will be notified of the reported content.
|
||||
|
||||
In addition to reporting messages, users can also report other users following a similar process.
|
||||
|
||||
We encourage server administrators to adhere to strict safety standards and provide mechanisms for addressing and moderating inappropriate content. For more information on the Matrix protocol and its safety standards, please refer to the following link: https://matrix.org/docs/older/moderation/
|
||||
|
|
@ -86,15 +86,6 @@
|
|||
<data android:scheme="com.talktolearn.chat" android:host="chat" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- App can receive shared files -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data
|
||||
android:mimeType="*/*"
|
||||
android:scheme="content" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- App can receive shared text -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
|
@ -102,32 +93,16 @@
|
|||
<data android:mimeType="text/*" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- App can receive shared documents -->
|
||||
<!-- App can receive shared any type of files -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="document/*" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- App can receive shared images -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- App can receive shared videos -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- App can receive shared audios -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="audio/*" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
|
|
|||
|
|
@ -2885,5 +2885,14 @@
|
|||
"pleaseFillOut": "من فضلك قم بتعبئته",
|
||||
"@pleaseFillOut": {},
|
||||
"unableToJoinChat": "يتعذر الانضمام إلى الدردشة. ربما يكون الطرف الآخر قد أغلق المحادثة بالفعل.",
|
||||
"@unableToJoinChat": {}
|
||||
"@unableToJoinChat": {},
|
||||
"sendImages": "إرسال {count} صورة",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"compress": "ضغط",
|
||||
"@compress": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"areGuestsAllowedToJoin": "Es pot entrar a la sala com a convidadi",
|
||||
"areGuestsAllowedToJoin": "Es pot entrar al xat com a convidadi",
|
||||
"@areGuestsAllowedToJoin": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
|
|
@ -1939,7 +1939,7 @@
|
|||
"user": {}
|
||||
}
|
||||
},
|
||||
"banUserDescription": "Es vetarà li usuàriï vetadi a la sala i no podrà tornar-hi a entrar fins que se li aixequi el veto.",
|
||||
"banUserDescription": "Es vetarà li usuàriï al xat i no podrà tornar-hi a entrar fins que se li aixequi el veto.",
|
||||
"@banUserDescription": {},
|
||||
"widgetEtherpad": "Nota de text",
|
||||
"@widgetEtherpad": {},
|
||||
|
|
@ -1958,7 +1958,7 @@
|
|||
"user": {}
|
||||
}
|
||||
},
|
||||
"unbanUserDescription": "L'usuàrïi ja pot tornar a entrar a la sala.",
|
||||
"unbanUserDescription": "L'usuàrïi ja pot tornar a entrar al xat.",
|
||||
"@unbanUserDescription": {},
|
||||
"youRejectedTheInvitation": "Has rebutjat la invitació",
|
||||
"@youRejectedTheInvitation": {},
|
||||
|
|
@ -2077,7 +2077,7 @@
|
|||
"provider": {}
|
||||
}
|
||||
},
|
||||
"fileIsTooBigForServer": "El servidor ha rebutjat l'arxiu perquè és massa gran.",
|
||||
"fileIsTooBigForServer": "No s'ha pogut enviar! El servidor només accepta adjunts de fins a {max}.",
|
||||
"@fileIsTooBigForServer": {},
|
||||
"homeserver": "Servidor",
|
||||
"@homeserver": {},
|
||||
|
|
@ -2105,7 +2105,7 @@
|
|||
"@optionalRedactReason": {},
|
||||
"dehydrate": "Exporta la sessió i neteja el dispositiu",
|
||||
"@dehydrate": {},
|
||||
"archiveRoomDescription": "Aquest xat serà arxivat. Els altres contactes del grup ho veuran com si haguessis abandonat la sala.",
|
||||
"archiveRoomDescription": "Aquest xat serà arxivat. Els altres contactes del grup ho veuran com si haguessis abandonat el xat.",
|
||||
"@archiveRoomDescription": {},
|
||||
"exportEmotePack": "Exporta com un pack Emote en .zip",
|
||||
"@exportEmotePack": {},
|
||||
|
|
@ -2170,7 +2170,7 @@
|
|||
"path": {}
|
||||
}
|
||||
},
|
||||
"redactMessageDescription": "S'estriparà el missatge per a totser d'aquesta sala. Aquesta acció és irreversible.",
|
||||
"redactMessageDescription": "S'estriparà el missatge per a totser d'aquesta conversa. Aquesta acció és irreversible.",
|
||||
"@redactMessageDescription": {},
|
||||
"recoveryKey": "Clau de recuperació",
|
||||
"@recoveryKey": {},
|
||||
|
|
@ -2254,7 +2254,7 @@
|
|||
},
|
||||
"importEmojis": "Importa emojis",
|
||||
"@importEmojis": {},
|
||||
"wasDirectChatDisplayName": "La sala buida ( va ser {oldDisplayName})",
|
||||
"wasDirectChatDisplayName": "Xat buit ( era {oldDisplayName})",
|
||||
"@wasDirectChatDisplayName": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
|
|
@ -2285,7 +2285,7 @@
|
|||
"@dehydrateTor": {},
|
||||
"removeFromSpace": "Esborra de l'espai",
|
||||
"@removeFromSpace": {},
|
||||
"roomUpgradeDescription": "El xat serà recreat amb una versió de sala nova. Totis lis participants seran notificadis que han de canviar a la nova sala. Pots llegir més sobre les versions de sala a https://spec.matrix.org/latest/rooms/",
|
||||
"roomUpgradeDescription": "El xat serà recreat amb una versió de sala nova. Totis lis participants seran notificadis que han de canviar al nou xat. Pots llegir més sobre les versions de sala a https://spec.matrix.org/latest/rooms/",
|
||||
"@roomUpgradeDescription": {},
|
||||
"pleaseEnterANumber": "Introdueix un número major que 0",
|
||||
"@pleaseEnterANumber": {},
|
||||
|
|
@ -2501,7 +2501,7 @@
|
|||
"@searchForUsers": {},
|
||||
"subspace": "Subespai",
|
||||
"@subspace": {},
|
||||
"addChatOrSubSpace": "Afegeix una sala o un subespai",
|
||||
"addChatOrSubSpace": "Afegeix un xat o un subespai",
|
||||
"@addChatOrSubSpace": {},
|
||||
"decline": "Denega",
|
||||
"@decline": {},
|
||||
|
|
@ -2644,7 +2644,7 @@
|
|||
"@changeGeneralChatSettings": {},
|
||||
"sendRoomNotifications": "Envia notificacions @room",
|
||||
"@sendRoomNotifications": {},
|
||||
"changeTheDescriptionOfTheGroup": "Canvia la descripció de la sala",
|
||||
"changeTheDescriptionOfTheGroup": "Canvia la descripció del xat",
|
||||
"@changeTheDescriptionOfTheGroup": {},
|
||||
"changelog": "Registre de canvis",
|
||||
"@changelog": {},
|
||||
|
|
@ -2664,11 +2664,11 @@
|
|||
},
|
||||
"inviteOtherUsers": "Convida més gent a la conversa",
|
||||
"@inviteOtherUsers": {},
|
||||
"changeTheChatPermissions": "Canvia els permisos de la sala",
|
||||
"changeTheChatPermissions": "Canvia els permisos del xat",
|
||||
"@changeTheChatPermissions": {},
|
||||
"changeTheVisibilityOfChatHistory": "Canvia la visibilitat de l'historial de conversa",
|
||||
"@changeTheVisibilityOfChatHistory": {},
|
||||
"changeTheCanonicalRoomAlias": "Canvia l'adreça principal de la sala",
|
||||
"changeTheCanonicalRoomAlias": "Canvia l'adreça principal del xat",
|
||||
"@changeTheCanonicalRoomAlias": {},
|
||||
"accessAndVisibilityDescription": "Qui pot entrar a aquesta conversa i com pot ser descoberta.",
|
||||
"@accessAndVisibilityDescription": {},
|
||||
|
|
@ -2707,7 +2707,7 @@
|
|||
"@usersMustKnock": {},
|
||||
"noOneCanJoin": "Ningú s'hi pot ficar",
|
||||
"@noOneCanJoin": {},
|
||||
"userWouldLikeToChangeTheChat": "{user} vol entrar a la sala.",
|
||||
"userWouldLikeToChangeTheChat": "{user} vol entrar al xat.",
|
||||
"@userWouldLikeToChangeTheChat": {
|
||||
"placeholders": {
|
||||
"user": {}
|
||||
|
|
@ -2717,7 +2717,7 @@
|
|||
"@customEmojisAndStickersBody": {},
|
||||
"hideRedactedMessagesBody": "Si algú estripa un missatge, ja no apareixerà a l'historial de la conversa.",
|
||||
"@hideRedactedMessagesBody": {},
|
||||
"searchIn": "Cerca a la sala \"{chat}\"...",
|
||||
"searchIn": "Cerca al xat \"{chat}\"...",
|
||||
"@searchIn": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
|
|
@ -2726,7 +2726,7 @@
|
|||
},
|
||||
"markAsUnread": "Marca com a no llegit",
|
||||
"@markAsUnread": {},
|
||||
"chatPermissionsDescription": "Defineix quin nivell de permisos cal per cada acció en aquesta sala. Els nivells 0, 50 i 100 normalment representen usuàriïs, mods i admins, però es pot canviar.",
|
||||
"chatPermissionsDescription": "Defineix quin nivell de permisos cal per cada acció en aquest xat. Els nivells 0, 50 i 100 normalment representen usuàriïs, mods i admins, però es pot canviar.",
|
||||
"@chatPermissionsDescription": {},
|
||||
"updateInstalled": "🎉 S'ha actualitzat a la versió {version}!",
|
||||
"@updateInstalled": {
|
||||
|
|
@ -2751,7 +2751,7 @@
|
|||
"@searchMore": {},
|
||||
"files": "Arxius",
|
||||
"@files": {},
|
||||
"publicChatAddresses": "Adreces públiques de la sala",
|
||||
"publicChatAddresses": "Adreces públiques del xat",
|
||||
"@publicChatAddresses": {},
|
||||
"unreadChatsInApp": "{appname}: {unread} converses pendents",
|
||||
"@unreadChatsInApp": {
|
||||
|
|
@ -2796,11 +2796,98 @@
|
|||
"@spaces": {},
|
||||
"noPublicLinkHasBeenCreatedYet": "No s'ha creat cap enllaç públic",
|
||||
"@noPublicLinkHasBeenCreatedYet": {},
|
||||
"chatCanBeDiscoveredViaSearchOnServer": "La sala es pot descobrir amb la cerca de {server}",
|
||||
"chatCanBeDiscoveredViaSearchOnServer": "El xat es pot descobrir amb la cerca de {server}",
|
||||
"@chatCanBeDiscoveredViaSearchOnServer": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"server": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"calculatingFileSize": "S'està calculant la mida de l'arxiu...",
|
||||
"@calculatingFileSize": {},
|
||||
"prepareSendingAttachment": "S'està preparant per enviar l'adjunt...",
|
||||
"@prepareSendingAttachment": {},
|
||||
"generatingVideoThumbnail": "S'està generant la miniatura del vídeo...",
|
||||
"@generatingVideoThumbnail": {},
|
||||
"noticeChatBackupDeviceVerification": "Nota: quan connectes tots els dispositius al backup del xat, es verifiquen automàticament.",
|
||||
"@noticeChatBackupDeviceVerification": {},
|
||||
"continueText": "Continua",
|
||||
"@continueText": {},
|
||||
"strikeThrough": "Text ratllat",
|
||||
"@strikeThrough": {},
|
||||
"addLink": "Afegeix un enllaç",
|
||||
"@addLink": {},
|
||||
"noContactInformationProvided": "El servidor no ofereix cap informació de contacte vàlida",
|
||||
"@noContactInformationProvided": {},
|
||||
"setWallpaper": "Tria imatge de fons",
|
||||
"@setWallpaper": {},
|
||||
"sendImages": "Envia {count} imatge",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"sendingAttachment": "S'està enviant l'adjunt...",
|
||||
"@sendingAttachment": {},
|
||||
"compressVideo": "S'està comprimint el vídeo...",
|
||||
"@compressVideo": {},
|
||||
"sendingAttachmentCountOfCount": "S'està enviant l'adjunt {index} de {length}...",
|
||||
"@sendingAttachmentCountOfCount": {
|
||||
"type": "integer",
|
||||
"placeholders": {
|
||||
"index": {},
|
||||
"length": {}
|
||||
}
|
||||
},
|
||||
"serverLimitReached": "S'ha arribat al límit del servidor! Esperant {seconds} segons...",
|
||||
"@serverLimitReached": {
|
||||
"type": "integer",
|
||||
"placeholders": {
|
||||
"seconds": {}
|
||||
}
|
||||
},
|
||||
"oneOfYourDevicesIsNotVerified": "Un dels teus dispositius no està verificat",
|
||||
"@oneOfYourDevicesIsNotVerified": {},
|
||||
"welcomeText": "Hola hola! 👋 Això és FluffyChat. Pots iniciar sessió en qualsevol servidor compatible amb https://matrix.org. I llavors xatejar amb qualsevol. És una xarxa enorme de missatgeria descentralitzada !",
|
||||
"@welcomeText": {},
|
||||
"blur": "Difumina:",
|
||||
"@blur": {},
|
||||
"opacity": "Opacitat:",
|
||||
"@opacity": {},
|
||||
"manageAccount": "Gestiona el compte",
|
||||
"@manageAccount": {},
|
||||
"contactServerAdmin": "Contacta l'admin del servidor",
|
||||
"@contactServerAdmin": {},
|
||||
"contactServerSecurity": "Contacta l'equip de seguretat del servidor",
|
||||
"@contactServerSecurity": {},
|
||||
"version": "Versió",
|
||||
"@version": {},
|
||||
"website": "Lloc web",
|
||||
"@website": {},
|
||||
"compress": "Comprimeix",
|
||||
"@compress": {},
|
||||
"pleaseFillOut": "Emplena",
|
||||
"@pleaseFillOut": {},
|
||||
"invalidUrl": "URL invàlida",
|
||||
"@invalidUrl": {},
|
||||
"unableToJoinChat": "No s'ha pogut entrar al xat. Pot ser que l'altri participant hagi tancat la conversa.",
|
||||
"@unableToJoinChat": {},
|
||||
"aboutHomeserver": "Quant a {homeserver}",
|
||||
"@aboutHomeserver": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"homeserver": {}
|
||||
}
|
||||
},
|
||||
"supportPage": "Pàgina de suport",
|
||||
"@supportPage": {},
|
||||
"serverInformation": "Informació del servidor:",
|
||||
"@serverInformation": {},
|
||||
"name": "Nom",
|
||||
"@name": {},
|
||||
"boldText": "Text en negreta",
|
||||
"@boldText": {},
|
||||
"italicText": "Text en cursiva",
|
||||
"@italicText": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2437,5 +2437,12 @@
|
|||
"@presencesToggle": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"aboutHomeserver": "O {homeserver}",
|
||||
"@aboutHomeserver": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"homeserver": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2882,5 +2882,21 @@
|
|||
"strikeThrough": "Durchgestrichen",
|
||||
"@strikeThrough": {},
|
||||
"pleaseFillOut": "Bitte ausfüllen",
|
||||
"@pleaseFillOut": {}
|
||||
"@pleaseFillOut": {},
|
||||
"sendImages": "Sende {count} Bilder",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"contactServerSecurity": "Server-Sicherheit kontaktieren",
|
||||
"@contactServerSecurity": {},
|
||||
"compress": "Komprimieren",
|
||||
"@compress": {},
|
||||
"supportPage": "Support-Seite",
|
||||
"@supportPage": {},
|
||||
"serverInformation": "Server-Informationen:",
|
||||
"@serverInformation": {},
|
||||
"appIntroduction": "Mit FluffyChat kannst du über verschiedene Messenger hinweg mit deinen Freunden chatten. Erfahre mehr dazu auf https://matrix.org oder tippe einfach auf *Fortfahren*."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1918,6 +1918,13 @@
|
|||
"type": "text",
|
||||
"placeholders": {}
|
||||
},
|
||||
"synchronizingPleaseWaitCounter": " Synchronizing… ({percentage}%)",
|
||||
"@synchronizingPleaseWaitCounter": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"percentage": {}
|
||||
}
|
||||
},
|
||||
"systemTheme": "System",
|
||||
"@systemTheme": {
|
||||
"type": "text",
|
||||
|
|
@ -4668,6 +4675,7 @@
|
|||
"chooseBestDefinition": "What does this word mean?",
|
||||
"chooseBaseForm": "Choose the base form",
|
||||
"notTheCodeError": "Sorry, that's not the code!",
|
||||
"previous": "Previous",
|
||||
"totalXP": "Total XP",
|
||||
"numLemmas": "Total number of lemmas",
|
||||
"listOfLemmas": "List of lemmas",
|
||||
|
|
@ -4745,6 +4753,8 @@
|
|||
},
|
||||
"notInClass": "Not in a class!",
|
||||
"noClassCode": "No class code!",
|
||||
"previous": "Previous",
|
||||
"otherPartyNotLoggedIn": "The other party is currently not logged in and therefore cannot receive messages!",
|
||||
"chooseCorrectLabel": "Choose the correct label",
|
||||
"levelPopupTitle": "Congratulations on reaching\nLevel {level}",
|
||||
"@levelPopupTitle": {
|
||||
|
|
@ -4774,6 +4784,7 @@
|
|||
"activityPlannerOverviewInstructionsBody": "Choose a topic, mode, learning objective and generate an activity for the chat!",
|
||||
"completeActivitiesToUnlock": "Complete the highlighted word activities to unlock",
|
||||
"myBookmarkedActivities": "My Bookmarked Activities",
|
||||
"noBookmarkedActivities": "No bookmarked activities",
|
||||
"noBookmarkedActivities": "When you bookmark activities, they will appear here. Bookmarked activities can be re-used across spaces and chats.",
|
||||
"activityTitle": "Activity Title",
|
||||
"addVocabulary": "Add Vocabulary",
|
||||
|
|
@ -4791,5 +4802,17 @@
|
|||
},
|
||||
"constructUsePvmDesc": "Produced in voice message",
|
||||
"lockedMorphFeature": "Waiting to be unlocked",
|
||||
"leaveSpaceDescription": "The space will be moved to the archive. Other users will be able to see that you have left the chat."
|
||||
}
|
||||
"leaveSpaceDescription": "The space will be moved to the archive. Other users will be able to see that you have left the chat.",
|
||||
"otherPartyNotLoggedIn": "The other party is currently not logged in and therefore cannot receive messages!",
|
||||
"appWantsToUseForLogin": "Use '{server}' to log in",
|
||||
"@appWantsToUseForLogin": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"server": {}
|
||||
}
|
||||
},
|
||||
"appWantsToUseForLoginDescription": "You hereby allow the app and website to share information about you.",
|
||||
"open": "Open",
|
||||
"waitingForServer": "Waiting for server...",
|
||||
"appIntroduction": "FluffyChat lets you chat with your friends across different messengers. Learn more at https://matrix.org or just tap *Continue*."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2885,5 +2885,14 @@
|
|||
"italicText": "Kaldkiri",
|
||||
"@italicText": {},
|
||||
"unableToJoinChat": "Vestlusega liitumine ei õnnestu. Võib-olla on teine osapool juba vestluse sulgenud.",
|
||||
"@unableToJoinChat": {}
|
||||
"@unableToJoinChat": {},
|
||||
"sendImages": "Saada {count} pilti",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"compress": "Paki kokku",
|
||||
"@compress": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2885,5 +2885,14 @@
|
|||
"pleaseFillOut": "Bete ezazu",
|
||||
"@pleaseFillOut": {},
|
||||
"unableToJoinChat": "Ezin da txatera batu. Agian besteak elkarrizketa itxiko zuen honezkero.",
|
||||
"@unableToJoinChat": {}
|
||||
"@unableToJoinChat": {},
|
||||
"sendImages": "Bidali {count} irudi",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"compress": "Konprimatu",
|
||||
"@compress": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2888,5 +2888,14 @@
|
|||
"invalidUrl": "URL neamhbhailí",
|
||||
"@invalidUrl": {},
|
||||
"unableToJoinChat": "Ní féidir páirt a ghlacadh sa chomhrá. B’fhéidir go bhfuil an comhrá dúnta cheana féin ag an bpáirtí eile.",
|
||||
"@unableToJoinChat": {}
|
||||
"@unableToJoinChat": {},
|
||||
"compress": "Comhbhrúigh",
|
||||
"@compress": {},
|
||||
"sendImages": "Seol {count} íomhá",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2885,5 +2885,14 @@
|
|||
"addLink": "Engadir ligazón",
|
||||
"@addLink": {},
|
||||
"unableToJoinChat": "Non se puido acceder. Pode que a outra parte xa pechase a conversa.",
|
||||
"@unableToJoinChat": {}
|
||||
"@unableToJoinChat": {},
|
||||
"sendImages": "Enviar {count} imaxe",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"compress": "Comprimir",
|
||||
"@compress": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2882,5 +2882,16 @@
|
|||
"boldText": "Teks tebal",
|
||||
"@boldText": {},
|
||||
"italicText": "Teks miring",
|
||||
"@italicText": {}
|
||||
"@italicText": {},
|
||||
"unableToJoinChat": "Tidak dapat bergabung dalam chat. Mungkin pihak lain telah menutup percakapan.",
|
||||
"@unableToJoinChat": {},
|
||||
"sendImages": "Kirim {count} gambar",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"compress": "Kompres",
|
||||
"@compress": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@
|
|||
"joinRules": {}
|
||||
}
|
||||
},
|
||||
"changedTheProfileAvatar": "{username} ha cambiato il loro avatar",
|
||||
"changedTheProfileAvatar": "{username} ha cambiato il suo avatar",
|
||||
"@changedTheProfileAvatar": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
|
|
@ -764,7 +764,7 @@
|
|||
"targetName": {}
|
||||
}
|
||||
},
|
||||
"invitedUsersOnly": "Solo per gli utenti invitati",
|
||||
"invitedUsersOnly": "Solo utenti invitati",
|
||||
"@invitedUsersOnly": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
|
|
@ -815,7 +815,7 @@
|
|||
"targetName": {}
|
||||
}
|
||||
},
|
||||
"kickFromChat": "Espulsa dalla discussione",
|
||||
"kickFromChat": "Espelli dalla chat",
|
||||
"@kickFromChat": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
|
|
@ -2884,5 +2884,14 @@
|
|||
"addLink": "Aggiungi collegamento",
|
||||
"@addLink": {},
|
||||
"unableToJoinChat": "Impossibile partecipare alla chat. Forse l'altra parte ha già chiuso la conversazione.",
|
||||
"@unableToJoinChat": {}
|
||||
"@unableToJoinChat": {},
|
||||
"sendImages": "Invia {count} immagine",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"compress": "Comprimere",
|
||||
"@compress": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -643,7 +643,7 @@
|
|||
"count": {}
|
||||
}
|
||||
},
|
||||
"noKeyForThisMessage": "Tā var notikt, ja ziņa tika nosūtīta, pirms pieteicies savā kontā šajā ierīcē.\n\nIr arī iespējams, ka sūtītājs noliedza Tavu ierīci vai kaut kas nogāja greizi ar interneta savienojumu.\n\nVai ziņas ir lasāmas citā sesijā? Tad Tu vari pārsūtīt ziņo no tās. Jādodas uz Iestatījumi > Ierīces un jāpārliecinās, ka ierīces viena otru ir apliecinājušas. Kad nākamreiz atvērsi istabu un abas sesijas būs priekšplānā, atslēgas tiks automātiski pārsūtītas.\n\nVai nevēlies zaudēt atslēgas, kad atsakies vai maini ierīces? Jāpārliecinās, ka iestatījumos ir iespējota tērzēšanas rezerves kopija.",
|
||||
"noKeyForThisMessage": "Tā var notikt, ja ziņa tika nosūtīta, pirms pieteicies savā kontā šajā ierīcē.\n\nIr arī iespējams, ka sūtītājs noliedza Tavu ierīci vai kaut kas nogāja greizi ar interneta savienojumu.\n\nVai ziņas ir lasāmas citā sesijā? Tad Tu vari pārsūtīt ziņo no tās. Jādodas uz Iestatījumi > Ierīces un jāpārliecinās, ka ierīces viena otru ir apliecinājušas. Kad nākamreiz atvērsi istabu un abas sesijas būs priekšplānā, atslēgas tiks automātiski pārsūtītas.\n\nVai nevēlies zaudēt atslēgas, kad atsakies vai maini ierīces? Jāpārliecinās, ka iestatījumos ir iespējota tērzēšanu rezerves kopija.",
|
||||
"@noKeyForThisMessage": {},
|
||||
"enableEncryptionWarning": "Vairs nebūs iespējams atspējot šifrēšanu. Vai tiešām to darīt?",
|
||||
"@enableEncryptionWarning": {
|
||||
|
|
@ -712,7 +712,7 @@
|
|||
"supportedVersions": {}
|
||||
}
|
||||
},
|
||||
"wipeChatBackup": "Notīrīt tērzēšanas rezerves kopiju, lai izveidotu jaunu atkopšanas atslēgu?",
|
||||
"wipeChatBackup": "Notīrīt tērzēšanu rezerves kopiju, lai izveidotu jaunu atkopšanas atslēgu?",
|
||||
"@wipeChatBackup": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
|
|
@ -911,9 +911,9 @@
|
|||
},
|
||||
"storeSecurlyOnThisDevice": "Droši uzglabāt šajā ierīcē",
|
||||
"@storeSecurlyOnThisDevice": {},
|
||||
"yourChatBackupHasBeenSetUp": "Tērzēšanas rezerves kopēšana tika iestatīta.",
|
||||
"yourChatBackupHasBeenSetUp": "Tērzēšanu rezerves kopēšana tika iestatīta.",
|
||||
"@yourChatBackupHasBeenSetUp": {},
|
||||
"chatBackup": "Tērzēšanas rezerves kopēšana",
|
||||
"chatBackup": "Tērzēšanu rezerves kopēšana",
|
||||
"@chatBackup": {
|
||||
"type": "text",
|
||||
"placeholders": {}
|
||||
|
|
@ -2320,7 +2320,7 @@
|
|||
},
|
||||
"custom": "Pielāgots",
|
||||
"@custom": {},
|
||||
"noBackupWarning": "Uzmanību! Bez tērzēšanas rezerves kopijas iespējošanas tiks zaudēta piekļuve savām šifrētajām ziņām. Ir ļoti ieteicams iespējot tērzēšanas rezerves kopiju pirms atteikšanās.",
|
||||
"noBackupWarning": "Uzmanību! Bez tērzēšanu rezerves kopiju veidošanas iespējošanas tiks zaudēta piekļuve savām šifrētajām ziņām. Ir ļoti ieteicams iespējot tērzēšanu rezerves kopiju veidošanu pirms atteikšanās.",
|
||||
"@noBackupWarning": {},
|
||||
"fromJoining": "No pievienošanās",
|
||||
"@fromJoining": {
|
||||
|
|
@ -2809,7 +2809,7 @@
|
|||
"@compressVideo": {},
|
||||
"oneOfYourDevicesIsNotVerified": "Viena no ierīcēm nav apliecināta",
|
||||
"@oneOfYourDevicesIsNotVerified": {},
|
||||
"noticeChatBackupDeviceVerification": "Piezīme: kad visas ierīces tiek savienotas ar tērzēšanas rezerves kopiju, tās tiek automātiski apliecinātas.",
|
||||
"noticeChatBackupDeviceVerification": "Piezīme: kad visas ierīces tiek savienotas ar tērzēšanu rezerves kopiju, tās tiek automātiski apliecinātas.",
|
||||
"@noticeChatBackupDeviceVerification": {},
|
||||
"continueText": "Turpināt",
|
||||
"@continueText": {},
|
||||
|
|
@ -2861,5 +2861,16 @@
|
|||
"pleaseFillOut": "Lūgums aizpildīt",
|
||||
"@pleaseFillOut": {},
|
||||
"sendUncompressed": "Sūtīt nesaspiestu",
|
||||
"@sendUncompressed": {}
|
||||
"@sendUncompressed": {},
|
||||
"sendImages": "Nosūtīt {count} attēlu(s)",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"compress": "Saspiest",
|
||||
"@compress": {},
|
||||
"unableToJoinChat": "Nevarēja pievienoties tērzēšanai. Varbūt otra puse jau ir aizvērusi sarunu.",
|
||||
"@unableToJoinChat": {}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2883,5 +2883,14 @@
|
|||
"version": "Версия",
|
||||
"@version": {},
|
||||
"website": "Сайт",
|
||||
"@website": {}
|
||||
"@website": {},
|
||||
"sendImages": "Отправить {count} изображений",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"compress": "Сжатие",
|
||||
"@compress": {}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2832,5 +2832,63 @@
|
|||
"oneOfYourDevicesIsNotVerified": "Aygıtlarınızdan biri doğrulanmadı",
|
||||
"@oneOfYourDevicesIsNotVerified": {},
|
||||
"noticeChatBackupDeviceVerification": "Not: Tüm aygıtlarınızı sohbet yedeklemesine bağladığınızda, otomatik olarak doğrulanırlar.",
|
||||
"@noticeChatBackupDeviceVerification": {}
|
||||
"@noticeChatBackupDeviceVerification": {},
|
||||
"blur": "Blur:",
|
||||
"@blur": {},
|
||||
"opacity": "Şeffaflık:",
|
||||
"@opacity": {},
|
||||
"setWallpaper": "Duvar kağıdı seç",
|
||||
"@setWallpaper": {},
|
||||
"manageAccount": "Hesabı yönet",
|
||||
"@manageAccount": {},
|
||||
"noContactInformationProvided": "Sunucu geçerli bir iletişim bilgisi sunmadı",
|
||||
"@noContactInformationProvided": {},
|
||||
"contactServerAdmin": "Sunucu yöneticisiyle iletişime geçin",
|
||||
"@contactServerAdmin": {},
|
||||
"contactServerSecurity": "Sunucu güvenliğiyle iletişime geçin",
|
||||
"@contactServerSecurity": {},
|
||||
"supportPage": "Destek sayfası",
|
||||
"@supportPage": {},
|
||||
"name": "İsim",
|
||||
"@name": {},
|
||||
"version": "Versiyon",
|
||||
"@version": {},
|
||||
"serverInformation": "Sunucu bilgisi:",
|
||||
"@serverInformation": {},
|
||||
"website": "Web sitesi",
|
||||
"@website": {},
|
||||
"compress": "Sıkıştırma",
|
||||
"@compress": {},
|
||||
"boldText": "Kalın metin",
|
||||
"@boldText": {},
|
||||
"italicText": "İtalik metin",
|
||||
"@italicText": {},
|
||||
"strikeThrough": "Üstü çizili",
|
||||
"@strikeThrough": {},
|
||||
"pleaseFillOut": "Lütfen doldurun",
|
||||
"@pleaseFillOut": {},
|
||||
"aboutHomeserver": "{homeserver} Hakkında",
|
||||
"@aboutHomeserver": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"homeserver": {}
|
||||
}
|
||||
},
|
||||
"invalidUrl": "Geçersiz url",
|
||||
"@invalidUrl": {},
|
||||
"addLink": "Link ekle",
|
||||
"@addLink": {},
|
||||
"unableToJoinChat": "Sohbete girilemiyor. Belki başka birileri konuşmayı kapatmış olabilir.",
|
||||
"@unableToJoinChat": {},
|
||||
"continueText": "Devam et",
|
||||
"@continueText": {},
|
||||
"sendImages": "{count} görsel gönder",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"welcomeText": "Hey Hey 👋 Karşınızda FluffyChat. https://matrix.org ile uyumlu herhangi bir homeserver'a giriş yapabilirsiniz. Ve herkesle konuşabilirsiniz. Bu koca bir merkeziyetsiz mesajlaşma ağı!",
|
||||
"@welcomeText": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2885,5 +2885,14 @@
|
|||
"addLink": "Додати посилання",
|
||||
"@addLink": {},
|
||||
"unableToJoinChat": "Неможливо приєднатися до чату. Можливо, інша сторона вже закрила розмову.",
|
||||
"@unableToJoinChat": {}
|
||||
"@unableToJoinChat": {},
|
||||
"sendImages": "Надіслати {count} зображення",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"compress": "Стиснути",
|
||||
"@compress": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -629,5 +629,25 @@
|
|||
"stickers": "Nhãn dán",
|
||||
"@stickers": {},
|
||||
"roomUpgradeDescription": "Cuộc trò chuyện sẽ được tạo lại với phiên bản phòng mới. Tất cả những người tham gia sẽ được thông báo rằng họ cần chuyển sang cuộc trò chuyện mới. Bạn có thể tìm hiểu thêm về các phiên bản phòng tại https://spec.matrix.org/latest/rooms/",
|
||||
"@roomUpgradeDescription": {}
|
||||
"@roomUpgradeDescription": {},
|
||||
"commandHint_hug": "Gửi một cái ôm",
|
||||
"@commandHint_hug": {},
|
||||
"aboutHomeserver": "Về {homeserver}",
|
||||
"@aboutHomeserver": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"homeserver": {}
|
||||
}
|
||||
},
|
||||
"alwaysUse24HourFormat": "Không",
|
||||
"@alwaysUse24HourFormat": {
|
||||
"description": "Set to true to always display time of day in 24 hour format."
|
||||
},
|
||||
"hugContent": "{senderName} ôm bạn",
|
||||
"@hugContent": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"senderName": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2885,5 +2885,14 @@
|
|||
"invalidUrl": "无效 url",
|
||||
"@invalidUrl": {},
|
||||
"unableToJoinChat": "无法加入聊天。可能其他方面已经关闭了对话。",
|
||||
"@unableToJoinChat": {}
|
||||
"@unableToJoinChat": {},
|
||||
"sendImages": "发送 {count} 张图片",
|
||||
"@sendImages": {
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
},
|
||||
"compress": "压缩",
|
||||
"@compress": {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,202 +0,0 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
96
fonts/Ubuntu/UFL.txt
Normal file
96
fonts/Ubuntu/UFL.txt
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
-------------------------------
|
||||
UBUNTU FONT LICENCE Version 1.0
|
||||
-------------------------------
|
||||
|
||||
PREAMBLE
|
||||
This licence allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely. The fonts, including any derivative works, can be
|
||||
bundled, embedded, and redistributed provided the terms of this licence
|
||||
are met. The fonts and derivatives, however, cannot be released under
|
||||
any other licence. The requirement for fonts to remain under this
|
||||
licence does not require any document created using the fonts or their
|
||||
derivatives to be published under this licence, as long as the primary
|
||||
purpose of the document is not to be a vehicle for the distribution of
|
||||
the fonts.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this licence and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Original Version" refers to the collection of Font Software components
|
||||
as received under this licence.
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to
|
||||
a new environment.
|
||||
|
||||
"Copyright Holder(s)" refers to all individuals and companies who have a
|
||||
copyright ownership of the Font Software.
|
||||
|
||||
"Substantially Changed" refers to Modified Versions which can be easily
|
||||
identified as dissimilar to the Font Software by users of the Font
|
||||
Software comparing the Original Version with the Modified Version.
|
||||
|
||||
To "Propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification and with or without charging
|
||||
a redistribution fee), making available to the public, and in some
|
||||
countries other activities as well.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
This licence does not grant any rights under trademark law and all such
|
||||
rights are reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of the Font Software, to propagate the Font Software, subject to
|
||||
the below conditions:
|
||||
|
||||
1) Each copy of the Font Software must contain the above copyright
|
||||
notice and this licence. These can be included either as stand-alone
|
||||
text files, human-readable headers or in the appropriate machine-
|
||||
readable metadata fields within text or binary files as long as those
|
||||
fields can be easily viewed by the user.
|
||||
|
||||
2) The font name complies with the following:
|
||||
(a) The Original Version must retain its name, unmodified.
|
||||
(b) Modified Versions which are Substantially Changed must be renamed to
|
||||
avoid use of the name of the Original Version or similar names entirely.
|
||||
(c) Modified Versions which are not Substantially Changed must be
|
||||
renamed to both (i) retain the name of the Original Version and (ii) add
|
||||
additional naming elements to distinguish the Modified Version from the
|
||||
Original Version. The name of such Modified Versions must be the name of
|
||||
the Original Version, with "derivative X" where X represents the name of
|
||||
the new work, appended to that name.
|
||||
|
||||
3) The name(s) of the Copyright Holder(s) and any contributor to the
|
||||
Font Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except (i) as required by this licence, (ii) to
|
||||
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
|
||||
their explicit written permission.
|
||||
|
||||
4) The Font Software, modified or unmodified, in part or in whole, must
|
||||
be distributed entirely under this licence, and must not be distributed
|
||||
under any other licence. The requirement for fonts to remain under this
|
||||
licence does not affect any document created using the Font Software,
|
||||
except any version of the Font Software extracted from a document
|
||||
created using the Font Software may only be distributed under this
|
||||
licence.
|
||||
|
||||
TERMINATION
|
||||
This licence becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
||||
DEALINGS IN THE FONT SOFTWARE.
|
||||
BIN
fonts/Ubuntu/Ubuntu-Bold.ttf
Normal file
BIN
fonts/Ubuntu/Ubuntu-Bold.ttf
Normal file
Binary file not shown.
BIN
fonts/Ubuntu/Ubuntu-BoldItalic.ttf
Normal file
BIN
fonts/Ubuntu/Ubuntu-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
fonts/Ubuntu/Ubuntu-Italic.ttf
Normal file
BIN
fonts/Ubuntu/Ubuntu-Italic.ttf
Normal file
Binary file not shown.
BIN
fonts/Ubuntu/Ubuntu-Regular.ttf
Normal file
BIN
fonts/Ubuntu/Ubuntu-Regular.ttf
Normal file
Binary file not shown.
BIN
fonts/Ubuntu/UbuntuMono-Regular.ttf
Normal file
BIN
fonts/Ubuntu/UbuntuMono-Regular.ttf
Normal file
Binary file not shown.
|
|
@ -27,11 +27,11 @@
|
|||
<key>NSExtensionActivationRule</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<integer>10</integer>
|
||||
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<integer>10</integer>
|
||||
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
|
||||
<integer>1</integer>
|
||||
<integer>10</integer>
|
||||
<key>NSExtensionActivationSupportsText</key>
|
||||
<true/>
|
||||
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
A10584DF00E2CBE024A7FEB1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F30C00BA233E7CA67AFBED5 /* Pods_Runner.framework */; };
|
||||
BCFA6E528F0B53B71B652C77 /* Pods_FluffyChat_Share.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B1F89C23F73F2B8E7922A37 /* Pods_FluffyChat_Share.framework */; };
|
||||
C1005C45261071B5002F4F32 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1005C44261071B5002F4F32 /* ShareViewController.swift */; };
|
||||
C1005C48261071B5002F4F32 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C1005C46261071B5002F4F32 /* MainInterface.storyboard */; };
|
||||
C1005C4C261071B5002F4F32 /* FluffyChat Share.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C1005C42261071B5002F4F32 /* FluffyChat Share.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
|
|
@ -60,6 +62,7 @@
|
|||
|
||||
/* Begin PBXFileReference section */
|
||||
09545B0C8C397F94966EA956 /* Pods-FluffyChat Share.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FluffyChat Share.debug.xcconfig"; path = "Target Support Files/Pods-FluffyChat Share/Pods-FluffyChat Share.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
0BDDCB1746F84339AF1A5F40 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
23120B990D2B5081843FB313 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
|
|
@ -78,6 +81,8 @@
|
|||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
9B1F89C23F73F2B8E7922A37 /* Pods_FluffyChat_Share.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FluffyChat_Share.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
9F30C00BA233E7CA67AFBED5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C1005C42261071B5002F4F32 /* FluffyChat Share.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "FluffyChat Share.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C1005C44261071B5002F4F32 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
C1005C47261071B5002F4F32 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import 'package:fluffychat/widgets/layouts/empty_page.dart';
|
|||
import 'package:fluffychat/widgets/layouts/two_column_layout.dart';
|
||||
import 'package:fluffychat/widgets/log_view.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/share_scaffold_dialog.dart';
|
||||
|
||||
abstract class AppRoutes {
|
||||
static FutureOr<String?> loggedInRedirect(
|
||||
|
|
@ -461,15 +462,25 @@ abstract class AppRoutes {
|
|||
),
|
||||
GoRoute(
|
||||
path: ':roomid',
|
||||
pageBuilder: (context, state) => defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
ChatPage(
|
||||
roomId: state.pathParameters['roomid']!,
|
||||
shareText: state.uri.queryParameters['body'],
|
||||
eventId: state.uri.queryParameters['event'],
|
||||
),
|
||||
),
|
||||
pageBuilder: (context, state) {
|
||||
final body = state.uri.queryParameters['body'];
|
||||
var shareItems = state.extra is List<ShareItem>
|
||||
? state.extra as List<ShareItem>
|
||||
: null;
|
||||
if (body != null && body.isNotEmpty) {
|
||||
shareItems ??= [];
|
||||
shareItems.add(TextShareItem(body));
|
||||
}
|
||||
return defaultPageBuilder(
|
||||
context,
|
||||
state,
|
||||
ChatPage(
|
||||
roomId: state.pathParameters['roomid']!,
|
||||
shareItems: shareItems,
|
||||
eventId: state.uri.queryParameters['event'],
|
||||
),
|
||||
);
|
||||
},
|
||||
redirect: loggedOutRedirect,
|
||||
routes: [
|
||||
GoRoute(
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ import 'package:flutter/cupertino.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'app_config.dart';
|
||||
|
||||
abstract class FluffyThemes {
|
||||
static const double columnWidth = 360.0;
|
||||
static const double columnWidth = 380.0;
|
||||
|
||||
static const double navRailWidth = 64.0;
|
||||
|
||||
|
|
@ -20,7 +19,7 @@ abstract class FluffyThemes {
|
|||
MediaQuery.of(context).size.width > FluffyThemes.columnWidth * 3.5;
|
||||
|
||||
static const fallbackTextStyle = TextStyle(
|
||||
fontFamily: 'Roboto',
|
||||
fontFamily: 'Ubuntu',
|
||||
fontFamilyFallback: ['NotoEmoji'],
|
||||
);
|
||||
|
||||
|
|
@ -68,22 +67,25 @@ abstract class FluffyThemes {
|
|||
brightness: brightness,
|
||||
seedColor: seed ?? AppConfig.colorSchemeSeed ?? AppConfig.primaryColor,
|
||||
);
|
||||
final isColumnMode = FluffyThemes.isColumnMode(context);
|
||||
return ThemeData(
|
||||
visualDensity: VisualDensity.standard,
|
||||
useMaterial3: true,
|
||||
brightness: brightness,
|
||||
colorScheme: colorScheme,
|
||||
textTheme: PlatformInfos.isDesktop
|
||||
? brightness == Brightness.light
|
||||
? Typography.material2018().black.merge(fallbackTextTheme)
|
||||
: Typography.material2018().white.merge(fallbackTextTheme)
|
||||
: null,
|
||||
textTheme: fallbackTextTheme,
|
||||
dividerColor: colorScheme.surfaceContainer,
|
||||
popupMenuTheme: PopupMenuThemeData(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
segmentedButtonTheme: SegmentedButtonThemeData(
|
||||
style: SegmentedButton.styleFrom(
|
||||
iconColor: colorScheme.onSurface,
|
||||
disabledIconColor: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
selectionColor: colorScheme.onSurface.withAlpha(128),
|
||||
selectionHandleColor: colorScheme.secondary,
|
||||
|
|
@ -96,14 +98,11 @@ abstract class FluffyThemes {
|
|||
filled: false,
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
toolbarHeight: FluffyThemes.isColumnMode(context) ? 72 : 56,
|
||||
shadowColor: FluffyThemes.isColumnMode(context)
|
||||
? colorScheme.surfaceContainer.withAlpha(128)
|
||||
: null,
|
||||
surfaceTintColor:
|
||||
FluffyThemes.isColumnMode(context) ? colorScheme.surface : null,
|
||||
backgroundColor:
|
||||
FluffyThemes.isColumnMode(context) ? colorScheme.surface : null,
|
||||
toolbarHeight: isColumnMode ? 72 : 56,
|
||||
shadowColor:
|
||||
isColumnMode ? colorScheme.surfaceContainer.withAlpha(128) : null,
|
||||
surfaceTintColor: isColumnMode ? colorScheme.surface : null,
|
||||
backgroundColor: isColumnMode ? colorScheme.surface : null,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: brightness.reversed,
|
||||
|
|
@ -124,14 +123,12 @@ abstract class FluffyThemes {
|
|||
),
|
||||
),
|
||||
),
|
||||
dialogTheme: DialogTheme(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
),
|
||||
),
|
||||
snackBarTheme: const SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
snackBarTheme: isColumnMode
|
||||
? const SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
width: FluffyThemes.columnWidth * 1.5,
|
||||
)
|
||||
: const SnackBarThemeData(behavior: SnackBarBehavior.floating),
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: colorScheme.secondaryContainer,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/archive/archive_view.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:matrix/encryption.dart';
|
||||
|
|
@ -11,6 +10,7 @@ import 'package:fluffychat/utils/error_reporter.dart';
|
|||
import 'package:fluffychat/utils/fluffy_share.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../utils/adaptive_bottom_sheet.dart';
|
||||
import '../key_verification/key_verification_dialog.dart';
|
||||
|
|
@ -27,7 +27,6 @@ class BootstrapDialog extends StatefulWidget {
|
|||
Future<bool?> show(BuildContext context) => showAdaptiveBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => this,
|
||||
maxHeight: 600,
|
||||
);
|
||||
|
||||
@override
|
||||
|
|
@ -133,7 +132,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
|||
minLines: 2,
|
||||
maxLines: 4,
|
||||
readOnly: true,
|
||||
style: const TextStyle(fontFamily: 'RobotoMono'),
|
||||
style: const TextStyle(fontFamily: 'UbuntuMono'),
|
||||
controller: TextEditingController(text: key),
|
||||
decoration: const InputDecoration(
|
||||
contentPadding: EdgeInsets.all(16),
|
||||
|
|
@ -258,7 +257,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
|||
? null
|
||||
: [AutofillHints.password],
|
||||
controller: _recoveryKeyTextEditingController,
|
||||
style: const TextStyle(fontFamily: 'RobotoMono'),
|
||||
style: const TextStyle(fontFamily: 'UbuntuMono'),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(16),
|
||||
hintStyle: TextStyle(
|
||||
|
|
@ -275,6 +274,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
|||
ElevatedButton.icon(
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
iconColor: theme.colorScheme.onPrimary,
|
||||
backgroundColor: theme.colorScheme.primary,
|
||||
),
|
||||
icon: _recoveryKeyInputLoading
|
||||
|
|
@ -366,7 +366,6 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
|||
.verifyOtherDeviceDescription,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
fullyCapitalizedForMaterial: false,
|
||||
);
|
||||
if (consent != OkCancelResult.ok) return;
|
||||
final req = await showFutureLoadingDialog(
|
||||
|
|
@ -390,6 +389,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
|||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.errorContainer,
|
||||
foregroundColor: theme.colorScheme.onErrorContainer,
|
||||
iconColor: theme.colorScheme.onErrorContainer,
|
||||
),
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
label: Text(L10n.of(context).recoveryKeyLost),
|
||||
|
|
@ -404,7 +404,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
|||
message: L10n.of(context).wipeChatBackup,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
isDestructiveAction: true,
|
||||
isDestructive: true,
|
||||
)) {
|
||||
setState(() => _createBootstrap(true));
|
||||
}
|
||||
|
|
@ -444,7 +444,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
|||
titleText = L10n.of(context).oopsSomethingWentWrong;
|
||||
body = const Icon(Icons.error_outline, color: Colors.red, size: 80);
|
||||
buttons.add(
|
||||
OutlinedButton(
|
||||
ElevatedButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context, rootNavigator: false).pop<bool>(false),
|
||||
child: Text(L10n.of(context).close),
|
||||
|
|
@ -470,7 +470,7 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
|||
],
|
||||
);
|
||||
buttons.add(
|
||||
OutlinedButton(
|
||||
ElevatedButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context, rootNavigator: false).pop<bool>(false),
|
||||
child: Text(L10n.of(context).close),
|
||||
|
|
@ -491,13 +491,17 @@ class BootstrapDialogState extends State<BootstrapDialog> {
|
|||
title: Text(titleText ?? L10n.of(context).loadingPleaseWait),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
body,
|
||||
const SizedBox(height: 8),
|
||||
...buttons,
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
body,
|
||||
const SizedBox(height: 8),
|
||||
...buttons,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// ignore_for_file: depend_on_referenced_packages, implementation_imports
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:core';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
|
|
@ -9,7 +8,6 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart';
|
||||
|
|
@ -54,9 +52,15 @@ import 'package:fluffychat/utils/file_selector.dart';
|
|||
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/filtered_timeline_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/utils/other_party_can_receive.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/utils/show_scaffold_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/share_scaffold_dialog.dart';
|
||||
import '../../utils/account_bundles.dart';
|
||||
import '../../utils/localized_exception_extension.dart';
|
||||
import 'send_file_dialog.dart';
|
||||
|
|
@ -64,14 +68,14 @@ import 'send_location_dialog.dart';
|
|||
|
||||
class ChatPage extends StatelessWidget {
|
||||
final String roomId;
|
||||
final String? shareText;
|
||||
final List<ShareItem>? shareItems;
|
||||
final String? eventId;
|
||||
|
||||
const ChatPage({
|
||||
super.key,
|
||||
required this.roomId,
|
||||
this.eventId,
|
||||
this.shareText,
|
||||
this.shareItems,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -95,7 +99,7 @@ class ChatPage extends StatelessWidget {
|
|||
return ChatPageWithRoom(
|
||||
key: Key('chat_page_${roomId}_$eventId'),
|
||||
room: room,
|
||||
shareText: shareText,
|
||||
shareItems: shareItems,
|
||||
eventId: eventId,
|
||||
);
|
||||
}
|
||||
|
|
@ -103,13 +107,13 @@ class ChatPage extends StatelessWidget {
|
|||
|
||||
class ChatPageWithRoom extends StatefulWidget {
|
||||
final Room room;
|
||||
final String? shareText;
|
||||
final List<ShareItem>? shareItems;
|
||||
final String? eventId;
|
||||
|
||||
const ChatPageWithRoom({
|
||||
super.key,
|
||||
required this.room,
|
||||
this.shareText,
|
||||
this.shareItems,
|
||||
this.eventId,
|
||||
});
|
||||
|
||||
|
|
@ -143,11 +147,12 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
Timer? typingTimeout;
|
||||
bool currentlyTyping = false;
|
||||
// #Pangea
|
||||
|
||||
// bool dragging = false;
|
||||
|
||||
// void onDragEntered(_) => setState(() => dragging = true);
|
||||
|
||||
// void onDragExited(_) => setState(() => dragging = false);
|
||||
|
||||
// void onDragDone(DropDoneDetails details) async {
|
||||
// setState(() => dragging = false);
|
||||
// if (details.files.isEmpty) return;
|
||||
|
|
@ -161,16 +166,8 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// await showAdaptiveDialog(
|
||||
// context: context,
|
||||
// builder: (c) => SendFileDialog(
|
||||
// files: matrixFiles,
|
||||
// room: room,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// Pangea#
|
||||
|
||||
bool get canSaveSelectedEvent =>
|
||||
selectedEvents.length == 1 &&
|
||||
{
|
||||
|
|
@ -248,7 +245,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
setReadMarker(eventId: mostRecentEventId);
|
||||
}
|
||||
|
||||
void updateScrollController() {
|
||||
void _updateScrollController() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -267,22 +264,63 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
}
|
||||
|
||||
void loadDraft() async {
|
||||
void _loadDraft() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final draft = widget.shareText ?? prefs.getString('draft_$roomId');
|
||||
final draft = prefs.getString('draft_$roomId');
|
||||
if (draft != null && draft.isNotEmpty) {
|
||||
sendController.text = draft;
|
||||
}
|
||||
}
|
||||
|
||||
void _shareItems([_]) {
|
||||
final shareItems = widget.shareItems;
|
||||
if (shareItems == null || shareItems.isEmpty) return;
|
||||
if (!room.otherPartyCanReceiveMessages) {
|
||||
final theme = Theme.of(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: theme.colorScheme.errorContainer,
|
||||
closeIconColor: theme.colorScheme.onErrorContainer,
|
||||
content: Text(
|
||||
L10n.of(context).otherPartyNotLoggedIn,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onErrorContainer,
|
||||
),
|
||||
),
|
||||
showCloseIcon: true,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (final item in shareItems) {
|
||||
if (item is FileShareItem) continue;
|
||||
if (item is TextShareItem) room.sendTextEvent(item.value);
|
||||
if (item is ContentShareItem) room.sendEvent(item.value);
|
||||
}
|
||||
final files = shareItems
|
||||
.whereType<FileShareItem>()
|
||||
.map((item) => item.value)
|
||||
.toList();
|
||||
if (files.isEmpty) return;
|
||||
showAdaptiveDialog(
|
||||
context: context,
|
||||
builder: (c) => SendFileDialog(
|
||||
files: files,
|
||||
room: room,
|
||||
outerContext: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
scrollController.addListener(updateScrollController);
|
||||
inputFocus.addListener(inputFocusListener);
|
||||
scrollController.addListener(_updateScrollController);
|
||||
inputFocus.addListener(_inputFocusListener);
|
||||
|
||||
loadDraft();
|
||||
_loadDraft();
|
||||
WidgetsBinding.instance.addPostFrameCallback(_shareItems);
|
||||
super.initState();
|
||||
displayChatDetailsColumn = ValueNotifier(
|
||||
_displayChatDetailsColumn = ValueNotifier(
|
||||
Matrix.of(context).store.getBool(SettingKeys.displayChatDetailsColumn) ??
|
||||
false,
|
||||
);
|
||||
|
|
@ -320,13 +358,13 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
),
|
||||
);
|
||||
// Pangea#
|
||||
tryLoadTimeline();
|
||||
_tryLoadTimeline();
|
||||
if (kIsWeb) {
|
||||
onFocusSub = html.window.onFocus.listen((_) => setReadMarker());
|
||||
}
|
||||
}
|
||||
|
||||
void tryLoadTimeline() async {
|
||||
void _tryLoadTimeline() async {
|
||||
final initialEventId = widget.eventId;
|
||||
loadTimelineFuture = _getTimeline();
|
||||
try {
|
||||
|
|
@ -353,7 +391,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
scrollToEventId(readMarkerEventId, highlightEvent: false);
|
||||
return;
|
||||
} else if (readMarkerEventId.isNotEmpty && readMarkerEventIndex == -1) {
|
||||
showScrollUpMaterialBanner(readMarkerEventId);
|
||||
_showScrollUpMaterialBanner(readMarkerEventId);
|
||||
}
|
||||
|
||||
// Mark room as read on first visit if requirements are fulfilled
|
||||
|
|
@ -372,7 +410,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
scrollUpBannerEventId = null;
|
||||
});
|
||||
|
||||
void showScrollUpMaterialBanner(String eventId) => setState(() {
|
||||
void _showScrollUpMaterialBanner(String eventId) => setState(() {
|
||||
scrollUpBannerEventId = eventId;
|
||||
});
|
||||
|
||||
|
|
@ -448,7 +486,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
if (!mounted) return;
|
||||
if (e is TimeoutException || e is IOException) {
|
||||
showScrollUpMaterialBanner(eventContextId!);
|
||||
_showScrollUpMaterialBanner(eventContextId!);
|
||||
}
|
||||
}
|
||||
timeline!.requestKeys(onlineKeyBackupOnly: false);
|
||||
|
|
@ -473,7 +511,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
setReadMarker();
|
||||
}
|
||||
|
||||
Future<void>? setReadMarkerFuture;
|
||||
Future<void>? _setReadMarkerFuture;
|
||||
|
||||
void setReadMarker({String? eventId}) {
|
||||
// #Pangea
|
||||
|
|
@ -485,7 +523,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
return;
|
||||
}
|
||||
// Pangea#
|
||||
if (setReadMarkerFuture != null) return;
|
||||
if (_setReadMarkerFuture != null) return;
|
||||
if (_scrolledUp) return;
|
||||
if (scrollUpBannerEventId != null) return;
|
||||
|
||||
|
|
@ -511,19 +549,17 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
final timeline = this.timeline;
|
||||
if (timeline == null || timeline.events.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (timeline == null || timeline.events.isEmpty) return;
|
||||
|
||||
Logs().d('Set read marker...', eventId);
|
||||
// ignore: unawaited_futures
|
||||
setReadMarkerFuture = timeline
|
||||
_setReadMarkerFuture = timeline
|
||||
.setReadMarker(
|
||||
eventId: eventId,
|
||||
public: AppConfig.sendPublicReadReceipts,
|
||||
)
|
||||
.then((_) {
|
||||
setReadMarkerFuture = null;
|
||||
_setReadMarkerFuture = null;
|
||||
})
|
||||
// #Pangea
|
||||
.catchError((e, s) {
|
||||
|
|
@ -544,7 +580,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
});
|
||||
// Pangea#
|
||||
|
||||
if (eventId == null || eventId == timeline.room.lastEvent?.eventId) {
|
||||
Matrix.of(context).backgroundPush?.cancelNotification(roomId);
|
||||
}
|
||||
|
|
@ -554,7 +589,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
void dispose() {
|
||||
timeline?.cancelSubscriptions();
|
||||
timeline = null;
|
||||
inputFocus.removeListener(inputFocusListener);
|
||||
inputFocus.removeListener(_inputFocusListener);
|
||||
onFocusSub?.cancel();
|
||||
//#Pangea
|
||||
choreographer.stateListener.close();
|
||||
|
|
@ -645,7 +680,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}) async {
|
||||
// Pangea#
|
||||
if (sendController.text.trim().isEmpty) return;
|
||||
storeInputTimeoutTimer?.cancel();
|
||||
_storeInputTimeoutTimer?.cancel();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.remove('draft_$roomId');
|
||||
var parseCommands = true;
|
||||
|
|
@ -677,7 +712,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
// wait for the next event to come through before clearing any fake event,
|
||||
// to make the replacement look smooth
|
||||
room.client.onEvent.stream.first.then((_) => clearFakeEvent());
|
||||
room.client.onTimelineEvent.stream.first.then((_) => clearFakeEvent());
|
||||
|
||||
room
|
||||
.pangeaSendTextEvent(
|
||||
|
|
@ -762,7 +797,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
|
||||
setState(() {
|
||||
sendController.text = pendingText;
|
||||
inputTextIsEmpty = pendingText.isEmpty;
|
||||
_inputTextIsEmpty = pendingText.isEmpty;
|
||||
replyEvent = null;
|
||||
editEvent = null;
|
||||
pendingText = '';
|
||||
|
|
@ -945,7 +980,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
setState(() => showEmojiPicker = !showEmojiPicker);
|
||||
}
|
||||
|
||||
void inputFocusListener() {
|
||||
void _inputFocusListener() {
|
||||
if (showEmojiPicker && inputFocus.hasFocus) {
|
||||
emojiPickerType = EmojiPickerType.keyboard;
|
||||
setState(() => showEmojiPicker = false);
|
||||
|
|
@ -959,7 +994,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
}
|
||||
|
||||
String getSelectedEventString() {
|
||||
String _getSelectedEventString() {
|
||||
var copyString = '';
|
||||
if (selectedEvents.length == 1) {
|
||||
return selectedEvents.first
|
||||
|
|
@ -977,7 +1012,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
void copyEventsAction() {
|
||||
Clipboard.setData(ClipboardData(text: getSelectedEventString()));
|
||||
Clipboard.setData(ClipboardData(text: _getSelectedEventString()));
|
||||
setState(() {
|
||||
showEmojiPicker = false;
|
||||
// #Pangea
|
||||
|
|
@ -992,23 +1027,22 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
// #Pangea
|
||||
clearSelectedEvents();
|
||||
// Pangea#
|
||||
final score = await showConfirmationDialog<int>(
|
||||
final score = await showModalActionPopup<int>(
|
||||
context: context,
|
||||
title: L10n.of(context).reportMessage,
|
||||
message: L10n.of(context).howOffensiveIsThisContent,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
okLabel: L10n.of(context).ok,
|
||||
actions: [
|
||||
AlertDialogAction(
|
||||
key: -100,
|
||||
AdaptiveModalAction(
|
||||
value: -100,
|
||||
label: L10n.of(context).extremeOffensive,
|
||||
),
|
||||
AlertDialogAction(
|
||||
key: -50,
|
||||
AdaptiveModalAction(
|
||||
value: -50,
|
||||
label: L10n.of(context).offensive,
|
||||
),
|
||||
AlertDialogAction(
|
||||
key: 0,
|
||||
AdaptiveModalAction(
|
||||
value: 0,
|
||||
label: L10n.of(context).inoffensive,
|
||||
),
|
||||
],
|
||||
|
|
@ -1019,18 +1053,18 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
title: L10n.of(context).whyDoYouWantToReportThis,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
textFields: [DialogTextField(hintText: L10n.of(context).reason)],
|
||||
hintText: L10n.of(context).reason,
|
||||
// #Pangea
|
||||
autoSubmit: true,
|
||||
// Pangea#
|
||||
);
|
||||
if (reason == null || reason.single.isEmpty) return;
|
||||
if (reason == null || reason.isEmpty) return;
|
||||
// #Pangea
|
||||
try {
|
||||
await reportMessage(
|
||||
context,
|
||||
roomId,
|
||||
reason.single,
|
||||
reason,
|
||||
event.senderId,
|
||||
event.content['body'].toString(),
|
||||
);
|
||||
|
|
@ -1040,7 +1074,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
s: StackTrace.current,
|
||||
data: {
|
||||
'roomId': roomId,
|
||||
'reason': reason.single,
|
||||
'reason': reason,
|
||||
'senderId': event.senderId,
|
||||
'content': event.content['body'].toString(),
|
||||
},
|
||||
|
|
@ -1055,10 +1089,10 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
// final result = await showFutureLoadingDialog(
|
||||
// context: context,
|
||||
// future: () => Matrix.of(context).client.reportContent(
|
||||
// future: () => Matrix.of(context).client.reportEvent(
|
||||
// event.roomId!,
|
||||
// event.eventId,
|
||||
// reason: reason.single,
|
||||
// reason: reason,
|
||||
// score: score,
|
||||
// ),
|
||||
// );
|
||||
|
|
@ -1098,26 +1132,22 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
context: context,
|
||||
title: L10n.of(context).redactMessage,
|
||||
message: L10n.of(context).redactMessageDescription,
|
||||
isDestructiveAction: true,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
hintText: L10n.of(context).optionalRedactReason,
|
||||
),
|
||||
],
|
||||
isDestructive: true,
|
||||
hintText: L10n.of(context).optionalRedactReason,
|
||||
okLabel: L10n.of(context).remove,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
// #Pangea
|
||||
autoSubmit: true,
|
||||
// Pangea#
|
||||
)
|
||||
: <String>[];
|
||||
: null;
|
||||
if (reasonInput == null) {
|
||||
// #Pangea
|
||||
clearSelectedEvents();
|
||||
// Pangea#
|
||||
return;
|
||||
}
|
||||
final reason = reasonInput.single.isEmpty ? null : reasonInput.single;
|
||||
final reason = reasonInput.isEmpty ? null : reasonInput;
|
||||
for (final event in selectedEvents) {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
|
|
@ -1196,17 +1226,17 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
|
||||
void forwardEventsAction() async {
|
||||
if (selectedEvents.length == 1) {
|
||||
Matrix.of(context).shareContent =
|
||||
selectedEvents.first.getDisplayEvent(timeline!).content;
|
||||
} else {
|
||||
Matrix.of(context).shareContent = {
|
||||
'msgtype': 'm.text',
|
||||
'body': getSelectedEventString(),
|
||||
};
|
||||
}
|
||||
if (selectedEvents.isEmpty) return;
|
||||
await showScaffoldDialog(
|
||||
context: context,
|
||||
builder: (context) => ShareScaffoldDialog(
|
||||
items: selectedEvents
|
||||
.map((event) => ContentShareItem(event.content))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() => selectedEvents.clear());
|
||||
context.go('/rooms');
|
||||
}
|
||||
|
||||
void sendAgainAction() {
|
||||
|
|
@ -1283,7 +1313,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
duration: FluffyThemes.animationDuration,
|
||||
preferPosition: AutoScrollPosition.middle,
|
||||
);
|
||||
updateScrollController();
|
||||
_updateScrollController();
|
||||
}
|
||||
|
||||
void scrollDown() async {
|
||||
|
|
@ -1397,18 +1427,20 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
// Pangea#
|
||||
|
||||
// #Pangea
|
||||
// void clearSelectedEvents() => setState(() {
|
||||
// selectedEvents.clear();
|
||||
// showEmojiPicker = false;
|
||||
// });
|
||||
void clearSelectedEvents() {
|
||||
// #Pangea
|
||||
if (!mounted) return;
|
||||
// Pangea#
|
||||
setState(() {
|
||||
// #Pangea
|
||||
closeSelectionOverlay();
|
||||
// Pangea#
|
||||
selectedEvents.clear();
|
||||
showEmojiPicker = false;
|
||||
});
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
void clearSingleSelectedEvent() {
|
||||
if (selectedEvents.length <= 1) {
|
||||
|
|
@ -1455,16 +1487,16 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
final result = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room.client.joinRoom(
|
||||
room
|
||||
.getState(EventTypes.RoomTombstone)!
|
||||
.parsedTombstoneContent
|
||||
.replacementRoom,
|
||||
),
|
||||
);
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: room.leave,
|
||||
future: () async {
|
||||
final roomId = room.client.joinRoom(
|
||||
room
|
||||
.getState(EventTypes.RoomTombstone)!
|
||||
.parsedTombstoneContent
|
||||
.replacementRoom,
|
||||
);
|
||||
await room.leave();
|
||||
return roomId;
|
||||
},
|
||||
);
|
||||
if (result.error == null) {
|
||||
context.go('/rooms/${result.result!}');
|
||||
|
|
@ -1587,18 +1619,18 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
);
|
||||
}
|
||||
|
||||
Timer? storeInputTimeoutTimer;
|
||||
static const storeInputTimeout = Duration(milliseconds: 500);
|
||||
Timer? _storeInputTimeoutTimer;
|
||||
Duration storeInputTimeout = const Duration(milliseconds: 500);
|
||||
|
||||
void onInputBarChanged(String text) {
|
||||
if (inputTextIsEmpty != text.isEmpty) {
|
||||
if (_inputTextIsEmpty != text.isEmpty) {
|
||||
setState(() {
|
||||
inputTextIsEmpty = text.isEmpty;
|
||||
_inputTextIsEmpty = text.isEmpty;
|
||||
});
|
||||
}
|
||||
|
||||
storeInputTimeoutTimer?.cancel();
|
||||
storeInputTimeoutTimer = Timer(storeInputTimeout, () async {
|
||||
_storeInputTimeoutTimer?.cancel();
|
||||
_storeInputTimeoutTimer = Timer(storeInputTimeout, () async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('draft_$roomId', text);
|
||||
});
|
||||
|
|
@ -1637,7 +1669,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
}
|
||||
|
||||
var inputTextIsEmpty = true;
|
||||
bool _inputTextIsEmpty = true;
|
||||
|
||||
bool get isArchived =>
|
||||
{Membership.leave, Membership.ban}.contains(room.membership);
|
||||
|
|
@ -1664,21 +1696,21 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
}
|
||||
});
|
||||
}
|
||||
final callType = await showModalActionSheet<CallType>(
|
||||
final callType = await showModalActionPopup<CallType>(
|
||||
context: context,
|
||||
title: L10n.of(context).warning,
|
||||
message: L10n.of(context).videoCallsBetaWarning,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
actions: [
|
||||
SheetAction(
|
||||
AdaptiveModalAction(
|
||||
label: L10n.of(context).voiceCall,
|
||||
icon: Icons.phone_outlined,
|
||||
key: CallType.kVoice,
|
||||
icon: const Icon(Icons.phone_outlined),
|
||||
value: CallType.kVoice,
|
||||
),
|
||||
SheetAction(
|
||||
AdaptiveModalAction(
|
||||
label: L10n.of(context).videoCall,
|
||||
icon: Icons.video_call_outlined,
|
||||
key: CallType.kVideo,
|
||||
icon: const Icon(Icons.video_call_outlined),
|
||||
value: CallType.kVideo,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
@ -1787,48 +1819,16 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
onSelectMessage(event);
|
||||
});
|
||||
}
|
||||
// Pangea#
|
||||
|
||||
// final List<int> selectedTokenIndicies = [];
|
||||
// void onClickOverlayMessageToken(
|
||||
// PangeaMessageEvent pangeaMessageEvent,
|
||||
// int tokenIndex,
|
||||
// ) {
|
||||
// if (pangeaMessageEvent.originalSent?.tokens == null ||
|
||||
// tokenIndex < 0 ||
|
||||
// tokenIndex >= pangeaMessageEvent.originalSent!.tokens!.length) {
|
||||
// selectedTokenIndicies.clear();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // if there's stuff that's already selected, then we already ahve a sentence deselect
|
||||
// if (selectedTokenIndicies.isNotEmpty) {
|
||||
// final bool listContainedIndex =
|
||||
// selectedTokenIndicies.contains(tokenIndex);
|
||||
|
||||
// selectedTokenIndicies.clear();
|
||||
// if (!listContainedIndex) {
|
||||
// selectedTokenIndicies.add(tokenIndex);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // TODO
|
||||
// // if this is already selected, see if there's sentnence and selelct that
|
||||
|
||||
// // if nothing is select, select one token
|
||||
// else {
|
||||
// selectedTokenIndicies.add(tokenIndex);
|
||||
// }
|
||||
// }
|
||||
// // Pangea#
|
||||
|
||||
late final ValueNotifier<bool> displayChatDetailsColumn;
|
||||
late final ValueNotifier<bool> _displayChatDetailsColumn;
|
||||
|
||||
void toggleDisplayChatDetailsColumn() async {
|
||||
await Matrix.of(context).store.setBool(
|
||||
SettingKeys.displayChatDetailsColumn,
|
||||
!displayChatDetailsColumn.value,
|
||||
!_displayChatDetailsColumn.value,
|
||||
);
|
||||
displayChatDetailsColumn.value = !displayChatDetailsColumn.value;
|
||||
_displayChatDetailsColumn.value = !_displayChatDetailsColumn.value;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -1843,7 +1843,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: displayChatDetailsColumn,
|
||||
valueListenable: _displayChatDetailsColumn,
|
||||
builder: (context, displayChatDetailsColumn, _) {
|
||||
if (!FluffyThemes.isThreeColumnMode(context) ||
|
||||
room.membership != Membership.join ||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/utils/sync_status_localization.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/presence_builder.dart';
|
||||
|
||||
|
|
@ -57,30 +59,68 @@ class ChatAppBarTitle extends StatelessWidget {
|
|||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: PresenceBuilder(
|
||||
userId: room.directChatMatrixID,
|
||||
builder: (context, presence) {
|
||||
final lastActiveTimestamp = presence?.lastActiveTimestamp;
|
||||
final style = Theme.of(context).textTheme.bodySmall;
|
||||
if (presence?.currentlyActive == true) {
|
||||
return Text(
|
||||
L10n.of(context).currentlyActive,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
if (lastActiveTimestamp != null) {
|
||||
return Text(
|
||||
L10n.of(context).lastActiveAgo(
|
||||
lastActiveTimestamp.localizedTimeShort(context),
|
||||
),
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: room.client.onSyncStatus.stream,
|
||||
builder: (context, snapshot) {
|
||||
final status = room.client.onSyncStatus.value ??
|
||||
const SyncStatusUpdate(SyncStatus.waitingForResponse);
|
||||
final hide = FluffyThemes.isColumnMode(context) ||
|
||||
(room.client.onSync.value != null &&
|
||||
status.status != SyncStatus.error &&
|
||||
room.client.prevBatch != null);
|
||||
return AnimatedSize(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
child: hide
|
||||
? PresenceBuilder(
|
||||
userId: room.directChatMatrixID,
|
||||
builder: (context, presence) {
|
||||
final lastActiveTimestamp =
|
||||
presence?.lastActiveTimestamp;
|
||||
final style =
|
||||
Theme.of(context).textTheme.bodySmall;
|
||||
if (presence?.currentlyActive == true) {
|
||||
return Text(
|
||||
L10n.of(context).currentlyActive,
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
if (lastActiveTimestamp != null) {
|
||||
return Text(
|
||||
L10n.of(context).lastActiveAgo(
|
||||
lastActiveTimestamp
|
||||
.localizedTimeShort(context),
|
||||
),
|
||||
style: style,
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
)
|
||||
: Row(
|
||||
children: [
|
||||
Icon(
|
||||
status.icon,
|
||||
size: 12,
|
||||
color: status.error != null
|
||||
? Theme.of(context).colorScheme.error
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: Text(
|
||||
status.calcLocalizedString(context),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: status.error != null
|
||||
? Theme.of(context).colorScheme.error
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ class ChatEmojiPicker extends StatelessWidget {
|
|||
theme.colorScheme.primary.withAlpha(128),
|
||||
iconColorSelected: theme.colorScheme.primary,
|
||||
indicatorColor: theme.colorScheme.primary,
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
),
|
||||
skinToneConfig: SkinToneConfig(
|
||||
dialogBackgroundColor: Color.lerp(
|
||||
|
|
@ -107,9 +108,15 @@ class NoRecent extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
L10n.of(context).emoteKeyboardNoRecents,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
L10n.of(context).emoteKeyboardNoRecents,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import 'package:fluffychat/pangea/choreographer/widgets/start_igc_button.dart';
|
|||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/pangea_reaction_picker.dart';
|
||||
import 'package:fluffychat/utils/other_party_can_receive.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import '../../config/themes.dart';
|
||||
import 'chat.dart';
|
||||
|
|
@ -38,6 +39,20 @@ class ChatInputRow extends StatelessWidget {
|
|||
controller.emojiPickerType == EmojiPickerType.reaction) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
if (!controller.room.otherPartyCanReceiveMessages) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Text(
|
||||
L10n.of(context).otherPartyNotLoggedIn,
|
||||
style: theme.textTheme.bodySmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
// const height = 48.0;
|
||||
const height = AppConfig.defaultFooterHeight;
|
||||
|
|
@ -129,7 +144,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
child: Row(
|
||||
children: <Widget>[
|
||||
// #Pangea
|
||||
// Text(L10n.of(context)!.reply),
|
||||
// Text(L10n.of(context).reply),
|
||||
// const Icon(Icons.keyboard_arrow_right),
|
||||
const Icon(Symbols.reply),
|
||||
const SizedBox(width: 6),
|
||||
|
|
@ -148,12 +163,11 @@ class ChatInputRow extends StatelessWidget {
|
|||
// children: <Widget>[
|
||||
// Text(L10n.of(context).tryToSendAgain),
|
||||
// const SizedBox(width: 4),
|
||||
// const Icon(Icons.send_outlined,
|
||||
// size: 16),
|
||||
// const Icon(Icons.send_outlined, size: 16),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// )
|
||||
// ),
|
||||
// Pangea#
|
||||
: const SizedBox.shrink(),
|
||||
// #Pangea
|
||||
|
|
@ -194,44 +208,31 @@ class ChatInputRow extends StatelessWidget {
|
|||
onSelected: controller.onAddPopupMenuButtonSelected,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
//#Pangea
|
||||
if (controller.pangeaController.permissionsController
|
||||
.canShareFile(controller.roomId))
|
||||
//Pangea#
|
||||
PopupMenuItem<String>(
|
||||
value: 'file',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.attachment_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendFile),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
PopupMenuItem<String>(
|
||||
value: 'file',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.green,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.attachment_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendFile),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
//#Pangea
|
||||
if (controller.pangeaController.permissionsController
|
||||
.canSharePhoto(controller.roomId))
|
||||
//Pangea#
|
||||
PopupMenuItem<String>(
|
||||
value: 'image',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.image_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendImage),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
value: 'image',
|
||||
child: ListTile(
|
||||
leading: const CircleAvatar(
|
||||
backgroundColor: Colors.blue,
|
||||
foregroundColor: Colors.white,
|
||||
child: Icon(Icons.image_outlined),
|
||||
),
|
||||
title: Text(L10n.of(context).sendImage),
|
||||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
//#Pangea
|
||||
// if (PlatformInfos.isMobile)
|
||||
if (PlatformInfos.isMobile &&
|
||||
controller.pangeaController.permissionsController
|
||||
.canSharePhoto(controller.roomId))
|
||||
//Pangea#
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera',
|
||||
child: ListTile(
|
||||
|
|
@ -244,12 +245,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
//#Pangea
|
||||
// if (PlatformInfos.isMobile)
|
||||
if (PlatformInfos.isMobile &&
|
||||
controller.pangeaController.permissionsController
|
||||
.canShareVideo(controller.roomId))
|
||||
//Pangea#
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'camera-video',
|
||||
child: ListTile(
|
||||
|
|
@ -262,12 +258,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
contentPadding: const EdgeInsets.all(0),
|
||||
),
|
||||
),
|
||||
//#Pangea
|
||||
// if (PlatformInfos.isMobile)
|
||||
if (PlatformInfos.isMobile &&
|
||||
controller.pangeaController.permissionsController
|
||||
.canShareLocation(controller.roomId))
|
||||
//Pangea#
|
||||
if (PlatformInfos.isMobile)
|
||||
PopupMenuItem<String>(
|
||||
value: 'location',
|
||||
child: ListTile(
|
||||
|
|
@ -319,7 +310,7 @@ class ChatInputRow extends StatelessWidget {
|
|||
),
|
||||
)
|
||||
// #Pangea
|
||||
: const SizedBox(width: 10),
|
||||
: const SizedBox.shrink(),
|
||||
// if (Matrix.of(context).isMultiAccount &&
|
||||
// Matrix.of(context).hasComplexBundles &&
|
||||
// Matrix.of(context).currentBundle!.length > 1)
|
||||
|
|
@ -465,9 +456,6 @@ class ChatInputRow extends StatelessWidget {
|
|||
// mxContent: snapshot.data?.avatarUrl,
|
||||
// name: snapshot.data?.displayName ??
|
||||
// client.userID!.localpart,
|
||||
// // #Pangea
|
||||
// presenceUserId: client.userID!,
|
||||
// // Pangea#
|
||||
// size: 20,
|
||||
// ),
|
||||
// title: Text(snapshot.data?.displayName ?? client.userID!),
|
||||
|
|
@ -481,9 +469,6 @@ class ChatInputRow extends StatelessWidget {
|
|||
// mxContent: snapshot.data?.avatarUrl,
|
||||
// name: snapshot.data?.displayName ??
|
||||
// Matrix.of(context).client.userID!.localpart,
|
||||
// // #Pangea
|
||||
// presenceUserId: Matrix.of(context).client.userID!,
|
||||
// // Pangea#
|
||||
// size: 20,
|
||||
// ),
|
||||
// ),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import 'package:fluffychat/config/themes.dart';
|
|||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_app_bar_title.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_emoji_picker.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_event_list.dart';
|
||||
import 'package:fluffychat/pages/chat/pinned_events.dart';
|
||||
import 'package:fluffychat/pages/chat/reply_display.dart';
|
||||
|
|
@ -26,12 +25,12 @@ import 'package:fluffychat/pangea/choreographer/widgets/it_bar.dart';
|
|||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/utils/account_config.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/connection_status_header.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
import 'package:fluffychat/widgets/unread_rooms_badge.dart';
|
||||
import '../../utils/stream_extension.dart';
|
||||
import 'chat_emoji_picker.dart';
|
||||
|
||||
enum _EventContextAction { info, report }
|
||||
|
||||
|
|
@ -144,7 +143,8 @@ class ChatView extends StatelessWidget {
|
|||
}
|
||||
// } else if (!controller.room.isArchived) {
|
||||
// return [
|
||||
// if (Matrix.of(context).voipPlugin != null &&
|
||||
// if (AppConfig.experimentalVoip &&
|
||||
// Matrix.of(context).voipPlugin != null &&
|
||||
// controller.room.isDirectChat)
|
||||
// IconButton(
|
||||
// onPressed: controller.onPhoneButtonTap,
|
||||
|
|
@ -287,6 +287,8 @@ class ChatView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
// #Pangea
|
||||
// floatingActionButtonLocation:
|
||||
// FloatingActionButtonLocation.miniCenterFloat,
|
||||
// floatingActionButton: controller.showScrollDownButton &&
|
||||
// controller.selectedEvents.isEmpty
|
||||
// ? Padding(
|
||||
|
|
@ -295,20 +297,19 @@ class ChatView extends StatelessWidget {
|
|||
// onPressed: controller.scrollDown,
|
||||
// heroTag: null,
|
||||
// mini: true,
|
||||
// backgroundColor: theme.colorScheme.surface,
|
||||
// foregroundColor: theme.colorScheme.onSurface,
|
||||
// child: const Icon(Icons.arrow_downward_outlined),
|
||||
// ),
|
||||
// )
|
||||
// : null,
|
||||
// Pangea#
|
||||
body:
|
||||
// #Pangea
|
||||
// DropTarget(
|
||||
// onDragDone: controller.onDragDone,
|
||||
// onDragEntered: controller.onDragEntered,
|
||||
// onDragExited: controller.onDragExited,
|
||||
// child:
|
||||
// Pangea#
|
||||
Stack(
|
||||
// body: DropTarget(
|
||||
// onDragDone: controller.onDragDone,
|
||||
// onDragEntered: controller.onDragEntered,
|
||||
// onDragExited: controller.onDragExited,
|
||||
// child: Stack(
|
||||
body: Stack(
|
||||
// Pangea#
|
||||
children: <Widget>[
|
||||
if (accountConfig.wallpaperUrl != null)
|
||||
Opacity(
|
||||
|
|
@ -405,7 +406,6 @@ class ChatView extends StatelessWidget {
|
|||
// : Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// const ConnectionStatusHeader(),
|
||||
// ReactionsPicker(controller),
|
||||
// ReplyDisplay(controller),
|
||||
// ChatInputRow(controller),
|
||||
|
|
@ -424,9 +424,7 @@ class ChatView extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
// #Pangea
|
||||
ChatViewBackground(
|
||||
choreographer: controller.choreographer,
|
||||
),
|
||||
ChatViewBackground(controller.choreographer),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
|
|
@ -484,7 +482,6 @@ class ChatView extends StatelessWidget {
|
|||
|
||||
child: Column(
|
||||
children: [
|
||||
const ConnectionStatusHeader(),
|
||||
ITBar(
|
||||
choreographer: controller.choreographer,
|
||||
),
|
||||
|
|
@ -518,7 +515,7 @@ class ChatView extends StatelessWidget {
|
|||
// #Pangea
|
||||
// if (controller.dragging)
|
||||
// Container(
|
||||
// color: theme.scaffoldBackgroundColor.withOpacity(0.9),
|
||||
// color: theme.scaffoldBackgroundColor.withAlpha(230),
|
||||
// alignment: Alignment.center,
|
||||
// child: const Icon(
|
||||
// Icons.upload_outlined,
|
||||
|
|
|
|||
|
|
@ -11,17 +11,18 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:opus_caf_converter_dart/opus_caf_converter_dart.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/utils/error_reporter.dart';
|
||||
import 'package:fluffychat/utils/file_description.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
import '../../../utils/matrix_sdk_extensions/event_extension.dart';
|
||||
|
||||
class AudioPlayerWidget extends StatefulWidget {
|
||||
final Color color;
|
||||
final Color linkColor;
|
||||
final double fontSize;
|
||||
// #Pangea
|
||||
// final Event event;
|
||||
|
|
@ -30,8 +31,9 @@ class AudioPlayerWidget extends StatefulWidget {
|
|||
final bool autoplay;
|
||||
final Function(bool)? setIsPlayingAudio;
|
||||
final double padding;
|
||||
final ChatController chatController;
|
||||
final bool isOverlay;
|
||||
final MessageOverlayController? overlayController;
|
||||
final ChatController? chatController;
|
||||
// Pangea#
|
||||
|
||||
static String? currentId;
|
||||
|
|
@ -45,7 +47,8 @@ class AudioPlayerWidget extends StatefulWidget {
|
|||
|
||||
const AudioPlayerWidget(
|
||||
this.event, {
|
||||
this.color = Colors.black,
|
||||
required this.color,
|
||||
required this.linkColor,
|
||||
required this.fontSize,
|
||||
// #Pangea
|
||||
this.matrixFile,
|
||||
|
|
@ -54,8 +57,9 @@ class AudioPlayerWidget extends StatefulWidget {
|
|||
this.sectionEndMS,
|
||||
this.setIsPlayingAudio,
|
||||
this.padding = 12.0,
|
||||
required this.chatController,
|
||||
required this.isOverlay,
|
||||
this.overlayController,
|
||||
this.chatController,
|
||||
// Pangea#
|
||||
super.key,
|
||||
});
|
||||
|
|
@ -356,7 +360,7 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
|||
: _downloadAction();
|
||||
}
|
||||
|
||||
_onShowToolbar = widget.chatController?.showToolbarStream.stream
|
||||
_onShowToolbar = widget.chatController.showToolbarStream.stream
|
||||
.where((eventID) => eventID == widget.event?.eventId)
|
||||
.listen((eventID) {
|
||||
audioPlayer?.pause();
|
||||
|
|
@ -373,17 +377,14 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
|||
final statusText = this.statusText ??= _durationString ?? '00:00';
|
||||
final audioPlayer = this.audioPlayer;
|
||||
|
||||
final body = widget.event?.content.tryGet<String>('body') ??
|
||||
widget.event?.content.tryGet<String>('filename');
|
||||
final displayBody = body != null &&
|
||||
body.isNotEmpty &&
|
||||
widget.event?.content['org.matrix.msc1767.audio'] == null;
|
||||
// #Pangea
|
||||
// final fileDescription = widget.event.fileDescription;
|
||||
final fileDescription = widget.event?.fileDescription;
|
||||
// Pangea#
|
||||
|
||||
final wavePosition =
|
||||
(currentPosition / maxPosition) * AudioPlayerWidget.wavesCount;
|
||||
|
||||
final fontSize = 12 * AppConfig.fontSizeFactor;
|
||||
|
||||
return Padding(
|
||||
// #Pangea
|
||||
// padding: const EdgeInsets.all(12.0),
|
||||
|
|
@ -508,46 +509,66 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
|
|||
),
|
||||
),
|
||||
// #Pangea
|
||||
// ),
|
||||
// const SizedBox(width: 8),
|
||||
// Badge(
|
||||
// isLabelVisible: audioPlayer != null,
|
||||
// label: audioPlayer == null
|
||||
// ? null
|
||||
// : Text(
|
||||
// '${audioPlayer.speed.toString()}x',
|
||||
// ),
|
||||
// backgroundColor: theme.colorScheme.secondary,
|
||||
// textColor: theme.colorScheme.onSecondary,
|
||||
// child: InkWell(
|
||||
// splashColor: widget.color.withAlpha(128),
|
||||
// borderRadius: BorderRadius.circular(64),
|
||||
// onTap: audioPlayer == null ? null : _toggleSpeed,
|
||||
// AnimatedCrossFade(
|
||||
// firstChild: Padding(
|
||||
// padding: const EdgeInsets.only(right: 8.0),
|
||||
// child: Icon(
|
||||
// Icons.mic_none_outlined,
|
||||
// color: widget.color,
|
||||
// ),
|
||||
// ),
|
||||
// secondChild: Material(
|
||||
// color: widget.color.withAlpha(64),
|
||||
// borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
// child: InkWell(
|
||||
// borderRadius:
|
||||
// BorderRadius.circular(AppConfig.borderRadius),
|
||||
// onTap: _toggleSpeed,
|
||||
// child: SizedBox(
|
||||
// width: 32,
|
||||
// height: 20,
|
||||
// child: Center(
|
||||
// child: Text(
|
||||
// '${audioPlayer?.speed.toString()}x',
|
||||
// style: TextStyle(
|
||||
// color: widget.color,
|
||||
// fontSize: 9,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// alignment: Alignment.center,
|
||||
// crossFadeState: audioPlayer == null
|
||||
// ? CrossFadeState.showFirst
|
||||
// : CrossFadeState.showSecond,
|
||||
// duration: FluffyThemes.animationDuration,
|
||||
// ),
|
||||
// const SizedBox(width: 8),
|
||||
// Pangea#
|
||||
],
|
||||
),
|
||||
),
|
||||
if (displayBody) ...[
|
||||
if (fileDescription != null
|
||||
// #Pangea
|
||||
&&
|
||||
widget.event != null
|
||||
// Pangea#
|
||||
) ...[
|
||||
const SizedBox(height: 8),
|
||||
Linkify(
|
||||
text: body,
|
||||
text: fileDescription,
|
||||
style: TextStyle(
|
||||
color: widget.color,
|
||||
fontSize: fontSize,
|
||||
fontSize: widget.fontSize,
|
||||
),
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
linkStyle: TextStyle(
|
||||
color: widget.color.withAlpha(150),
|
||||
fontSize: fontSize,
|
||||
color: widget.linkColor,
|
||||
fontSize: widget.fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: widget.color.withAlpha(150),
|
||||
decorationColor: widget.linkColor,
|
||||
),
|
||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,9 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/image_viewer/image_viewer.dart';
|
||||
import 'package:fluffychat/utils/file_description.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
import '../../../widgets/blur_hash.dart';
|
||||
|
||||
|
|
@ -13,12 +16,15 @@ class ImageBubble extends StatelessWidget {
|
|||
final BoxFit fit;
|
||||
final bool maxSize;
|
||||
final Color? backgroundColor;
|
||||
final Color? textColor;
|
||||
final Color? linkColor;
|
||||
final bool thumbnailOnly;
|
||||
final bool animated;
|
||||
final double width;
|
||||
final double height;
|
||||
final void Function()? onTap;
|
||||
final BorderRadius? borderRadius;
|
||||
final Timeline? timeline;
|
||||
|
||||
const ImageBubble(
|
||||
this.event, {
|
||||
|
|
@ -32,6 +38,9 @@ class ImageBubble extends StatelessWidget {
|
|||
this.animated = false,
|
||||
this.onTap,
|
||||
this.borderRadius,
|
||||
this.timeline,
|
||||
this.textColor,
|
||||
this.linkColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -62,6 +71,7 @@ class ImageBubble extends StatelessWidget {
|
|||
context: context,
|
||||
builder: (_) => ImageViewer(
|
||||
event,
|
||||
timeline: timeline,
|
||||
outerContext: context,
|
||||
),
|
||||
);
|
||||
|
|
@ -73,35 +83,64 @@ class ImageBubble extends StatelessWidget {
|
|||
|
||||
final borderRadius =
|
||||
this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius);
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
side: BorderSide(
|
||||
color: event.messageType == MessageTypes.Sticker
|
||||
? Colors.transparent
|
||||
: theme.dividerColor,
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _onTap(context),
|
||||
borderRadius: borderRadius,
|
||||
child: Hero(
|
||||
tag: event.eventId,
|
||||
child: MxcImage(
|
||||
event: event,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
animated: animated,
|
||||
isThumbnail: thumbnailOnly,
|
||||
placeholder: event.messageType == MessageTypes.Sticker
|
||||
? null
|
||||
: _buildPlaceholder,
|
||||
|
||||
final fileDescription = event.fileDescription;
|
||||
final textColor = this.textColor;
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
side: BorderSide(
|
||||
color: event.messageType == MessageTypes.Sticker
|
||||
? Colors.transparent
|
||||
: theme.dividerColor,
|
||||
),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _onTap(context),
|
||||
borderRadius: borderRadius,
|
||||
child: Hero(
|
||||
tag: event.eventId,
|
||||
child: MxcImage(
|
||||
event: event,
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
animated: animated,
|
||||
isThumbnail: thumbnailOnly,
|
||||
placeholder: event.messageType == MessageTypes.Sticker
|
||||
? null
|
||||
: _buildPlaceholder,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (fileDescription != null && textColor != null)
|
||||
SizedBox(
|
||||
width: width,
|
||||
child: Linkify(
|
||||
text: fileDescription,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
),
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
linkStyle: TextStyle(
|
||||
color: linkColor,
|
||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: linkColor,
|
||||
),
|
||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dar
|
|||
import 'package:fluffychat/pangea/toolbar/widgets/message_buttons.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart';
|
||||
import 'package:fluffychat/utils/date_time_extension.dart';
|
||||
import 'package:fluffychat/utils/file_description.dart';
|
||||
import 'package:fluffychat/utils/string_color.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -157,10 +158,19 @@ class Message extends StatelessWidget {
|
|||
final textColor = ownMessage
|
||||
?
|
||||
// #Pangea
|
||||
// theme.colorScheme.onPrimary
|
||||
// theme.brightness == Brightness.light
|
||||
// ? theme.colorScheme.onPrimary
|
||||
// : theme.colorScheme.onPrimaryContainer
|
||||
ThemeData.dark().colorScheme.onPrimary
|
||||
// Pangea#
|
||||
: theme.colorScheme.onSurface;
|
||||
|
||||
final linkColor = ownMessage
|
||||
? theme.brightness == Brightness.light
|
||||
? theme.colorScheme.primaryFixed
|
||||
: theme.colorScheme.onTertiaryContainer
|
||||
: theme.colorScheme.primary;
|
||||
|
||||
final rowMainAxisAlignment =
|
||||
ownMessage ? MainAxisAlignment.end : MainAxisAlignment.start;
|
||||
|
||||
|
|
@ -180,6 +190,7 @@ class Message extends StatelessWidget {
|
|||
MessageTypes.Image,
|
||||
MessageTypes.Sticker,
|
||||
}.contains(event.messageType) &&
|
||||
event.fileDescription == null &&
|
||||
!event.redacted) ||
|
||||
(event.messageType == MessageTypes.Text &&
|
||||
event.relationshipType == null &&
|
||||
|
|
@ -195,7 +206,9 @@ class Message extends StatelessWidget {
|
|||
color = displayEvent.status.isError
|
||||
? Colors.redAccent
|
||||
// #Pangea
|
||||
// : ThemeData.dark().colorScheme.primary;
|
||||
// : theme.brightness == Brightness.light
|
||||
// ? theme.colorScheme.primary
|
||||
// : theme.colorScheme.primaryContainer;
|
||||
: Color.alphaBlend(
|
||||
Colors.white.withAlpha(180),
|
||||
ThemeData.dark().colorScheme.primary,
|
||||
|
|
@ -212,10 +225,9 @@ class Message extends StatelessWidget {
|
|||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
animateIn = false;
|
||||
// #Pangea
|
||||
if (context.mounted) {
|
||||
// Pangea#
|
||||
setState(resetAnimateIn);
|
||||
}
|
||||
// setState(resetAnimateIn);
|
||||
if (context.mounted) setState(resetAnimateIn);
|
||||
// Pangea#
|
||||
});
|
||||
}
|
||||
return AnimatedSize(
|
||||
|
|
@ -248,13 +260,10 @@ class Message extends StatelessWidget {
|
|||
child: Material(
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius / 2),
|
||||
color: selected
|
||||
color: selected || highlightMarker
|
||||
? theme.colorScheme.secondaryContainer
|
||||
.withAlpha(100)
|
||||
: highlightMarker
|
||||
? theme.colorScheme.tertiaryContainer
|
||||
.withAlpha(100)
|
||||
: Colors.transparent,
|
||||
.withAlpha(128)
|
||||
: Colors.transparent,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -520,8 +529,10 @@ class Message extends StatelessWidget {
|
|||
MessageContent(
|
||||
displayEvent,
|
||||
textColor: textColor,
|
||||
linkColor: linkColor,
|
||||
onInfoTab: onInfoTab,
|
||||
borderRadius: borderRadius,
|
||||
timeline: timeline,
|
||||
// #Pangea
|
||||
pangeaMessageEvent:
|
||||
pangeaMessageEvent,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import 'message_download_content.dart';
|
|||
class MessageContent extends StatelessWidget {
|
||||
final Event event;
|
||||
final Color textColor;
|
||||
final Color linkColor;
|
||||
final void Function(Event)? onInfoTab;
|
||||
final BorderRadius borderRadius;
|
||||
// #Pangea
|
||||
|
|
@ -41,11 +42,13 @@ class MessageContent extends StatelessWidget {
|
|||
final Event? nextEvent;
|
||||
final Event? prevEvent;
|
||||
// Pangea#
|
||||
final Timeline timeline;
|
||||
|
||||
const MessageContent(
|
||||
this.event, {
|
||||
this.onInfoTab,
|
||||
super.key,
|
||||
required this.timeline,
|
||||
required this.textColor,
|
||||
// #Pangea
|
||||
this.pangeaMessageEvent,
|
||||
|
|
@ -55,6 +58,7 @@ class MessageContent extends StatelessWidget {
|
|||
this.nextEvent,
|
||||
this.prevEvent,
|
||||
// Pangea#
|
||||
required this.linkColor,
|
||||
required this.borderRadius,
|
||||
});
|
||||
|
||||
|
|
@ -194,6 +198,8 @@ class MessageContent extends StatelessWidget {
|
|||
height: height,
|
||||
fit: fit,
|
||||
borderRadius: borderRadius,
|
||||
timeline: timeline,
|
||||
textColor: textColor,
|
||||
);
|
||||
case CuteEventContent.eventType:
|
||||
return CuteContent(event);
|
||||
|
|
@ -208,17 +214,27 @@ class MessageContent extends StatelessWidget {
|
|||
return AudioPlayerWidget(
|
||||
event,
|
||||
color: textColor,
|
||||
linkColor: linkColor,
|
||||
fontSize: fontSize,
|
||||
// #Pangea
|
||||
isOverlay: overlayController != null,
|
||||
chatController: controller,
|
||||
// Pangea#
|
||||
);
|
||||
}
|
||||
return MessageDownloadContent(event, textColor);
|
||||
return MessageDownloadContent(
|
||||
event,
|
||||
textColor: textColor,
|
||||
linkColor: linkColor,
|
||||
);
|
||||
case MessageTypes.Video:
|
||||
return EventVideoPlayer(event);
|
||||
return EventVideoPlayer(event, textColor: textColor);
|
||||
case MessageTypes.File:
|
||||
return MessageDownloadContent(event, textColor);
|
||||
return MessageDownloadContent(
|
||||
event,
|
||||
textColor: textColor,
|
||||
linkColor: linkColor,
|
||||
);
|
||||
|
||||
case MessageTypes.Text:
|
||||
case MessageTypes.Notice:
|
||||
|
|
@ -244,6 +260,15 @@ class MessageContent extends StatelessWidget {
|
|||
isSelected: overlayController != null ? isSelected : null,
|
||||
onClick: onClick,
|
||||
// Pangea#
|
||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
linkStyle: TextStyle(
|
||||
color: linkColor,
|
||||
fontSize:
|
||||
AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: linkColor,
|
||||
),
|
||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||
);
|
||||
}
|
||||
// else we fall through to the normal message rendering
|
||||
|
|
@ -367,7 +392,6 @@ class MessageContent extends StatelessWidget {
|
|||
prevEvent: prevEvent,
|
||||
child:
|
||||
// Pangea#
|
||||
|
||||
Linkify(
|
||||
text: event.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
|
|
@ -377,17 +401,16 @@ class MessageContent extends StatelessWidget {
|
|||
// style: TextStyle(
|
||||
// color: textColor,
|
||||
// fontSize: bigEmotes ? fontSize * 5 : fontSize,
|
||||
// decoration:
|
||||
// event.redacted ? TextDecoration.lineThrough : null,
|
||||
// decoration: event.redacted ? TextDecoration.lineThrough : null,
|
||||
// ),
|
||||
style: messageTextStyle,
|
||||
// Pangea#
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
linkStyle: TextStyle(
|
||||
color: textColor.withAlpha(150),
|
||||
color: linkColor,
|
||||
fontSize: fontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: textColor.withAlpha(150),
|
||||
decorationColor: linkColor,
|
||||
),
|
||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,24 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/utils/file_description.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
|
||||
class MessageDownloadContent extends StatelessWidget {
|
||||
final Event event;
|
||||
final Color textColor;
|
||||
final Color linkColor;
|
||||
|
||||
const MessageDownloadContent(this.event, this.textColor, {super.key});
|
||||
const MessageDownloadContent(
|
||||
this.event, {
|
||||
required this.textColor,
|
||||
required this.linkColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -21,59 +31,83 @@ class MessageDownloadContent extends StatelessWidget {
|
|||
?.toUpperCase() ??
|
||||
'UNKNOWN');
|
||||
final sizeString = event.sizeString;
|
||||
return InkWell(
|
||||
onTap: () => event.saveFile(context),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.file_download_outlined,
|
||||
color: textColor,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
child: Text(
|
||||
filename,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
final fileDescription = event.fileDescription;
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => event.saveFile(context),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.file_download_outlined,
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
filetype,
|
||||
style: TextStyle(
|
||||
color: textColor.withAlpha(150),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
if (sizeString != null)
|
||||
Text(
|
||||
sizeString,
|
||||
style: TextStyle(
|
||||
color: textColor.withAlpha(150),
|
||||
const SizedBox(width: 16),
|
||||
Flexible(
|
||||
child: Text(
|
||||
filename,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
filetype,
|
||||
style: TextStyle(
|
||||
color: linkColor,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
if (sizeString != null)
|
||||
Text(
|
||||
sizeString,
|
||||
style: TextStyle(
|
||||
color: linkColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (fileDescription != null)
|
||||
Linkify(
|
||||
text: fileDescription,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
),
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
linkStyle: TextStyle(
|
||||
color: linkColor,
|
||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: linkColor,
|
||||
),
|
||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,9 +33,11 @@ class ReplyContent extends StatelessWidget {
|
|||
final displayEvent =
|
||||
timeline != null ? replyEvent.getDisplayEvent(timeline) : replyEvent;
|
||||
final fontSize = AppConfig.messageFontSize * AppConfig.fontSizeFactor;
|
||||
final color = ownMessage
|
||||
? theme.colorScheme.tertiaryContainer
|
||||
: theme.colorScheme.tertiary;
|
||||
final color = theme.brightness == Brightness.dark
|
||||
? theme.colorScheme.onTertiaryContainer
|
||||
: ownMessage
|
||||
? theme.colorScheme.tertiaryContainer
|
||||
: theme.colorScheme.tertiary;
|
||||
|
||||
return Material(
|
||||
color: backgroundColor ??
|
||||
|
|
@ -80,9 +82,11 @@ class ReplyContent extends StatelessWidget {
|
|||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
color: ownMessage
|
||||
? theme.colorScheme.onTertiary
|
||||
: theme.colorScheme.onSurface,
|
||||
color: theme.brightness == Brightness.dark
|
||||
? theme.colorScheme.onSurface
|
||||
: ownMessage
|
||||
? theme.colorScheme.onTertiary
|
||||
: theme.colorScheme.onSurface,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_linkify/flutter_linkify.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:universal_html/html.dart' as html;
|
||||
|
|
@ -12,15 +13,24 @@ import 'package:video_player/video_player.dart';
|
|||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat/events/image_bubble.dart';
|
||||
import 'package:fluffychat/utils/file_description.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
import 'package:fluffychat/widgets/blur_hash.dart';
|
||||
import '../../../utils/error_reporter.dart';
|
||||
|
||||
class EventVideoPlayer extends StatefulWidget {
|
||||
final Event event;
|
||||
const EventVideoPlayer(this.event, {super.key});
|
||||
final Color? textColor;
|
||||
final Color? linkColor;
|
||||
const EventVideoPlayer(
|
||||
this.event, {
|
||||
this.textColor,
|
||||
this.linkColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
EventVideoPlayerState createState() => EventVideoPlayerState();
|
||||
|
|
@ -102,51 +112,86 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
|
|||
final blurHash = (widget.event.infoMap as Map<String, dynamic>)
|
||||
.tryGet<String>('xyz.amorgan.blurhash') ??
|
||||
fallbackBlurHash;
|
||||
final fileDescription = widget.event.fileDescription;
|
||||
final textColor = widget.textColor;
|
||||
final linkColor = widget.linkColor;
|
||||
|
||||
const width = 300.0;
|
||||
|
||||
final chewieManager = _chewieManager;
|
||||
return Material(
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
child: SizedBox(
|
||||
height: 300,
|
||||
child: chewieManager != null
|
||||
? Center(child: Chewie(controller: chewieManager))
|
||||
: Stack(
|
||||
children: [
|
||||
if (hasThumbnail)
|
||||
Center(
|
||||
child: ImageBubble(
|
||||
widget.event,
|
||||
tapToView: false,
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8,
|
||||
children: [
|
||||
Material(
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||
child: SizedBox(
|
||||
height: width,
|
||||
child: chewieManager != null
|
||||
? Center(child: Chewie(controller: chewieManager))
|
||||
: Stack(
|
||||
children: [
|
||||
if (hasThumbnail)
|
||||
Center(
|
||||
child: ImageBubble(
|
||||
widget.event,
|
||||
tapToView: false,
|
||||
textColor: widget.textColor,
|
||||
),
|
||||
)
|
||||
else
|
||||
BlurHash(
|
||||
blurhash: blurHash,
|
||||
width: width,
|
||||
height: width,
|
||||
),
|
||||
Center(
|
||||
child: IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
),
|
||||
icon: _isDownloading
|
||||
? const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.play_circle_outlined),
|
||||
tooltip: _isDownloading
|
||||
? L10n.of(context).loadingPleaseWait
|
||||
: L10n.of(context).videoWithSize(
|
||||
widget.event.sizeString ?? '?MB',
|
||||
),
|
||||
onPressed: _isDownloading ? null : _downloadAction,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
BlurHash(blurhash: blurHash, width: 300, height: 300),
|
||||
Center(
|
||||
child: IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
),
|
||||
icon: _isDownloading
|
||||
? const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.play_circle_outlined),
|
||||
tooltip: _isDownloading
|
||||
? L10n.of(context).loadingPleaseWait
|
||||
: L10n.of(context).videoWithSize(
|
||||
widget.event.sizeString ?? '?MB',
|
||||
),
|
||||
onPressed: _isDownloading ? null : _downloadAction,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (fileDescription != null && textColor != null && linkColor != null)
|
||||
SizedBox(
|
||||
width: width,
|
||||
child: Linkify(
|
||||
text: fileDescription,
|
||||
style: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
),
|
||||
),
|
||||
options: const LinkifyOptions(humanize: false),
|
||||
linkStyle: TextStyle(
|
||||
color: linkColor,
|
||||
fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize,
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: linkColor,
|
||||
),
|
||||
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/chat/chat.dart';
|
||||
import 'package:fluffychat/pages/chat/chat_app_bar_list_tile.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
||||
class PinnedEvents extends StatelessWidget {
|
||||
|
|
@ -30,13 +30,15 @@ class PinnedEvents extends StatelessWidget {
|
|||
|
||||
final eventId = events.length == 1
|
||||
? events.single?.eventId
|
||||
: await showConfirmationDialog<String>(
|
||||
: await showModalActionPopup<String>(
|
||||
context: context,
|
||||
title: L10n.of(context).pinMessage,
|
||||
title: L10n.of(context).pin,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
actions: events
|
||||
.map(
|
||||
(event) => AlertDialogAction(
|
||||
key: event?.eventId ?? '',
|
||||
(event) => AdaptiveModalAction(
|
||||
value: event?.eventId ?? '',
|
||||
icon: const Icon(Icons.push_pin_outlined),
|
||||
label: event?.calcLocalizedBodyFallback(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
withSenderNamePrefix: true,
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ class RecordingDialogState extends State<RecordingDialog> {
|
|||
CupertinoDialogAction(
|
||||
onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
|
||||
child: Text(
|
||||
L10n.of(context).cancel.toUpperCase(),
|
||||
L10n.of(context).cancel,
|
||||
style: TextStyle(
|
||||
color: theme.textTheme.bodyMedium?.color?.withAlpha(150),
|
||||
),
|
||||
|
|
@ -215,7 +215,7 @@ class RecordingDialogState extends State<RecordingDialog> {
|
|||
if (error != true)
|
||||
CupertinoDialogAction(
|
||||
onPressed: _stopAndSend,
|
||||
child: Text(L10n.of(context).send.toUpperCase()),
|
||||
child: Text(L10n.of(context).send),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
@ -226,23 +226,16 @@ class RecordingDialogState extends State<RecordingDialog> {
|
|||
TextButton(
|
||||
onPressed: () => Navigator.of(context, rootNavigator: false).pop(),
|
||||
child: Text(
|
||||
L10n.of(context).cancel.toUpperCase(),
|
||||
L10n.of(context).cancel,
|
||||
style: TextStyle(
|
||||
color: theme.textTheme.bodyMedium?.color?.withAlpha(150),
|
||||
color: theme.colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (error != true)
|
||||
TextButton(
|
||||
onPressed: _stopAndSend,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(L10n.of(context).send.toUpperCase()),
|
||||
const SizedBox(width: 4),
|
||||
const Icon(Icons.send_outlined, size: 15),
|
||||
],
|
||||
),
|
||||
child: Text(L10n.of(context).send),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ import 'package:mime/mime.dart';
|
|||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
|
||||
import 'package:fluffychat/utils/other_party_can_receive.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/utils/size_string.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialog_action.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||
import '../../utils/resize_video.dart';
|
||||
|
||||
class SendFileDialog extends StatefulWidget {
|
||||
|
|
@ -37,17 +38,20 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
bool compress = true;
|
||||
|
||||
/// Images smaller than 20kb don't need compression.
|
||||
static const int minSizeToCompress = 20 * 1024;
|
||||
static const int minSizeToCompress = 20 * 1000;
|
||||
|
||||
Future<void> _send() async {
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(widget.outerContext);
|
||||
final l10n = L10n.of(context);
|
||||
|
||||
try {
|
||||
if (!widget.room.otherPartyCanReceiveMessages) {
|
||||
throw OtherPartyCanNotReceiveMessages();
|
||||
}
|
||||
scaffoldMessenger.showLoadingSnackBar(l10n.prepareSendingAttachment);
|
||||
Navigator.of(context, rootNavigator: false).pop();
|
||||
final clientConfig = await widget.room.client.getConfig();
|
||||
final maxUploadSize = clientConfig.mUploadSize ?? 100 * 1024 * 1024;
|
||||
final maxUploadSize = clientConfig.mUploadSize ?? 100 * 1000 * 1000;
|
||||
|
||||
for (final xfile in widget.files) {
|
||||
final MatrixFile file;
|
||||
|
|
@ -66,6 +70,9 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
scaffoldMessenger.showLoadingSnackBar(l10n.generatingVideoThumbnail);
|
||||
thumbnail = await xfile.getVideoThumbnail();
|
||||
} else {
|
||||
if (length > maxUploadSize) {
|
||||
throw FileTooBigMatrixException(length, maxUploadSize);
|
||||
}
|
||||
// Else we just create a MatrixFile
|
||||
file = MatrixFile(
|
||||
bytes: await xfile.readAsBytes(),
|
||||
|
|
@ -124,9 +131,15 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
scaffoldMessenger.clearSnackBars();
|
||||
} catch (e) {
|
||||
scaffoldMessenger.clearSnackBars();
|
||||
final theme = Theme.of(context);
|
||||
scaffoldMessenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(e.toLocalizedString(widget.outerContext)),
|
||||
backgroundColor: theme.colorScheme.errorContainer,
|
||||
closeIconColor: theme.colorScheme.onErrorContainer,
|
||||
content: Text(
|
||||
e.toLocalizedString(widget.outerContext),
|
||||
style: TextStyle(color: theme.colorScheme.onErrorContainer),
|
||||
),
|
||||
duration: const Duration(seconds: 30),
|
||||
showCloseIcon: true,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import 'package:geolocator/geolocator.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/chat/events/map_bubble.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialog_action.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
||||
class SendLocationDialog extends StatefulWidget {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import 'package:flutter/material.dart' hide Visibility;
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_page.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -149,14 +151,15 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
|||
);
|
||||
final capabilities = capabilitiesResult.result;
|
||||
if (capabilities == null) return;
|
||||
final newVersion = await showConfirmationDialog<String>(
|
||||
final newVersion = await showModalActionPopup<String>(
|
||||
context: context,
|
||||
title: L10n.of(context).replaceRoomWithNewerVersion,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
actions: capabilities.mRoomVersions!.available.entries
|
||||
.where((r) => r.key != roomVersion)
|
||||
.map(
|
||||
(version) => AlertDialogAction(
|
||||
key: version.key,
|
||||
(version) => AdaptiveModalAction(
|
||||
value: version.key,
|
||||
label:
|
||||
'${version.key} (${version.value.toString().split('.').last})',
|
||||
),
|
||||
|
|
@ -172,7 +175,7 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
|||
cancelLabel: L10n.of(context).cancel,
|
||||
title: L10n.of(context).areYouSure,
|
||||
message: L10n.of(context).roomUpgradeDescription,
|
||||
isDestructiveAction: true,
|
||||
isDestructive: true,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -191,15 +194,11 @@ class ChatAccessSettingsController extends State<ChatAccessSettings> {
|
|||
final input = await showTextInputDialog(
|
||||
context: context,
|
||||
title: L10n.of(context).editRoomAliases,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
prefixText: '#',
|
||||
suffixText: domain,
|
||||
hintText: L10n.of(context).alias,
|
||||
),
|
||||
],
|
||||
prefixText: '#',
|
||||
suffixText: domain,
|
||||
hintText: L10n.of(context).alias,
|
||||
);
|
||||
final aliasLocalpart = input?.singleOrNull?.trim();
|
||||
final aliasLocalpart = input?.trim();
|
||||
if (aliasLocalpart == null || aliasLocalpart.isEmpty) return;
|
||||
final alias = '#$aliasLocalpart:$domain';
|
||||
|
||||
|
|
|
|||
|
|
@ -238,8 +238,8 @@ class _AliasListTile extends StatelessWidget {
|
|||
'https://matrix.to/#/$alias',
|
||||
context,
|
||||
),
|
||||
child: Text(
|
||||
'https://matrix.to/#/$alias',
|
||||
child: SelectableText(
|
||||
alias,
|
||||
style: TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
decorationColor: theme.colorScheme.primary,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
|
@ -12,6 +11,8 @@ import 'package:fluffychat/pangea/chat_settings/pages/pangea_chat_details.dart';
|
|||
import 'package:fluffychat/pangea/spaces/utils/set_class_name.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
@ -54,20 +55,16 @@ class ChatDetailsController extends State<ChatDetails> {
|
|||
// title: L10n.of(context).changeTheNameOfTheGroup,
|
||||
// okLabel: L10n.of(context).ok,
|
||||
// cancelLabel: L10n.of(context).cancel,
|
||||
// textFields: [
|
||||
// DialogTextField(
|
||||
// initialText: room.getLocalizedDisplayname(
|
||||
// MatrixLocals(
|
||||
// L10n.of(context),
|
||||
// ),
|
||||
// ),
|
||||
// initialText: room.getLocalizedDisplayname(
|
||||
// MatrixLocals(
|
||||
// L10n.of(context),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// if (input == null) return;
|
||||
// final success = await showFutureLoadingDialog(
|
||||
// context: context,
|
||||
// future: () => room.setName(input.single),
|
||||
// future: () => room.setName(input),
|
||||
// );
|
||||
// if (success.error == null) {
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
|
@ -84,20 +81,16 @@ class ChatDetailsController extends State<ChatDetails> {
|
|||
title: L10n.of(context).setChatDescription,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
hintText: L10n.of(context).noChatDescriptionYet,
|
||||
initialText: room.topic,
|
||||
minLines: 4,
|
||||
maxLines: 8,
|
||||
),
|
||||
],
|
||||
hintText: L10n.of(context).noChatDescriptionYet,
|
||||
initialText: room.topic,
|
||||
minLines: 4,
|
||||
maxLines: 8,
|
||||
);
|
||||
if (input == null) return;
|
||||
// #Pangea
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => room.setDescription(input.single),
|
||||
future: () => room.setDescription(input),
|
||||
);
|
||||
// final success = await showFutureLoadingDialog(
|
||||
// context: context,
|
||||
|
|
@ -131,30 +124,31 @@ class ChatDetailsController extends State<ChatDetails> {
|
|||
final room = Matrix.of(context).client.getRoomById(roomId!);
|
||||
final actions = [
|
||||
if (PlatformInfos.isMobile)
|
||||
SheetAction(
|
||||
key: AvatarAction.camera,
|
||||
AdaptiveModalAction(
|
||||
value: AvatarAction.camera,
|
||||
label: L10n.of(context).openCamera,
|
||||
isDefaultAction: true,
|
||||
icon: Icons.camera_alt_outlined,
|
||||
icon: const Icon(Icons.camera_alt_outlined),
|
||||
),
|
||||
SheetAction(
|
||||
key: AvatarAction.file,
|
||||
AdaptiveModalAction(
|
||||
value: AvatarAction.file,
|
||||
label: L10n.of(context).openGallery,
|
||||
icon: Icons.photo_outlined,
|
||||
icon: const Icon(Icons.photo_outlined),
|
||||
),
|
||||
if (room?.avatar != null)
|
||||
SheetAction(
|
||||
key: AvatarAction.remove,
|
||||
AdaptiveModalAction(
|
||||
value: AvatarAction.remove,
|
||||
label: L10n.of(context).delete,
|
||||
isDestructiveAction: true,
|
||||
icon: Icons.delete_outlined,
|
||||
isDestructive: true,
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
),
|
||||
];
|
||||
final action = actions.length == 1
|
||||
? actions.single.key
|
||||
: await showModalActionSheet<AvatarAction>(
|
||||
? actions.single.value
|
||||
: await showModalActionPopup<AvatarAction>(
|
||||
context: context,
|
||||
title: L10n.of(context).editRoomAvatar,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
actions: actions,
|
||||
);
|
||||
if (action == null) return;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import 'package:flutter_linkify/flutter_linkify.dart';
|
|||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/pages/chat_details/chat_details.dart';
|
||||
import 'package:fluffychat/pages/chat_details/participant_list_item.dart';
|
||||
import 'package:fluffychat/utils/fluffy_share.dart';
|
||||
|
|
@ -15,6 +14,7 @@ import 'package:fluffychat/widgets/chat_settings_popup_menu.dart';
|
|||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../utils/url_launcher.dart';
|
||||
import '../../widgets/qr_code_viewer.dart';
|
||||
|
||||
class ChatDetailsView extends StatelessWidget {
|
||||
final ChatDetailsController controller;
|
||||
|
|
@ -57,15 +57,16 @@ class ChatDetailsView extends StatelessWidget {
|
|||
const Center(child: BackButton()),
|
||||
elevation: theme.appBarTheme.elevation,
|
||||
actions: <Widget>[
|
||||
if (room.canonicalAlias.isNotEmpty)
|
||||
if (room.canonicalAlias.isNotEmpty) ...[
|
||||
IconButton(
|
||||
tooltip: L10n.of(context).share,
|
||||
icon: Icon(Icons.adaptive.share_outlined),
|
||||
onPressed: () => FluffyShare.share(
|
||||
AppConfig.inviteLinkPrefix + room.canonicalAlias,
|
||||
icon: const Icon(Icons.qr_code_rounded),
|
||||
onPressed: () => showQrCodeViewer(
|
||||
context,
|
||||
room.canonicalAlias,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (controller.widget.embeddedCloseButton == null)
|
||||
ChatSettingsPopupMenu(room, false),
|
||||
],
|
||||
|
|
@ -150,6 +151,7 @@ class ChatDetailsView extends StatelessWidget {
|
|||
style: TextButton.styleFrom(
|
||||
foregroundColor:
|
||||
theme.colorScheme.onSurface,
|
||||
iconColor: theme.colorScheme.onSurface,
|
||||
),
|
||||
label: Text(
|
||||
room.isDirectChat
|
||||
|
|
@ -173,6 +175,7 @@ class ChatDetailsView extends StatelessWidget {
|
|||
style: TextButton.styleFrom(
|
||||
foregroundColor:
|
||||
theme.colorScheme.secondary,
|
||||
iconColor: theme.colorScheme.secondary,
|
||||
),
|
||||
label: Text(
|
||||
L10n.of(context).countParticipants(
|
||||
|
|
@ -207,6 +210,8 @@ class ChatDetailsView extends StatelessWidget {
|
|||
label: Text(L10n.of(context).setChatDescription),
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
style: TextButton.styleFrom(
|
||||
iconColor:
|
||||
theme.colorScheme.onSecondaryContainer,
|
||||
backgroundColor:
|
||||
theme.colorScheme.secondaryContainer,
|
||||
foregroundColor:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/encryption.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pages/chat_encryption_settings/chat_encryption_settings_view.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../key_verification/key_verification_dialog.dart';
|
||||
|
|
@ -76,7 +76,6 @@ class ChatEncryptionSettingsController extends State<ChatEncryptionSettings> {
|
|||
message: L10n.of(context).verifyOtherUserDescription,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
fullyCapitalizedForMaterial: false,
|
||||
);
|
||||
if (consent != OkCancelResult.ok) return;
|
||||
final req = await room.client.userDeviceKeys[room.directChatMatrixID]!
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ class ChatEncryptionSettingsView extends StatelessWidget {
|
|||
deviceKeys[i].ed25519Key?.beautified ??
|
||||
L10n.of(context).unknownEncryptionAlgorithm,
|
||||
style: TextStyle(
|
||||
fontFamily: 'RobotoMono',
|
||||
fontFamily: 'UbuntuMono',
|
||||
color: theme.colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_shortcuts/flutter_shortcuts.dart';
|
||||
|
|
@ -15,8 +14,6 @@ import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
|||
import 'package:uni_links/uni_links.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat/send_file_dialog.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list_view.dart';
|
||||
import 'package:fluffychat/pangea/chat_list/utils/app_version_util.dart';
|
||||
import 'package:fluffychat/pangea/chat_list/utils/chat_list_handle_space_tap.dart';
|
||||
|
|
@ -29,12 +26,17 @@ import 'package:fluffychat/pangea/subscription/widgets/subscription_snackbar.dar
|
|||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/utils/show_scaffold_dialog.dart';
|
||||
import 'package:fluffychat/utils/show_update_snackbar.dart';
|
||||
import 'package:fluffychat/utils/url_launcher.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/share_scaffold_dialog.dart';
|
||||
import '../../../utils/account_bundles.dart';
|
||||
import '../../config/setting_keys.dart';
|
||||
import '../../utils/url_launcher.dart';
|
||||
import '../../utils/voip/callkeep_manager.dart';
|
||||
import '../../widgets/fluffy_chat_app.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
|
|
@ -42,11 +44,6 @@ import '../../widgets/matrix.dart';
|
|||
import 'package:fluffychat/utils/tor_stub.dart'
|
||||
if (dart.library.html) 'package:tor_detector_web/tor_detector_web.dart';
|
||||
|
||||
enum SelectMode {
|
||||
normal,
|
||||
share,
|
||||
}
|
||||
|
||||
enum PopupMenuAction {
|
||||
settings,
|
||||
invite,
|
||||
|
|
@ -117,12 +114,6 @@ class ChatListController extends State<ChatList>
|
|||
setState(() {
|
||||
_activeSpaceId = spaceId;
|
||||
});
|
||||
|
||||
// #Pangea
|
||||
if (FluffyThemes.isColumnMode(context)) {
|
||||
context.go('/rooms/$spaceId/details');
|
||||
}
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
// #Pangea
|
||||
|
|
@ -139,50 +130,6 @@ class ChatListController extends State<ChatList>
|
|||
|
||||
void onChatTap(Room room) async {
|
||||
if (room.membership == Membership.invite) {
|
||||
final inviterId =
|
||||
room.getState(EventTypes.RoomMember, room.client.userID!)?.senderId;
|
||||
final inviteAction = await showModalActionSheet<InviteActions>(
|
||||
context: context,
|
||||
message: room.isDirectChat
|
||||
? L10n.of(context).invitePrivateChat
|
||||
// #Pangea
|
||||
// : L10n.of(context).inviteGroupChat,
|
||||
: L10n.of(context).inviteChat,
|
||||
// Pangea#
|
||||
title: room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
||||
actions: [
|
||||
SheetAction(
|
||||
key: InviteActions.accept,
|
||||
label: L10n.of(context).accept,
|
||||
icon: Icons.check_outlined,
|
||||
isDefaultAction: true,
|
||||
),
|
||||
SheetAction(
|
||||
key: InviteActions.decline,
|
||||
label: L10n.of(context).decline,
|
||||
icon: Icons.close_outlined,
|
||||
isDestructiveAction: true,
|
||||
),
|
||||
SheetAction(
|
||||
key: InviteActions.block,
|
||||
label: L10n.of(context).block,
|
||||
icon: Icons.block_outlined,
|
||||
isDestructiveAction: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
if (inviteAction == null) return;
|
||||
if (inviteAction == InviteActions.block) {
|
||||
context.go('/rooms/settings/security/ignorelist', extra: inviterId);
|
||||
return;
|
||||
}
|
||||
if (inviteAction == InviteActions.decline) {
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: room.leave,
|
||||
);
|
||||
return;
|
||||
}
|
||||
final joinResult = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
|
|
@ -216,42 +163,6 @@ class ChatListController extends State<ChatList>
|
|||
setActiveSpace(room.id);
|
||||
return;
|
||||
}
|
||||
// Share content into this room
|
||||
final shareContent = Matrix.of(context).shareContent;
|
||||
if (shareContent != null) {
|
||||
final shareFile = shareContent.tryGet<XFile>('file');
|
||||
if (shareContent.tryGet<String>('msgtype') == 'chat.fluffy.shared_file' &&
|
||||
shareFile != null) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (c) => SendFileDialog(
|
||||
files: [shareFile],
|
||||
room: room,
|
||||
outerContext: context,
|
||||
),
|
||||
);
|
||||
Matrix.of(context).shareContent = null;
|
||||
} else {
|
||||
final consent = await showOkCancelAlertDialog(
|
||||
context: context,
|
||||
title: L10n.of(context).forward,
|
||||
message: L10n.of(context).forwardMessageTo(
|
||||
room.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
||||
),
|
||||
okLabel: L10n.of(context).forward,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
);
|
||||
if (consent == OkCancelResult.cancel) {
|
||||
Matrix.of(context).shareContent = null;
|
||||
return;
|
||||
}
|
||||
if (consent == OkCancelResult.ok) {
|
||||
room.sendEvent(shareContent);
|
||||
Matrix.of(context).shareContent = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.go('/rooms/${room.id}');
|
||||
}
|
||||
|
|
@ -274,16 +185,18 @@ class ChatListController extends State<ChatList>
|
|||
case ActiveFilter.groups:
|
||||
return (room) =>
|
||||
!room.isSpace &&
|
||||
!room.isDirectChat // #Pangea
|
||||
!room.isDirectChat
|
||||
// #Pangea
|
||||
&&
|
||||
!room.isAnalyticsRoom;
|
||||
// Pangea#;
|
||||
// Pangea#
|
||||
case ActiveFilter.unread:
|
||||
return (room) =>
|
||||
room.isUnreadOrInvited // #Pangea
|
||||
room.isUnreadOrInvited
|
||||
// #Pangea
|
||||
&&
|
||||
!room.isAnalyticsRoom;
|
||||
// Pangea#;
|
||||
// Pangea#
|
||||
case ActiveFilter.spaces:
|
||||
return (room) => room.isSpace;
|
||||
}
|
||||
|
|
@ -312,23 +225,19 @@ class ChatListController extends State<ChatList>
|
|||
context: context,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
prefixText: 'https://',
|
||||
hintText: Matrix.of(context).client.homeserver?.host,
|
||||
initialText: searchServer,
|
||||
keyboardType: TextInputType.url,
|
||||
autocorrect: false,
|
||||
validator: (server) => server?.contains('.') == true
|
||||
? null
|
||||
: L10n.of(context).invalidServerName,
|
||||
),
|
||||
],
|
||||
prefixText: 'https://',
|
||||
hintText: Matrix.of(context).client.homeserver?.host,
|
||||
initialText: searchServer,
|
||||
keyboardType: TextInputType.url,
|
||||
autocorrect: false,
|
||||
validator: (server) => server.contains('.') == true
|
||||
? null
|
||||
: L10n.of(context).invalidServerName,
|
||||
);
|
||||
if (newServer == null) return;
|
||||
Matrix.of(context).store.setString(_serverStoreNamespace, newServer.single);
|
||||
Matrix.of(context).store.setString(_serverStoreNamespace, newServer);
|
||||
setState(() {
|
||||
searchServer = newServer.single;
|
||||
searchServer = newServer;
|
||||
});
|
||||
_coolDown?.cancel();
|
||||
_coolDown = Timer(const Duration(milliseconds: 500), _search);
|
||||
|
|
@ -463,53 +372,30 @@ class ChatListController extends State<ChatList>
|
|||
|
||||
String? get activeChat => widget.activeChat;
|
||||
|
||||
SelectMode get selectMode => Matrix.of(context).shareContent != null
|
||||
? SelectMode.share
|
||||
: SelectMode.normal;
|
||||
|
||||
void _processIncomingSharedMedia(List<SharedMediaFile> files) {
|
||||
if (files.isEmpty) return;
|
||||
|
||||
if (files.length > 1) {
|
||||
Logs().w(
|
||||
'Received ${files.length} incoming shared media but app can only handle the first one',
|
||||
);
|
||||
}
|
||||
|
||||
// We only handle the first file currently
|
||||
final sharedMedia = files.first;
|
||||
|
||||
// Handle URIs and Texts, which are also passed in path
|
||||
if (sharedMedia.type case SharedMediaType.text || SharedMediaType.url) {
|
||||
return _processIncomingSharedText(sharedMedia.path);
|
||||
}
|
||||
|
||||
final file = XFile(
|
||||
sharedMedia.path.replaceFirst('file://', ''),
|
||||
mimeType: sharedMedia.mimeType,
|
||||
showScaffoldDialog(
|
||||
context: context,
|
||||
builder: (context) => ShareScaffoldDialog(
|
||||
items: files.map(
|
||||
(file) {
|
||||
if ({
|
||||
SharedMediaType.text,
|
||||
SharedMediaType.url,
|
||||
}.contains(file.type)) {
|
||||
return TextShareItem(file.path);
|
||||
}
|
||||
return FileShareItem(
|
||||
XFile(
|
||||
file.path.replaceFirst('file://', ''),
|
||||
mimeType: file.mimeType,
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
);
|
||||
|
||||
Matrix.of(context).shareContent = {
|
||||
'msgtype': 'chat.fluffy.shared_file',
|
||||
'file': file,
|
||||
if (sharedMedia.message != null) 'body': sharedMedia.message,
|
||||
};
|
||||
context.go('/rooms');
|
||||
}
|
||||
|
||||
void _processIncomingSharedText(String? text) {
|
||||
if (text == null) return;
|
||||
if (text.toLowerCase().startsWith(AppConfig.deepLinkPrefix) ||
|
||||
text.toLowerCase().startsWith(AppConfig.inviteLinkPrefix) ||
|
||||
(text.toLowerCase().startsWith(AppConfig.schemePrefix) &&
|
||||
!RegExp(r'\s').hasMatch(text))) {
|
||||
return _processIncomingUris(text);
|
||||
}
|
||||
Matrix.of(context).shareContent = {
|
||||
'msgtype': 'm.text',
|
||||
'body': text,
|
||||
};
|
||||
context.go('/rooms');
|
||||
}
|
||||
|
||||
void _processIncomingUris(String? text) async {
|
||||
|
|
@ -573,8 +459,9 @@ class ChatListController extends State<ChatList>
|
|||
Matrix.of(context).store.getString(_serverStoreNamespace);
|
||||
Matrix.of(context).backgroundPush?.setupPush();
|
||||
UpdateNotifier.showUpdateSnackBar(context);
|
||||
|
||||
// #Pangea
|
||||
AppVersionUtil.showAppVersionDialog(context);
|
||||
// Pangea#
|
||||
}
|
||||
|
||||
// Workaround for system UI overlay style not applied on app start
|
||||
|
|
@ -699,10 +586,6 @@ class ChatListController extends State<ChatList>
|
|||
BuildContext posContext, [
|
||||
Room? space,
|
||||
]) async {
|
||||
if (room.membership == Membership.invite) {
|
||||
return onChatTap(room);
|
||||
}
|
||||
|
||||
final overlay =
|
||||
Overlay.of(posContext).context.findRenderObject() as RenderBox;
|
||||
|
||||
|
|
@ -781,114 +664,146 @@ class ChatListController extends State<ChatList>
|
|||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.mute,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
// #Pangea
|
||||
// room.pushRuleState == PushRuleState.notify
|
||||
// ? Icons.notifications_off_outlined
|
||||
// : Icons.notifications_off,
|
||||
room.pushRuleState == PushRuleState.notify
|
||||
? Icons.notifications_on_outlined
|
||||
: Icons.notifications_off_outlined,
|
||||
// Pangea#
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
// #Pangea
|
||||
// room.pushRuleState == PushRuleState.notify
|
||||
// ? L10n.of(context).muteChat
|
||||
// : L10n.of(context).unmuteChat,
|
||||
room.pushRuleState == PushRuleState.notify
|
||||
? L10n.of(context).notificationsOn
|
||||
: L10n.of(context).notificationsOff,
|
||||
// Pangea#
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.markUnread,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
room.markedUnread
|
||||
? Icons.mark_as_unread
|
||||
: Icons.mark_as_unread_outlined,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
room.markedUnread
|
||||
? L10n.of(context).markAsRead
|
||||
: L10n.of(context).markAsUnread,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.favorite,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(room.isFavourite ? Icons.push_pin : Icons.push_pin_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
room.isFavourite
|
||||
? L10n.of(context).unpin
|
||||
: L10n.of(context).pin,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (spacesWithPowerLevels.isNotEmpty
|
||||
// #Pangea
|
||||
&&
|
||||
!room.isSpace
|
||||
// Pangea#
|
||||
)
|
||||
if (room.membership == Membership.join) ...[
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.addToSpace,
|
||||
value: ChatContextAction.mute,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.group_work_outlined),
|
||||
Icon(
|
||||
// #Pangea
|
||||
// room.pushRuleState == PushRuleState.notify
|
||||
// ? Icons.notifications_off_outlined
|
||||
// : Icons.notifications_off,
|
||||
room.pushRuleState == PushRuleState.notify
|
||||
? Icons.notifications_on_outlined
|
||||
: Icons.notifications_off_outlined,
|
||||
// Pangea#
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).addToSpace),
|
||||
Text(
|
||||
// #Pangea
|
||||
// room.pushRuleState == PushRuleState.notify
|
||||
// ? L10n.of(context).muteChat
|
||||
// : L10n.of(context).unmuteChat,
|
||||
room.pushRuleState == PushRuleState.notify
|
||||
? L10n.of(context).notificationsOn
|
||||
: L10n.of(context).notificationsOff,
|
||||
// Pangea#
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
// if the room has a parent for which the user has a high enough power level
|
||||
// to set parent's space child events, show option to remove the room from the space
|
||||
if (room.spaceParents.isNotEmpty &&
|
||||
room.pangeaSpaceParents.any(
|
||||
(r) => r.canChangeStateEvent(EventTypes.SpaceChild),
|
||||
) &&
|
||||
activeSpaceId != null)
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.removeFromSpace,
|
||||
value: ChatContextAction.markUnread,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.delete_sweep_outlined),
|
||||
Icon(
|
||||
room.markedUnread
|
||||
? Icons.mark_as_unread
|
||||
: Icons.mark_as_unread_outlined,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).removeFromSpace),
|
||||
Text(
|
||||
room.markedUnread
|
||||
? L10n.of(context).markAsRead
|
||||
: L10n.of(context).markAsUnread,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.favorite,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
room.isFavourite ? Icons.push_pin : Icons.push_pin_outlined,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
room.isFavourite
|
||||
? L10n.of(context).unpin
|
||||
: L10n.of(context).pin,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (spacesWithPowerLevels.isNotEmpty
|
||||
// #Pangea
|
||||
&&
|
||||
!room.isSpace
|
||||
// Pangea#
|
||||
)
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.addToSpace,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.group_work_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).addToSpace),
|
||||
],
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
// if the room has a parent for which the user has a high enough power level
|
||||
// to set parent's space child events, show option to remove the room from the space
|
||||
if (room.spaceParents.isNotEmpty &&
|
||||
room.pangeaSpaceParents.any(
|
||||
(r) => r.canChangeStateEvent(EventTypes.SpaceChild),
|
||||
) &&
|
||||
activeSpaceId != null)
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.removeFromSpace,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.delete_sweep_outlined),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).removeFromSpace),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
if (room.membership == Membership.invite)
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.block,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.block_outlined,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
L10n.of(context).block,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Pangea#
|
||||
PopupMenuItem(
|
||||
value: ChatContextAction.leave,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.delete_outlined),
|
||||
Icon(
|
||||
Icons.delete_outlined,
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(L10n.of(context).leave),
|
||||
Text(
|
||||
room.membership == Membership.invite
|
||||
? L10n.of(context).delete
|
||||
: L10n.of(context).leave,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -932,15 +847,15 @@ class ChatListController extends State<ChatList>
|
|||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context).areYouSure,
|
||||
okLabel: L10n.of(context).leave,
|
||||
cancelLabel: L10n.of(context).no,
|
||||
// #Pangea
|
||||
// message: L10n.of(context).archiveRoomDescription,
|
||||
message: room.isSpace
|
||||
? L10n.of(context).leaveSpaceDescription
|
||||
: L10n.of(context).archiveRoomDescription,
|
||||
// Pangea#
|
||||
isDestructiveAction: true,
|
||||
okLabel: L10n.of(context).leave,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
isDestructive: true,
|
||||
);
|
||||
if (confirmed == OkCancelResult.cancel) return;
|
||||
if (!mounted) return;
|
||||
|
|
@ -955,13 +870,13 @@ class ChatListController extends State<ChatList>
|
|||
|
||||
return;
|
||||
case ChatContextAction.addToSpace:
|
||||
final space = await showConfirmationDialog(
|
||||
final space = await showModalActionPopup(
|
||||
context: context,
|
||||
title: L10n.of(context).space,
|
||||
actions: spacesWithPowerLevels
|
||||
.map(
|
||||
(space) => AlertDialogAction(
|
||||
key: space,
|
||||
(space) => AdaptiveModalAction(
|
||||
value: space,
|
||||
label: space
|
||||
.getLocalizedDisplayname(MatrixLocals(L10n.of(context))),
|
||||
),
|
||||
|
|
@ -1009,6 +924,10 @@ class ChatListController extends State<ChatList>
|
|||
);
|
||||
return;
|
||||
// Pangea#
|
||||
case ChatContextAction.block:
|
||||
final userId =
|
||||
room.getState(EventTypes.RoomMember, room.client.userID!)?.senderId;
|
||||
context.go('/rooms/settings/security/ignorelist', extra: userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1050,15 +969,11 @@ class ChatListController extends State<ChatList>
|
|||
message: L10n.of(context).leaveEmptyToClearStatus,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
hintText: L10n.of(context).statusExampleMessage,
|
||||
maxLines: 6,
|
||||
minLines: 1,
|
||||
maxLength: 255,
|
||||
initialText: currentPresence.statusMsg,
|
||||
),
|
||||
],
|
||||
hintText: L10n.of(context).statusExampleMessage,
|
||||
maxLines: 6,
|
||||
minLines: 1,
|
||||
maxLength: 255,
|
||||
initialText: currentPresence.statusMsg,
|
||||
);
|
||||
if (input == null) return;
|
||||
if (!mounted) return;
|
||||
|
|
@ -1067,7 +982,7 @@ class ChatListController extends State<ChatList>
|
|||
future: () => client.setPresence(
|
||||
client.userID!,
|
||||
PresenceType.online,
|
||||
statusMsg: input.single,
|
||||
statusMsg: input,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -1118,7 +1033,9 @@ class ChatListController extends State<ChatList>
|
|||
// controller = ScaffoldMessenger.of(context).showSnackBar(
|
||||
// SnackBar(
|
||||
// duration: const Duration(seconds: 15),
|
||||
// showCloseIcon: true,
|
||||
// backgroundColor: theme.colorScheme.errorContainer,
|
||||
// closeIconColor: theme.colorScheme.onErrorContainer,
|
||||
// content: Text(
|
||||
// L10n.of(context).oneOfYourDevicesIsNotVerified,
|
||||
// style: TextStyle(
|
||||
|
|
@ -1149,12 +1066,6 @@ class ChatListController extends State<ChatList>
|
|||
}
|
||||
// Pangea#
|
||||
|
||||
void cancelAction() {
|
||||
if (selectMode == SelectMode.share) {
|
||||
setState(() => Matrix.of(context).shareContent = null);
|
||||
}
|
||||
}
|
||||
|
||||
void setActiveFilter(ActiveFilter filter) {
|
||||
setState(() {
|
||||
activeFilter = filter;
|
||||
|
|
@ -1190,17 +1101,18 @@ class ChatListController extends State<ChatList>
|
|||
final client = Matrix.of(context)
|
||||
.widget
|
||||
.clients[Matrix.of(context).getClientIndexByMatrixId(userId!)];
|
||||
final action = await showConfirmationDialog<EditBundleAction>(
|
||||
final action = await showModalActionPopup<EditBundleAction>(
|
||||
context: context,
|
||||
title: L10n.of(context).editBundlesForAccount,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
actions: [
|
||||
AlertDialogAction(
|
||||
key: EditBundleAction.addToBundle,
|
||||
AdaptiveModalAction(
|
||||
value: EditBundleAction.addToBundle,
|
||||
label: L10n.of(context).addToBundle,
|
||||
),
|
||||
if (activeBundle != client.userID)
|
||||
AlertDialogAction(
|
||||
key: EditBundleAction.removeFromBundle,
|
||||
AdaptiveModalAction(
|
||||
value: EditBundleAction.removeFromBundle,
|
||||
label: L10n.of(context).removeFromBundle,
|
||||
),
|
||||
],
|
||||
|
|
@ -1211,12 +1123,12 @@ class ChatListController extends State<ChatList>
|
|||
final bundle = await showTextInputDialog(
|
||||
context: context,
|
||||
title: l10n.bundleName,
|
||||
textFields: [DialogTextField(hintText: l10n.bundleName)],
|
||||
hintText: l10n.bundleName,
|
||||
);
|
||||
if (bundle == null || bundle.isEmpty || bundle.single.isEmpty) return;
|
||||
if (bundle == null || bundle.isEmpty || bundle.isEmpty) return;
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => client.setAccountBundle(bundle.single),
|
||||
future: () => client.setAccountBundle(bundle),
|
||||
);
|
||||
break;
|
||||
case EditBundleAction.removeFromBundle:
|
||||
|
|
@ -1282,6 +1194,7 @@ enum ChatContextAction {
|
|||
mute,
|
||||
leave,
|
||||
addToSpace,
|
||||
block,
|
||||
// #Pangea
|
||||
removeFromSpace,
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import 'package:fluffychat/widgets/avatar.dart';
|
|||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import 'package:fluffychat/widgets/public_room_bottom_sheet.dart';
|
||||
import '../../config/themes.dart';
|
||||
import '../../widgets/connection_status_header.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
|
||||
class ChatListViewBody extends StatelessWidget {
|
||||
|
|
@ -152,7 +151,6 @@ class ChatListViewBody extends StatelessWidget {
|
|||
// ),
|
||||
// ),
|
||||
// Pangea#
|
||||
const ConnectionStatusHeader(),
|
||||
AnimatedContainer(
|
||||
height: controller.isTorBrowser ? 64 : 0,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||
import 'package:fluffychat/pages/chat_list/client_chooser_button.dart';
|
||||
import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators.dart';
|
||||
import 'package:fluffychat/utils/sync_status_localization.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
final ChatListController controller;
|
||||
|
|
@ -20,44 +22,24 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final selectMode = controller.selectMode;
|
||||
final client = Matrix.of(context).client;
|
||||
|
||||
return SliverAppBar(
|
||||
floating: true,
|
||||
// #Pangea
|
||||
// toolbarHeight: 72,
|
||||
toolbarHeight: controller.isSearchMode ? 72 : 175,
|
||||
// Pangea#
|
||||
pinned:
|
||||
FluffyThemes.isColumnMode(context) || selectMode != SelectMode.normal,
|
||||
scrolledUnderElevation: selectMode == SelectMode.normal ? 0 : null,
|
||||
// #Pangea
|
||||
// backgroundColor:
|
||||
// selectMode == SelectMode.normal ? Colors.transparent : null,
|
||||
// Pangea#
|
||||
toolbarHeight: 72,
|
||||
pinned: FluffyThemes.isColumnMode(context),
|
||||
scrolledUnderElevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
automaticallyImplyLeading: false,
|
||||
// #Pangea
|
||||
// leading: selectMode == SelectMode.normal
|
||||
// ? null
|
||||
// : IconButton(
|
||||
// tooltip: L10n.of(context).cancel,
|
||||
// icon: const Icon(Icons.close_outlined),
|
||||
// onPressed: controller.cancelAction,
|
||||
// color: theme.colorScheme.primary,
|
||||
// ),
|
||||
// Pangea#
|
||||
title:
|
||||
// #Pangea
|
||||
// selectMode == SelectMode.share
|
||||
// ? Text(
|
||||
// L10n.of(context).share,
|
||||
// key: const ValueKey(SelectMode.share),
|
||||
// )
|
||||
Column(
|
||||
children: [
|
||||
// Pangea#
|
||||
TextField(
|
||||
title: StreamBuilder(
|
||||
stream: client.onSyncStatus.stream,
|
||||
builder: (context, snapshot) {
|
||||
final status = client.onSyncStatus.value ??
|
||||
const SyncStatusUpdate(SyncStatus.waitingForResponse);
|
||||
final hide = client.onSync.value != null &&
|
||||
status.status != SyncStatus.error &&
|
||||
client.prevBatch != null;
|
||||
return TextField(
|
||||
controller: controller.searchController,
|
||||
focusNode: controller.searchFocusNode,
|
||||
textInputAction: TextInputAction.search,
|
||||
|
|
@ -73,25 +55,36 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||
borderRadius: BorderRadius.circular(99),
|
||||
),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
hintText: L10n.of(context).searchChatsRooms,
|
||||
hintText: hide
|
||||
? L10n.of(context).searchChatsRooms
|
||||
: status.calcLocalizedString(context),
|
||||
hintStyle: TextStyle(
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
color: status.error != null
|
||||
? theme.colorScheme.error
|
||||
: theme.colorScheme.onPrimaryContainer,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
prefixIcon: controller.isSearchMode
|
||||
? IconButton(
|
||||
tooltip: L10n.of(context).cancel,
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: controller.cancelSearch,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: controller.startSearch,
|
||||
icon: Icon(
|
||||
Icons.search_outlined,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
prefixIcon: hide
|
||||
? controller.isSearchMode
|
||||
? IconButton(
|
||||
tooltip: L10n.of(context).cancel,
|
||||
icon: const Icon(Icons.close_outlined),
|
||||
onPressed: controller.cancelSearch,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: controller.startSearch,
|
||||
icon: Icon(
|
||||
Icons.search_outlined,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
: Icon(
|
||||
status.icon,
|
||||
color: status.error != null
|
||||
? theme.colorScheme.error
|
||||
: theme.colorScheme.onPrimaryContainer,
|
||||
size: 18,
|
||||
),
|
||||
suffixIcon: controller.isSearchMode && globalSearch
|
||||
? controller.isSearching
|
||||
|
|
@ -107,59 +100,33 @@ class ChatListHeader extends StatelessWidget implements PreferredSizeWidget {
|
|||
),
|
||||
),
|
||||
)
|
||||
// #Pangea
|
||||
: const SizedBox(
|
||||
width: 0,
|
||||
child: ClientChooserButton(),
|
||||
: TextButton.icon(
|
||||
onPressed: controller.setServer,
|
||||
style: TextButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
),
|
||||
textStyle: const TextStyle(fontSize: 12),
|
||||
),
|
||||
icon: const Icon(Icons.edit_outlined, size: 16),
|
||||
label: Text(
|
||||
controller.searchServer ??
|
||||
Matrix.of(context).client.homeserver!.host,
|
||||
maxLines: 2,
|
||||
),
|
||||
)
|
||||
// : TextButton.icon(
|
||||
// onPressed: controller.setServer,
|
||||
// style: TextButton.styleFrom(
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(99),
|
||||
// ),
|
||||
// textStyle: const TextStyle(fontSize: 12),
|
||||
// ),
|
||||
// icon: const Icon(Icons.edit_outlined, size: 16),
|
||||
// label: Text(
|
||||
// controller.searchServer ??
|
||||
// Matrix.of(context).client.homeserver!.host,
|
||||
// maxLines: 2,
|
||||
// ),
|
||||
// )
|
||||
// #Pangea
|
||||
: const SizedBox(
|
||||
width: 0,
|
||||
child: ClientChooserButton(
|
||||
// #Pangea
|
||||
// controller
|
||||
// controller,
|
||||
// Pangea#
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// #Pangea
|
||||
if (!controller.isSearchMode)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(top: 16.0),
|
||||
child: LearningProgressIndicators(),
|
||||
),
|
||||
// Pangea#
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
// #Pangea
|
||||
// actions: selectMode == SelectMode.share
|
||||
// ? [
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 16.0,
|
||||
// vertical: 8.0,
|
||||
// ),
|
||||
// child: ClientChooserButton(controller),
|
||||
// ),
|
||||
// ]
|
||||
// : null,
|
||||
// Pangea#
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
|
|
@ -9,6 +8,7 @@ import 'package:fluffychat/pangea/chat_list/utils/get_chat_list_item_subtitle.da
|
|||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
|
||||
import 'package:fluffychat/utils/room_status_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import '../../config/themes.dart';
|
||||
|
|
@ -58,7 +58,7 @@ class ChatListItem extends StatelessWidget {
|
|||
? L10n.of(context).leaveSpaceDescription
|
||||
: L10n.of(context).archiveRoomDescription,
|
||||
// Pangea#
|
||||
isDestructiveAction: true,
|
||||
isDestructive: true,
|
||||
);
|
||||
if (confirmed != OkCancelResult.ok) return false;
|
||||
final leaveResult = await showFutureLoadingDialog(
|
||||
|
|
@ -339,7 +339,7 @@ class ChatListItem extends StatelessWidget {
|
|||
// Pangea#
|
||||
: FutureBuilder(
|
||||
key: ValueKey(
|
||||
'${lastEvent?.eventId}_${lastEvent?.type}',
|
||||
'${lastEvent?.eventId}_${lastEvent?.type}_${lastEvent?.redacted}',
|
||||
),
|
||||
future: needLastEventSender
|
||||
? lastEvent.calcLocalizedBody(
|
||||
|
|
|
|||
|
|
@ -22,133 +22,119 @@ class ChatListView extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final client = Matrix.of(context).client;
|
||||
return StreamBuilder<Object?>(
|
||||
stream: Matrix.of(context).onShareContentChanged.stream,
|
||||
builder: (_, __) {
|
||||
final selectMode = controller.selectMode;
|
||||
return PopScope(
|
||||
canPop: controller.selectMode == SelectMode.normal &&
|
||||
!controller.isSearchMode &&
|
||||
controller.activeSpaceId == null,
|
||||
onPopInvokedWithResult: (pop, _) {
|
||||
if (pop) return;
|
||||
if (controller.activeSpaceId != null) {
|
||||
controller.clearActiveSpace();
|
||||
return;
|
||||
}
|
||||
final selMode = controller.selectMode;
|
||||
if (controller.isSearchMode) {
|
||||
controller.cancelSearch();
|
||||
return;
|
||||
}
|
||||
if (selMode != SelectMode.normal) {
|
||||
controller.cancelAction();
|
||||
return;
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
if (FluffyThemes.isColumnMode(context) &&
|
||||
controller.widget.displayNavigationRail) ...[
|
||||
StreamBuilder(
|
||||
key: ValueKey(
|
||||
client.userID.toString(),
|
||||
),
|
||||
stream: client.onSync.stream
|
||||
.where((s) => s.hasRoomUpdate)
|
||||
.rateLimit(const Duration(seconds: 1)),
|
||||
builder: (context, _) {
|
||||
final allSpaces = Matrix.of(context)
|
||||
.client
|
||||
.rooms
|
||||
.where((room) => room.isSpace);
|
||||
final rootSpaces = allSpaces
|
||||
.where(
|
||||
(space) => !allSpaces.any(
|
||||
(parentSpace) => parentSpace.spaceChildren
|
||||
.any((child) => child.roomId == space.id),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return SizedBox(
|
||||
width: FluffyThemes.navRailWidth,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: rootSpaces.length + 2,
|
||||
itemBuilder: (context, i) {
|
||||
if (i == 0) {
|
||||
return NaviRailItem(
|
||||
isSelected: controller.activeSpaceId == null,
|
||||
onTap: controller.clearActiveSpace,
|
||||
icon: const Icon(Icons.forum_outlined),
|
||||
selectedIcon: const Icon(Icons.forum),
|
||||
toolTip: L10n.of(context).chats,
|
||||
unreadBadgeFilter: (room) => true,
|
||||
);
|
||||
}
|
||||
i--;
|
||||
if (i == rootSpaces.length) {
|
||||
return NaviRailItem(
|
||||
isSelected: false,
|
||||
onTap: () => context.go('/rooms/newspace'),
|
||||
icon: const Icon(Icons.add),
|
||||
toolTip: L10n.of(context).createNewSpace,
|
||||
);
|
||||
}
|
||||
final space = rootSpaces[i];
|
||||
final displayname =
|
||||
rootSpaces[i].getLocalizedDisplayname(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
);
|
||||
final spaceChildrenIds =
|
||||
space.spaceChildren.map((c) => c.roomId).toSet();
|
||||
return NaviRailItem(
|
||||
toolTip: displayname,
|
||||
isSelected: controller.activeSpaceId == space.id,
|
||||
onTap: () =>
|
||||
controller.setActiveSpace(rootSpaces[i].id),
|
||||
unreadBadgeFilter: (room) =>
|
||||
spaceChildrenIds.contains(room.id),
|
||||
icon: Avatar(
|
||||
mxContent: rootSpaces[i].avatar,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: space.directChatMatrixID,
|
||||
// Pangea#
|
||||
size: 32,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius / 4,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
return PopScope(
|
||||
canPop: !controller.isSearchMode && controller.activeSpaceId == null,
|
||||
onPopInvokedWithResult: (pop, _) {
|
||||
if (pop) return;
|
||||
if (controller.activeSpaceId != null) {
|
||||
controller.clearActiveSpace();
|
||||
return;
|
||||
}
|
||||
if (controller.isSearchMode) {
|
||||
controller.cancelSearch();
|
||||
return;
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
if (FluffyThemes.isColumnMode(context) &&
|
||||
controller.widget.displayNavigationRail) ...[
|
||||
StreamBuilder(
|
||||
key: ValueKey(
|
||||
client.userID.toString(),
|
||||
),
|
||||
stream: client.onSync.stream
|
||||
.where((s) => s.hasRoomUpdate)
|
||||
.rateLimit(const Duration(seconds: 1)),
|
||||
builder: (context, _) {
|
||||
final allSpaces = Matrix.of(context)
|
||||
.client
|
||||
.rooms
|
||||
.where((room) => room.isSpace);
|
||||
final rootSpaces = allSpaces
|
||||
.where(
|
||||
(space) => !allSpaces.any(
|
||||
(parentSpace) => parentSpace.spaceChildren
|
||||
.any((child) => child.roomId == space.id),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: FocusManager.instance.primaryFocus?.unfocus,
|
||||
excludeFromSemantics: true,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Scaffold(
|
||||
// #Pangea
|
||||
// body: ChatListViewBody(controller),
|
||||
body: ChatListViewBodyWrapper(controller: controller),
|
||||
// Pangea#
|
||||
floatingActionButton: selectMode == SelectMode.normal &&
|
||||
!controller.isSearchMode &&
|
||||
controller.activeSpaceId == null
|
||||
)
|
||||
.toList();
|
||||
|
||||
return SizedBox(
|
||||
width: FluffyThemes.navRailWidth,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: rootSpaces.length + 2,
|
||||
itemBuilder: (context, i) {
|
||||
if (i == 0) {
|
||||
return NaviRailItem(
|
||||
isSelected: controller.activeSpaceId == null,
|
||||
onTap: controller.clearActiveSpace,
|
||||
icon: const Icon(Icons.forum_outlined),
|
||||
selectedIcon: const Icon(Icons.forum),
|
||||
toolTip: L10n.of(context).chats,
|
||||
unreadBadgeFilter: (room) => true,
|
||||
);
|
||||
}
|
||||
i--;
|
||||
if (i == rootSpaces.length) {
|
||||
return NaviRailItem(
|
||||
isSelected: false,
|
||||
onTap: () => context.go('/rooms/newspace'),
|
||||
icon: const Icon(Icons.add),
|
||||
toolTip: L10n.of(context).createNewSpace,
|
||||
);
|
||||
}
|
||||
final space = rootSpaces[i];
|
||||
final displayname = rootSpaces[i].getLocalizedDisplayname(
|
||||
MatrixLocals(L10n.of(context)),
|
||||
);
|
||||
final spaceChildrenIds =
|
||||
space.spaceChildren.map((c) => c.roomId).toSet();
|
||||
return NaviRailItem(
|
||||
toolTip: displayname,
|
||||
isSelected: controller.activeSpaceId == space.id,
|
||||
onTap: () =>
|
||||
controller.setActiveSpace(rootSpaces[i].id),
|
||||
unreadBadgeFilter: (room) =>
|
||||
spaceChildrenIds.contains(room.id),
|
||||
icon: Avatar(
|
||||
mxContent: rootSpaces[i].avatar,
|
||||
name: displayname,
|
||||
// #Pangea
|
||||
presenceUserId: space.directChatMatrixID,
|
||||
// Pangea#
|
||||
size: 32,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius / 4,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: FocusManager.instance.primaryFocus?.unfocus,
|
||||
excludeFromSemantics: true,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Scaffold(
|
||||
// #Pangea
|
||||
// body: ChatListViewBody(controller),
|
||||
body: ChatListViewBodyWrapper(controller: controller),
|
||||
// Pangea#
|
||||
floatingActionButton:
|
||||
!controller.isSearchMode && controller.activeSpaceId == null
|
||||
? FloatingActionButton.extended(
|
||||
// #Pangea
|
||||
// onPressed: () =>
|
||||
// context.go('/rooms/newprivatechat'),
|
||||
// onPressed: () => context.go('/rooms/newprivatechat'),
|
||||
onPressed: () => context.go('/rooms/newgroup'),
|
||||
// Pangea#
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
|
|
@ -158,13 +144,11 @@ class ChatListView extends StatelessWidget {
|
|||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
|
@ -8,6 +7,7 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart';
|
||||
import 'package:fluffychat/pangea/spaces/utils/space_code.dart';
|
||||
import 'package:fluffychat/pangea/user/utils/logout.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import '../../config/themes.dart';
|
||||
|
||||
class NaviRailItem extends StatefulWidget {
|
||||
final String toolTip;
|
||||
final bool isSelected;
|
||||
final void Function() onTap;
|
||||
final Widget icon;
|
||||
final Widget? selectedIcon;
|
||||
|
||||
const NaviRailItem({
|
||||
required this.toolTip,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
required this.icon,
|
||||
this.selectedIcon,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<NaviRailItem> createState() => _NaviRailItemState();
|
||||
}
|
||||
|
||||
class _NaviRailItemState extends State<NaviRailItem> {
|
||||
bool _hovered = false;
|
||||
|
||||
void _onHover(bool hover) {
|
||||
if (hover == _hovered) return;
|
||||
setState(() {
|
||||
_hovered = hover;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final borderRadius = BorderRadius.circular(AppConfig.borderRadius);
|
||||
return SizedBox(
|
||||
height: 64,
|
||||
width: 64,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 16,
|
||||
bottom: 16,
|
||||
left: 0,
|
||||
child: AnimatedContainer(
|
||||
width: widget.isSelected ? 4 : 0,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topRight: Radius.circular(90),
|
||||
bottomRight: Radius.circular(90),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: AnimatedScale(
|
||||
scale: _hovered ? 1.2 : 1.0,
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
child: Material(
|
||||
borderRadius: borderRadius,
|
||||
color: widget.isSelected
|
||||
? theme.colorScheme.primaryContainer
|
||||
: theme.colorScheme.surface,
|
||||
child: Tooltip(
|
||||
message: widget.toolTip,
|
||||
child: InkWell(
|
||||
borderRadius: borderRadius,
|
||||
onTap: widget.onTap,
|
||||
onHover: _onHover,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: widget.isSelected
|
||||
? widget.selectedIcon ?? widget.icon
|
||||
: widget.icon,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
|
@ -19,6 +18,7 @@ import 'package:fluffychat/pangea/spaces/widgets/add_room_dialog.dart';
|
|||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -305,6 +305,7 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// message: L10n.of(context).archiveRoomDescription,
|
||||
message: L10n.of(context).leaveSpaceDescription,
|
||||
// Pangea#
|
||||
isDestructive: true,
|
||||
);
|
||||
if (!mounted) return;
|
||||
if (confirmed != OkCancelResult.ok) return;
|
||||
|
|
@ -324,23 +325,17 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
|
||||
void _addChatOrSubspace() async {
|
||||
// #Pangea
|
||||
// final roomType = await showConfirmationDialog(
|
||||
// final roomType = await showModalActionPopup(
|
||||
// context: context,
|
||||
// title: L10n.of(context).addChatOrSubSpace,
|
||||
// actions: [
|
||||
// AlertDialogAction(
|
||||
// key: AddRoomType.subspace,
|
||||
// // #Pangea
|
||||
// // label: L10n.of(context).createNewSpace,
|
||||
// label: L10n.of(context).newSpace,
|
||||
// // Pangea#
|
||||
// AdaptiveModalAction(
|
||||
// value: AddRoomType.subspace,
|
||||
// label: L10n.of(context).createNewSpace,
|
||||
// ),
|
||||
// AlertDialogAction(
|
||||
// key: AddRoomType.chat,
|
||||
// // #Pangea
|
||||
// // label: L10n.of(context).createGroup,
|
||||
// label: L10n.of(context).newChat,
|
||||
// // Pangea#
|
||||
// AdaptiveModalAction(
|
||||
// value: AddRoomType.chat,
|
||||
// label: L10n.of(context).createGroup,
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
|
|
@ -361,28 +356,18 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// title: roomType == AddRoomType.subspace
|
||||
// ? L10n.of(context).createNewSpace
|
||||
// : L10n.of(context).createGroup,
|
||||
// textFields: [
|
||||
// DialogTextField(
|
||||
// hintText: roomType == AddRoomType.subspace
|
||||
// ? L10n.of(context).spaceName
|
||||
// : L10n.of(context).groupName,
|
||||
// minLines: 1,
|
||||
// maxLines: 1,
|
||||
// maxLength: 64,
|
||||
// validator: (text) {
|
||||
// if (text == null || text.isEmpty) {
|
||||
// return L10n.of(context).pleaseChoose;
|
||||
// }
|
||||
// return null;
|
||||
// },
|
||||
// ),
|
||||
// DialogTextField(
|
||||
// hintText: L10n.of(context).chatDescription,
|
||||
// minLines: 4,
|
||||
// maxLines: 8,
|
||||
// maxLength: 255,
|
||||
// ),
|
||||
// ],
|
||||
// hintText: roomType == AddRoomType.subspace
|
||||
// ? L10n.of(context).spaceName
|
||||
// : L10n.of(context).groupName,
|
||||
// minLines: 1,
|
||||
// maxLines: 1,
|
||||
// maxLength: 64,
|
||||
// validator: (text) {
|
||||
// if (text.isEmpty) {
|
||||
// return L10n.of(context).pleaseChoose;
|
||||
// }
|
||||
// return null;
|
||||
// },
|
||||
// okLabel: L10n.of(context).create,
|
||||
// cancelLabel: L10n.of(context).cancel,
|
||||
// );
|
||||
|
|
@ -399,8 +384,7 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// #Pangea
|
||||
// if (roomType == AddRoomType.subspace) {
|
||||
// roomId = await client.createSpace(
|
||||
// name: names.first,
|
||||
// topic: names.last.isEmpty ? null : names.last,
|
||||
// name: names,
|
||||
// visibility: activeSpace.joinRules == JoinRules.public
|
||||
// ? sdk.Visibility.public
|
||||
// : sdk.Visibility.private,
|
||||
|
|
@ -409,21 +393,13 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
// Pangea#
|
||||
roomId = await client.createGroupChat(
|
||||
// #Pangea
|
||||
// groupName: names.first,
|
||||
// groupName: names,
|
||||
// preset: activeSpace.joinRules == JoinRules.public
|
||||
// ? CreateRoomPreset.publicChat
|
||||
// : CreateRoomPreset.privateChat,
|
||||
// visibility: activeSpace.joinRules == JoinRules.public
|
||||
// ? sdk.Visibility.public
|
||||
// : sdk.Visibility.private,
|
||||
// initialState: names.length > 1 && names.last.isNotEmpty
|
||||
// ? [
|
||||
// StateEvent(
|
||||
// type: EventTypes.RoomTopic,
|
||||
// content: {'topic': names.last},
|
||||
// ),
|
||||
// ]
|
||||
// : null,
|
||||
groupName: response.roomName,
|
||||
preset: response.joinRules == sdk.JoinRules.public
|
||||
? CreateRoomPreset.publicChat
|
||||
|
|
@ -440,6 +416,7 @@ class _SpaceViewState extends State<SpaceView> {
|
|||
enableEncryption: false,
|
||||
// Pangea#
|
||||
);
|
||||
// }
|
||||
await activeSpace.setSpaceChild(roomId);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/encryption/utils/key_verification.dart';
|
||||
|
|
@ -8,6 +7,8 @@ import 'package:matrix/matrix.dart';
|
|||
|
||||
import 'package:fluffychat/pages/device_settings/device_settings_view.dart';
|
||||
import 'package:fluffychat/pages/key_verification/key_verification_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
|
||||
|
|
@ -54,9 +55,10 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
|||
if (await showOkCancelAlertDialog(
|
||||
context: context,
|
||||
title: L10n.of(context).areYouSure,
|
||||
okLabel: L10n.of(context).yes,
|
||||
okLabel: L10n.of(context).remove,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
message: L10n.of(context).removeDevicesDescription,
|
||||
isDestructive: true,
|
||||
) ==
|
||||
OkCancelResult.cancel) {
|
||||
return;
|
||||
|
|
@ -86,18 +88,14 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
|||
title: L10n.of(context).changeDeviceName,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
hintText: device.displayName,
|
||||
),
|
||||
],
|
||||
hintText: device.displayName,
|
||||
);
|
||||
if (displayName == null) return;
|
||||
final success = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => Matrix.of(context)
|
||||
.client
|
||||
.updateDevice(device.deviceId, displayName: displayName.single),
|
||||
.updateDevice(device.deviceId, displayName: displayName),
|
||||
);
|
||||
if (success.error == null) {
|
||||
reload();
|
||||
|
|
@ -111,7 +109,6 @@ class DevicesSettingsController extends State<DevicesSettings> {
|
|||
message: L10n.of(context).verifyOtherDeviceDescription,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
fullyCapitalizedForMaterial: false,
|
||||
);
|
||||
if (consent != OkCancelResult.ok) return;
|
||||
final req = await Matrix.of(context)
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ class DevicesSettingsView extends StatelessWidget {
|
|||
L10n.of(context).removeAllOtherDevices,
|
||||
),
|
||||
style: TextButton.styleFrom(
|
||||
iconColor: theme.colorScheme.onErrorContainer,
|
||||
foregroundColor:
|
||||
theme.colorScheme.onErrorContainer,
|
||||
backgroundColor:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
import '../../utils/date_time_extension.dart';
|
||||
import '../../utils/matrix_sdk_extensions/device_extension.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
|
|
@ -49,37 +49,43 @@ class UserDeviceListItem extends StatelessWidget {
|
|||
clipBehavior: Clip.hardEdge,
|
||||
child: ListTile(
|
||||
onTap: () async {
|
||||
final action = await showModalActionSheet<UserDeviceListItemAction>(
|
||||
final action = await showModalActionPopup<UserDeviceListItemAction>(
|
||||
context: context,
|
||||
title: '${userDevice.displayName} (${userDevice.deviceId})',
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
actions: [
|
||||
SheetAction(
|
||||
key: UserDeviceListItemAction.rename,
|
||||
AdaptiveModalAction(
|
||||
value: UserDeviceListItemAction.rename,
|
||||
icon: const Icon(Icons.edit_outlined),
|
||||
label: L10n.of(context).changeDeviceName,
|
||||
),
|
||||
if (!isOwnDevice && keys != null) ...{
|
||||
SheetAction(
|
||||
key: UserDeviceListItemAction.verify,
|
||||
AdaptiveModalAction(
|
||||
value: UserDeviceListItemAction.verify,
|
||||
icon: const Icon(Icons.verified_outlined),
|
||||
label: L10n.of(context).verifyStart,
|
||||
),
|
||||
if (!keys.blocked)
|
||||
SheetAction(
|
||||
key: UserDeviceListItemAction.block,
|
||||
AdaptiveModalAction(
|
||||
value: UserDeviceListItemAction.block,
|
||||
icon: const Icon(Icons.block_outlined),
|
||||
label: L10n.of(context).blockDevice,
|
||||
isDestructiveAction: true,
|
||||
isDestructive: true,
|
||||
),
|
||||
if (keys.blocked)
|
||||
SheetAction(
|
||||
key: UserDeviceListItemAction.unblock,
|
||||
AdaptiveModalAction(
|
||||
value: UserDeviceListItemAction.unblock,
|
||||
icon: const Icon(Icons.block),
|
||||
label: L10n.of(context).unblockDevice,
|
||||
isDestructiveAction: true,
|
||||
isDestructive: true,
|
||||
),
|
||||
},
|
||||
if (!isOwnDevice)
|
||||
SheetAction(
|
||||
key: UserDeviceListItemAction.remove,
|
||||
AdaptiveModalAction(
|
||||
value: UserDeviceListItemAction.remove,
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
label: L10n.of(context).delete,
|
||||
isDestructiveAction: true,
|
||||
isDestructive: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'dart:async';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
||||
|
|
@ -17,6 +16,7 @@ import 'package:fluffychat/config/app_config.dart';
|
|||
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../utils/localized_exception_extension.dart';
|
||||
|
||||
|
|
@ -33,7 +33,6 @@ class HomeserverPicker extends StatefulWidget {
|
|||
|
||||
class HomeserverPickerController extends State<HomeserverPicker> {
|
||||
bool isLoading = false;
|
||||
bool isLoggingIn = false;
|
||||
|
||||
final TextEditingController homeserverController = TextEditingController(
|
||||
text: AppConfig.defaultHomeserver,
|
||||
|
|
@ -61,58 +60,29 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
isTorBrowser = isTor;
|
||||
}
|
||||
|
||||
String? _lastCheckedUrl;
|
||||
|
||||
Timer? _checkHomeserverCooldown;
|
||||
|
||||
tryCheckHomeserverActionWithCooldown([_]) {
|
||||
_checkHomeserverCooldown?.cancel();
|
||||
_checkHomeserverCooldown = Timer(
|
||||
const Duration(milliseconds: 500),
|
||||
checkHomeserverAction,
|
||||
);
|
||||
}
|
||||
|
||||
void tryCheckHomeserverActionWithoutCooldown([_]) {
|
||||
_checkHomeserverCooldown?.cancel();
|
||||
_lastCheckedUrl = null;
|
||||
checkHomeserverAction();
|
||||
}
|
||||
|
||||
void onSubmitted([_]) {
|
||||
if (isLoading || _checkHomeserverCooldown?.isActive == true) {
|
||||
return tryCheckHomeserverActionWithoutCooldown();
|
||||
}
|
||||
if (supportsSso) return ssoLoginAction();
|
||||
if (supportsPasswordLogin) return login();
|
||||
return tryCheckHomeserverActionWithoutCooldown();
|
||||
}
|
||||
|
||||
/// Starts an analysis of the given homeserver. It uses the current domain and
|
||||
/// makes sure that it is prefixed with https. Then it searches for the
|
||||
/// well-known information and forwards to the login page depending on the
|
||||
/// login type.
|
||||
Future<void> checkHomeserverAction([_]) async {
|
||||
Future<void> checkHomeserverAction({bool legacyPasswordLogin = false}) async {
|
||||
final homeserverInput =
|
||||
homeserverController.text.trim().toLowerCase().replaceAll(' ', '-');
|
||||
|
||||
if (homeserverInput.isEmpty || !homeserverInput.contains('.')) {
|
||||
if (homeserverInput.isEmpty) {
|
||||
setState(() {
|
||||
error = loginFlows = null;
|
||||
isLoading = false;
|
||||
Matrix.of(context).getLoginClient().homeserver = null;
|
||||
_lastCheckedUrl = null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (_lastCheckedUrl == homeserverInput) return;
|
||||
|
||||
_lastCheckedUrl = homeserverInput;
|
||||
setState(() {
|
||||
error = loginFlows = null;
|
||||
isLoading = true;
|
||||
});
|
||||
|
||||
final l10n = L10n.of(context);
|
||||
|
||||
try {
|
||||
var homeserver = Uri.parse(homeserverInput);
|
||||
if (homeserver.scheme.isEmpty) {
|
||||
|
|
@ -121,6 +91,21 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
final client = Matrix.of(context).getLoginClient();
|
||||
final (_, _, loginFlows) = await client.checkHomeserver(homeserver);
|
||||
this.loginFlows = loginFlows;
|
||||
if (supportsSso && !legacyPasswordLogin) {
|
||||
if (!PlatformInfos.isMobile) {
|
||||
final consent = await showOkCancelAlertDialog(
|
||||
context: context,
|
||||
title: l10n.appWantsToUseForLogin(homeserverInput),
|
||||
message: l10n.appWantsToUseForLoginDescription,
|
||||
okLabel: l10n.continueText,
|
||||
);
|
||||
if (consent != OkCancelResult.ok) return;
|
||||
}
|
||||
return ssoLoginAction();
|
||||
}
|
||||
context.push(
|
||||
'${GoRouter.of(context).routeInformationProvider.value.uri.path}/login',
|
||||
);
|
||||
} catch (e) {
|
||||
setState(
|
||||
() => error = (e).toLocalizedString(
|
||||
|
|
@ -176,7 +161,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
|
||||
setState(() {
|
||||
error = null;
|
||||
isLoading = isLoggingIn = true;
|
||||
isLoading = true;
|
||||
});
|
||||
try {
|
||||
await Matrix.of(context).getLoginClient().login(
|
||||
|
|
@ -191,27 +176,16 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = isLoggingIn = false;
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void login() async {
|
||||
if (!supportsPasswordLogin) {
|
||||
homeserverController.text = AppConfig.defaultHomeserver;
|
||||
await checkHomeserverAction();
|
||||
}
|
||||
context.push(
|
||||
'${GoRouter.of(context).routeInformationProvider.value.uri.path}/login',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_checkTorBrowser();
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback(checkHomeserverAction);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -223,7 +197,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
if (file == null) return;
|
||||
setState(() {
|
||||
error = null;
|
||||
isLoading = isLoggingIn = true;
|
||||
isLoading = true;
|
||||
});
|
||||
try {
|
||||
final client = Matrix.of(context).getLoginClient();
|
||||
|
|
@ -236,7 +210,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = isLoggingIn = false;
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -245,7 +219,7 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||
void onMoreAction(MoreLoginActions action) {
|
||||
switch (action) {
|
||||
case MoreLoginActions.passwordLogin:
|
||||
login();
|
||||
checkHomeserverAction(legacyPasswordLogin: true);
|
||||
case MoreLoginActions.privacy:
|
||||
launchUrlString(AppConfig.privacyUrl);
|
||||
case MoreLoginActions.about:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:url_launcher/url_launcher.dart';
|
|||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialog_action.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../config/themes.dart';
|
||||
|
|
@ -121,7 +121,7 @@ class HomeserverPickerView extends StatelessWidget {
|
|||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32.0),
|
||||
child: SelectableLinkify(
|
||||
text: L10n.of(context).welcomeText,
|
||||
text: L10n.of(context).appIntroduction,
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
|
@ -142,30 +142,13 @@ class HomeserverPickerView extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
onChanged:
|
||||
controller.tryCheckHomeserverActionWithCooldown,
|
||||
onSubmitted: controller.onSubmitted,
|
||||
onTap:
|
||||
controller.tryCheckHomeserverActionWithCooldown,
|
||||
onSubmitted: (_) =>
|
||||
controller.checkHomeserverAction(),
|
||||
controller: controller.homeserverController,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.url,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: controller.isLoading
|
||||
? Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
alignment: Alignment.center,
|
||||
child: const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child:
|
||||
CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.search_outlined),
|
||||
prefixIcon: const Icon(Icons.search_outlined),
|
||||
filled: false,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(
|
||||
|
|
@ -219,25 +202,21 @@ class HomeserverPickerView extends StatelessWidget {
|
|||
backgroundColor: theme.colorScheme.primary,
|
||||
foregroundColor: theme.colorScheme.onPrimary,
|
||||
),
|
||||
onPressed:
|
||||
controller.isLoggingIn || controller.isLoading
|
||||
? null
|
||||
: controller.supportsSso
|
||||
? controller.ssoLoginAction
|
||||
: controller.supportsPasswordLogin
|
||||
? controller.login
|
||||
: null,
|
||||
child: Text(L10n.of(context).continueText),
|
||||
onPressed: controller.isLoading
|
||||
? null
|
||||
: controller.checkHomeserverAction,
|
||||
child: controller.isLoading
|
||||
? const LinearProgressIndicator()
|
||||
: Text(L10n.of(context).continueText),
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.secondary,
|
||||
textStyle: theme.textTheme.labelMedium,
|
||||
),
|
||||
onPressed:
|
||||
controller.isLoggingIn || controller.isLoading
|
||||
? null
|
||||
: controller.restoreBackup,
|
||||
onPressed: controller.isLoading
|
||||
? null
|
||||
: controller.restoreBackup,
|
||||
child: Text(L10n.of(context).hydrate),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,36 +1,89 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pages/image_viewer/image_viewer_view.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/utils/show_scaffold_dialog.dart';
|
||||
import 'package:fluffychat/widgets/share_scaffold_dialog.dart';
|
||||
import '../../utils/matrix_sdk_extensions/event_extension.dart';
|
||||
|
||||
class ImageViewer extends StatefulWidget {
|
||||
final Event event;
|
||||
final Timeline? timeline;
|
||||
final BuildContext outerContext;
|
||||
|
||||
const ImageViewer(this.event, {required this.outerContext, super.key});
|
||||
const ImageViewer(
|
||||
this.event, {
|
||||
required this.outerContext,
|
||||
this.timeline,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
ImageViewerController createState() => ImageViewerController();
|
||||
}
|
||||
|
||||
class ImageViewerController extends State<ImageViewer> {
|
||||
/// Forward this image to another room.
|
||||
void forwardAction() {
|
||||
Matrix.of(widget.outerContext).shareContent = widget.event.content;
|
||||
Navigator.of(context).pop();
|
||||
widget.outerContext.go('/rooms');
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
allEvents = widget.timeline?.events
|
||||
.where((event) => event.messageType == MessageTypes.Image)
|
||||
.toList()
|
||||
.reversed
|
||||
.toList() ??
|
||||
[widget.event];
|
||||
var index =
|
||||
allEvents.indexWhere((event) => event.eventId == widget.event.eventId);
|
||||
if (index < 0) index = 0;
|
||||
pageController = PageController(initialPage: index);
|
||||
}
|
||||
|
||||
/// Save this file with a system call.
|
||||
void saveFileAction(BuildContext context) => widget.event.saveFile(context);
|
||||
late final PageController pageController;
|
||||
|
||||
late final List<Event> allEvents;
|
||||
|
||||
void prevImage() async {
|
||||
await pageController.previousPage(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void nextImage() async {
|
||||
await pageController.nextPage(
|
||||
duration: FluffyThemes.animationDuration,
|
||||
curve: FluffyThemes.animationCurve,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
int get _index => pageController.page?.toInt() ?? 0;
|
||||
|
||||
Event get currentEvent => allEvents[_index];
|
||||
|
||||
bool get canGoNext => _index < allEvents.length - 1;
|
||||
|
||||
bool get canGoBack => _index > 0;
|
||||
|
||||
/// Forward this image to another room.
|
||||
void forwardAction() => showScaffoldDialog(
|
||||
context: context,
|
||||
builder: (context) => ShareScaffoldDialog(
|
||||
items: [ContentShareItem(currentEvent.content)],
|
||||
),
|
||||
);
|
||||
|
||||
/// Save this file with a system call.
|
||||
void shareFileAction(BuildContext context) => widget.event.shareFile(context);
|
||||
void saveFileAction(BuildContext context) => currentEvent.saveFile(context);
|
||||
|
||||
/// Save this file with a system call.
|
||||
void shareFileAction(BuildContext context) => currentEvent.shareFile(context);
|
||||
|
||||
static const maxScaleFactor = 1.5;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/hover_builder.dart';
|
||||
import 'package:fluffychat/widgets/mxc_image.dart';
|
||||
import 'image_viewer.dart';
|
||||
|
||||
|
|
@ -13,73 +14,113 @@ class ImageViewerView extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black.withAlpha(128),
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.black.withAlpha(128),
|
||||
),
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
color: Colors.white,
|
||||
tooltip: L10n.of(context).close,
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
actions: [
|
||||
IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.black.withAlpha(128),
|
||||
),
|
||||
icon: const Icon(Icons.reply_outlined),
|
||||
onPressed: controller.forwardAction,
|
||||
final iconButtonStyle = IconButton.styleFrom(
|
||||
backgroundColor: Colors.black.withAlpha(200),
|
||||
foregroundColor: Colors.white,
|
||||
);
|
||||
return GestureDetector(
|
||||
onTap: () => Navigator.of(context).pop(),
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.black.withAlpha(128),
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
style: iconButtonStyle,
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
color: Colors.white,
|
||||
tooltip: L10n.of(context).share,
|
||||
tooltip: L10n.of(context).close,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.black.withAlpha(128),
|
||||
backgroundColor: Colors.transparent,
|
||||
actions: [
|
||||
IconButton(
|
||||
style: iconButtonStyle,
|
||||
icon: const Icon(Icons.reply_outlined),
|
||||
onPressed: controller.forwardAction,
|
||||
color: Colors.white,
|
||||
tooltip: L10n.of(context).share,
|
||||
),
|
||||
icon: const Icon(Icons.download_outlined),
|
||||
onPressed: () => controller.saveFileAction(context),
|
||||
color: Colors.white,
|
||||
tooltip: L10n.of(context).downloadFile,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
if (PlatformInfos.isMobile)
|
||||
// Use builder context to correctly position the share dialog on iPad
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Builder(
|
||||
builder: (context) => IconButton(
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.black.withAlpha(128),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
style: iconButtonStyle,
|
||||
icon: const Icon(Icons.download_outlined),
|
||||
onPressed: () => controller.saveFileAction(context),
|
||||
color: Colors.white,
|
||||
tooltip: L10n.of(context).downloadFile,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
if (PlatformInfos.isMobile)
|
||||
// Use builder context to correctly position the share dialog on iPad
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8.0),
|
||||
child: Builder(
|
||||
builder: (context) => IconButton(
|
||||
style: iconButtonStyle,
|
||||
onPressed: () => controller.shareFileAction(context),
|
||||
tooltip: L10n.of(context).share,
|
||||
color: Colors.white,
|
||||
icon: Icon(Icons.adaptive.share_outlined),
|
||||
),
|
||||
onPressed: () => controller.shareFileAction(context),
|
||||
tooltip: L10n.of(context).share,
|
||||
color: Colors.white,
|
||||
icon: Icon(Icons.adaptive.share_outlined),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: InteractiveViewer(
|
||||
minScale: 1.0,
|
||||
maxScale: 10.0,
|
||||
onInteractionEnd: controller.onInteractionEnds,
|
||||
child: Center(
|
||||
child: Hero(
|
||||
tag: controller.widget.event.eventId,
|
||||
child: MxcImage(
|
||||
event: controller.widget.event,
|
||||
fit: BoxFit.contain,
|
||||
isThumbnail: false,
|
||||
animated: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: HoverBuilder(
|
||||
builder: (context, hovered) => Stack(
|
||||
children: [
|
||||
PageView.builder(
|
||||
controller: controller.pageController,
|
||||
itemCount: controller.allEvents.length,
|
||||
itemBuilder: (context, i) => InteractiveViewer(
|
||||
minScale: 1.0,
|
||||
maxScale: 10.0,
|
||||
onInteractionEnd: controller.onInteractionEnds,
|
||||
child: Center(
|
||||
child: Hero(
|
||||
tag: controller.allEvents[i].eventId,
|
||||
child: GestureDetector(
|
||||
// Ignore taps to not go back here:
|
||||
onTap: () {},
|
||||
child: MxcImage(
|
||||
key: ValueKey(controller.allEvents[i].eventId),
|
||||
event: controller.allEvents[i],
|
||||
fit: BoxFit.contain,
|
||||
isThumbnail: false,
|
||||
animated: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (hovered && controller.canGoBack)
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: IconButton(
|
||||
style: iconButtonStyle,
|
||||
tooltip: L10n.of(context).previous,
|
||||
icon: const Icon(Icons.chevron_left_outlined),
|
||||
onPressed: controller.prevImage,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (hovered && controller.canGoNext)
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: IconButton(
|
||||
style: iconButtonStyle,
|
||||
tooltip: L10n.of(context).next,
|
||||
icon: const Icon(Icons.chevron_right_outlined),
|
||||
onPressed: controller.nextImage,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import 'dart:ui';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/encryption.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/adaptive_dialog_action.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/adaptive_dialog_action.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
|||
await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
message: L10n.of(context).incorrectPassphraseOrKey,
|
||||
title: L10n.of(context).incorrectPassphraseOrKey,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
|
|
@ -12,6 +11,8 @@ import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart';
|
|||
import 'package:fluffychat/pangea/login/pages/pangea_login_view.dart';
|
||||
import 'package:fluffychat/pangea/login/widgets/p_sso_button.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../utils/platform_infos.dart';
|
||||
|
|
@ -255,7 +256,7 @@ class LoginController extends State<Login> {
|
|||
final dialogResult = await showOkCancelAlertDialog(
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
message: L10n.of(context).noMatrixServer(newDomain, oldHomeserver!),
|
||||
title: L10n.of(context).noMatrixServer(newDomain, oldHomeserver!),
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
);
|
||||
|
|
@ -289,15 +290,10 @@ class LoginController extends State<Login> {
|
|||
message: L10n.of(context).enterAnEmailAddress,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
fullyCapitalizedForMaterial: false,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
initialText:
|
||||
usernameController.text.isEmail ? usernameController.text : '',
|
||||
hintText: L10n.of(context).enterAnEmailAddress,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
],
|
||||
initialText:
|
||||
usernameController.text.isEmail ? usernameController.text : '',
|
||||
hintText: L10n.of(context).enterAnEmailAddress,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
);
|
||||
if (input == null) return;
|
||||
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
|
|
@ -306,7 +302,7 @@ class LoginController extends State<Login> {
|
|||
future: () =>
|
||||
Matrix.of(context).getLoginClient().requestTokenToResetPasswordEmail(
|
||||
clientSecret,
|
||||
input.single,
|
||||
input,
|
||||
sendAttempt++,
|
||||
),
|
||||
);
|
||||
|
|
@ -318,15 +314,10 @@ class LoginController extends State<Login> {
|
|||
message: L10n.of(context).chooseAStrongPassword,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
fullyCapitalizedForMaterial: false,
|
||||
textFields: [
|
||||
const DialogTextField(
|
||||
hintText: '******',
|
||||
obscureText: true,
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
),
|
||||
],
|
||||
hintText: '******',
|
||||
obscureText: true,
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
);
|
||||
if (password == null) return;
|
||||
final ok = await showOkAlertDialog(
|
||||
|
|
@ -335,11 +326,10 @@ class LoginController extends State<Login> {
|
|||
title: L10n.of(context).weSentYouAnEmail,
|
||||
message: L10n.of(context).pleaseClickOnLink,
|
||||
okLabel: L10n.of(context).iHaveClickedOnLink,
|
||||
fullyCapitalizedForMaterial: false,
|
||||
);
|
||||
if (ok != OkCancelResult.ok) return;
|
||||
final data = <String, dynamic>{
|
||||
'new_password': password.single,
|
||||
'new_password': password,
|
||||
'logout_devices': false,
|
||||
"auth": AuthenticationThreePidCreds(
|
||||
type: AuthenticationTypes.emailIdentity,
|
||||
|
|
@ -361,8 +351,8 @@ class LoginController extends State<Login> {
|
|||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(L10n.of(context).passwordHasBeenChanged)),
|
||||
);
|
||||
usernameController.text = input.single;
|
||||
passwordController.text = password.single;
|
||||
usernameController.text = input;
|
||||
passwordController.text = password;
|
||||
login();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class NewGroupController extends State<NewGroup> {
|
|||
bool requiredCodeToJoin = false;
|
||||
// bool publicGroup = false;
|
||||
// Pangea#
|
||||
bool groupCanBeFound = true;
|
||||
bool groupCanBeFound = false;
|
||||
|
||||
Uint8List? avatar;
|
||||
|
||||
|
|
@ -54,7 +54,8 @@ class NewGroupController extends State<NewGroup> {
|
|||
setState(() => _createGroupType = b.single);
|
||||
|
||||
// #Pangea
|
||||
// void setPublicGroup(bool b) => setState(() => publicGroup = b);
|
||||
// void setPublicGroup(bool b) =>
|
||||
// setState(() => publicGroup = groupCanBeFound = b);
|
||||
void setRequireCode(bool b) => setState(() => requiredCodeToJoin = b);
|
||||
// Pangea#
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import 'package:fluffychat/utils/url_launcher.dart';
|
|||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
import 'package:fluffychat/widgets/layouts/max_width_body.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../widgets/qr_code_viewer.dart';
|
||||
|
||||
class NewPrivateChatView extends StatelessWidget {
|
||||
final NewPrivateChatController controller;
|
||||
|
|
@ -25,6 +26,7 @@ class NewPrivateChatView extends StatelessWidget {
|
|||
final theme = Theme.of(context);
|
||||
|
||||
final searchResponse = controller.searchResponse;
|
||||
final userId = Matrix.of(context).client.userID!;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
scrolledUnderElevation: 0,
|
||||
|
|
@ -142,10 +144,7 @@ class NewPrivateChatView extends StatelessWidget {
|
|||
foregroundColor: theme.colorScheme.onTertiaryContainer,
|
||||
child: const Icon(Icons.group_add_outlined),
|
||||
),
|
||||
// #Pangea
|
||||
// title: Text(L10n.of(context).createGroup),
|
||||
title: Text(L10n.of(context).createChat),
|
||||
// Pangea#
|
||||
title: Text(L10n.of(context).createGroup),
|
||||
onTap: () => context.go('/rooms/newgroup'),
|
||||
),
|
||||
if (PlatformInfos.isMobile)
|
||||
|
|
@ -160,26 +159,35 @@ class NewPrivateChatView extends StatelessWidget {
|
|||
),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(64.0),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 256),
|
||||
child: Material(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
elevation: 10,
|
||||
color: Colors.white,
|
||||
shadowColor: theme.appBarTheme.shadowColor,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 64.0,
|
||||
vertical: 24.0,
|
||||
),
|
||||
child: Material(
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius),
|
||||
color: theme.colorScheme.primaryContainer,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
BorderRadius.circular(AppConfig.borderRadius),
|
||||
onTap: () => showQrCodeViewer(
|
||||
context,
|
||||
userId,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: PrettyQrView.data(
|
||||
data:
|
||||
'https://matrix.to/#/${Matrix.of(context).client.userID}',
|
||||
decoration: PrettyQrDecoration(
|
||||
shape: PrettyQrSmoothSymbol(
|
||||
roundFactor: 1,
|
||||
color: theme.brightness == Brightness.light
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.onPrimary,
|
||||
padding: const EdgeInsets.all(32.0),
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
const BoxConstraints(maxWidth: 256),
|
||||
child: PrettyQrView.data(
|
||||
data: 'https://matrix.to/#/$userId',
|
||||
decoration: PrettyQrDecoration(
|
||||
shape: PrettyQrSmoothSymbol(
|
||||
roundFactor: 1,
|
||||
color:
|
||||
theme.colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
|
@ -9,6 +8,8 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/pangea/user/utils/logout.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'settings_view.dart';
|
||||
|
|
@ -37,25 +38,14 @@ class SettingsController extends State<Settings> {
|
|||
title: L10n.of(context).editDisplayname,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
// #Pangea
|
||||
maxLength: 32,
|
||||
// Pangea#
|
||||
initialText: profile?.displayName ??
|
||||
Matrix.of(context).client.userID!.localpart,
|
||||
),
|
||||
],
|
||||
// #Pangea
|
||||
autoSubmit: true,
|
||||
// Pangea#
|
||||
initialText:
|
||||
profile?.displayName ?? Matrix.of(context).client.userID!.localpart,
|
||||
);
|
||||
if (input == null) return;
|
||||
final matrix = Matrix.of(context);
|
||||
final success = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () =>
|
||||
matrix.client.setDisplayName(matrix.client.userID!, input.single),
|
||||
future: () => matrix.client.setDisplayName(matrix.client.userID!, input),
|
||||
);
|
||||
if (success.error == null) {
|
||||
updateProfile();
|
||||
|
|
@ -71,7 +61,7 @@ class SettingsController extends State<Settings> {
|
|||
// context: context,
|
||||
// title: L10n.of(context).areYouSureYouWantToLogout,
|
||||
// message: L10n.of(context).noBackupWarning,
|
||||
// isDestructiveAction: noBackup,
|
||||
// isDestructive: noBackup,
|
||||
// okLabel: L10n.of(context).logout,
|
||||
// cancelLabel: L10n.of(context).cancel,
|
||||
// ) ==
|
||||
|
|
@ -90,30 +80,31 @@ class SettingsController extends State<Settings> {
|
|||
final profile = await profileFuture;
|
||||
final actions = [
|
||||
if (PlatformInfos.isMobile)
|
||||
SheetAction(
|
||||
key: AvatarAction.camera,
|
||||
AdaptiveModalAction(
|
||||
value: AvatarAction.camera,
|
||||
label: L10n.of(context).openCamera,
|
||||
isDefaultAction: true,
|
||||
icon: Icons.camera_alt_outlined,
|
||||
icon: const Icon(Icons.camera_alt_outlined),
|
||||
),
|
||||
SheetAction(
|
||||
key: AvatarAction.file,
|
||||
AdaptiveModalAction(
|
||||
value: AvatarAction.file,
|
||||
label: L10n.of(context).openGallery,
|
||||
icon: Icons.photo_outlined,
|
||||
icon: const Icon(Icons.photo_outlined),
|
||||
),
|
||||
if (profile?.avatarUrl != null)
|
||||
SheetAction(
|
||||
key: AvatarAction.remove,
|
||||
AdaptiveModalAction(
|
||||
value: AvatarAction.remove,
|
||||
label: L10n.of(context).removeYourAvatar,
|
||||
isDestructiveAction: true,
|
||||
icon: Icons.delete_outlined,
|
||||
isDestructive: true,
|
||||
icon: const Icon(Icons.delete_outlined),
|
||||
),
|
||||
];
|
||||
final action = actions.length == 1
|
||||
? actions.single.key
|
||||
: await showModalActionSheet<AvatarAction>(
|
||||
? actions.single.value
|
||||
: await showModalActionPopup<AvatarAction>(
|
||||
context: context,
|
||||
title: L10n.of(context).changeYourAvatar,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
actions: actions,
|
||||
);
|
||||
if (action == null) return;
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ class SettingsView extends StatelessWidget {
|
|||
),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.onSurface,
|
||||
iconColor: theme.colorScheme.onSurface,
|
||||
),
|
||||
label: Text(
|
||||
displayname,
|
||||
|
|
@ -115,6 +116,7 @@ class SettingsView extends StatelessWidget {
|
|||
),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: theme.colorScheme.secondary,
|
||||
iconColor: theme.colorScheme.secondary,
|
||||
),
|
||||
label: Text(
|
||||
mxid,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'settings_3pid_view.dart';
|
||||
|
|
@ -25,12 +26,8 @@ class Settings3PidController extends State<Settings3Pid> {
|
|||
title: L10n.of(context).enterAnEmailAddress,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
hintText: L10n.of(context).enterAnEmailAddress,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
],
|
||||
hintText: L10n.of(context).enterAnEmailAddress,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
);
|
||||
if (input == null) return;
|
||||
final clientSecret = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
|
|
@ -38,7 +35,7 @@ class Settings3PidController extends State<Settings3Pid> {
|
|||
context: context,
|
||||
future: () => Matrix.of(context).client.requestTokenToRegisterEmail(
|
||||
clientSecret,
|
||||
input.single,
|
||||
input,
|
||||
Settings3Pid.sendAttempt++,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:archive/archive.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
|
|
@ -11,6 +10,7 @@ import 'package:matrix/matrix.dart';
|
|||
|
||||
import 'package:fluffychat/pages/settings_emotes/settings_emotes.dart';
|
||||
import 'package:fluffychat/utils/client_manager.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
class ImportEmoteArchiveDialog extends StatefulWidget {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import 'package:archive/archive.dart'
|
||||
if (dart.library.io) 'package:archive/archive_io.dart';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
|
@ -11,14 +13,12 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:fluffychat/utils/client_manager.dart';
|
||||
import 'package:fluffychat/utils/file_selector.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'import_archive_dialog.dart';
|
||||
import 'settings_emotes_view.dart';
|
||||
|
||||
import 'package:archive/archive.dart'
|
||||
if (dart.library.io) 'package:archive/archive_io.dart';
|
||||
|
||||
class EmotesSettings extends StatefulWidget {
|
||||
const EmotesSettings({super.key});
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
|||
showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
message: L10n.of(context).emoteExists,
|
||||
title: L10n.of(context).emoteExists,
|
||||
okLabel: L10n.of(context).ok,
|
||||
);
|
||||
return;
|
||||
|
|
@ -147,7 +147,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
|||
showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
message: L10n.of(context).emoteInvalid,
|
||||
title: L10n.of(context).emoteInvalid,
|
||||
okLabel: L10n.of(context).ok,
|
||||
);
|
||||
return;
|
||||
|
|
@ -183,7 +183,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
|||
await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
message: L10n.of(context).emoteWarnNeedToPick,
|
||||
title: L10n.of(context).emoteWarnNeedToPick,
|
||||
okLabel: L10n.of(context).ok,
|
||||
);
|
||||
return;
|
||||
|
|
@ -193,7 +193,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
|||
await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
message: L10n.of(context).emoteExists,
|
||||
title: L10n.of(context).emoteExists,
|
||||
okLabel: L10n.of(context).ok,
|
||||
);
|
||||
return;
|
||||
|
|
@ -202,7 +202,7 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
|||
await showOkAlertDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
message: L10n.of(context).emoteInvalid,
|
||||
title: L10n.of(context).emoteInvalid,
|
||||
okLabel: L10n.of(context).ok,
|
||||
);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class SettingsHomeserverView extends StatelessWidget {
|
|||
if (supportPage != null)
|
||||
ListTile(
|
||||
title: Text(L10n.of(context).supportPage),
|
||||
subtitle: Text(supportPage),
|
||||
subtitle: Text(supportPage.toString()),
|
||||
),
|
||||
if (contacts != null)
|
||||
...contacts.map(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_modal_action_popup.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'settings_notifications_view.dart';
|
||||
|
|
@ -138,15 +138,16 @@ class SettingsNotificationsController extends State<SettingsNotifications> {
|
|||
}
|
||||
|
||||
void onPusherTap(Pusher pusher) async {
|
||||
final delete = await showModalActionSheet<bool>(
|
||||
final delete = await showModalActionPopup<bool>(
|
||||
context: context,
|
||||
title: pusher.deviceDisplayName,
|
||||
message: '${pusher.appDisplayName} (${pusher.appId})',
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
actions: [
|
||||
SheetAction(
|
||||
AdaptiveModalAction(
|
||||
label: L10n.of(context).delete,
|
||||
isDestructiveAction: true,
|
||||
key: true,
|
||||
isDestructive: true,
|
||||
value: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_text_input_dialog.dart';
|
||||
import 'package:fluffychat/widgets/app_lock.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
|
@ -29,25 +30,20 @@ class SettingsSecurityController extends State<SettingsSecurity> {
|
|||
title: L10n.of(context).pleaseChooseAPasscode,
|
||||
message: L10n.of(context).pleaseEnter4Digits,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
validator: (text) {
|
||||
if (text!.isEmpty ||
|
||||
(text.length == 4 && int.tryParse(text)! >= 0)) {
|
||||
return null;
|
||||
}
|
||||
return L10n.of(context).pleaseEnter4Digits;
|
||||
},
|
||||
keyboardType: TextInputType.number,
|
||||
obscureText: true,
|
||||
maxLines: 1,
|
||||
minLines: 1,
|
||||
maxLength: 4,
|
||||
),
|
||||
],
|
||||
validator: (text) {
|
||||
if (text.isEmpty || (text.length == 4 && int.tryParse(text)! >= 0)) {
|
||||
return null;
|
||||
}
|
||||
return L10n.of(context).pleaseEnter4Digits;
|
||||
},
|
||||
keyboardType: TextInputType.number,
|
||||
obscureText: true,
|
||||
maxLines: 1,
|
||||
minLines: 1,
|
||||
maxLength: 4,
|
||||
);
|
||||
if (newLock != null) {
|
||||
await AppLock.of(context).changePincode(newLock.single);
|
||||
await AppLock.of(context).changePincode(newLock);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +78,7 @@ class SettingsSecurityController extends State<SettingsSecurity> {
|
|||
message: L10n.of(context).deactivateAccountWarning,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
isDestructiveAction: true,
|
||||
isDestructive: true,
|
||||
) ==
|
||||
OkCancelResult.cancel) {
|
||||
return;
|
||||
|
|
@ -92,18 +88,14 @@ class SettingsSecurityController extends State<SettingsSecurity> {
|
|||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context).confirmMatrixId,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
validator: (text) => text == supposedMxid
|
||||
? null
|
||||
: L10n.of(context).supposedMxid(supposedMxid),
|
||||
),
|
||||
],
|
||||
isDestructiveAction: true,
|
||||
validator: (text) => text == supposedMxid
|
||||
? null
|
||||
: L10n.of(context).supposedMxid(supposedMxid),
|
||||
isDestructive: true,
|
||||
okLabel: L10n.of(context).delete,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
);
|
||||
if (mxids == null || mxids.length != 1 || mxids.single != supposedMxid) {
|
||||
if (mxids == null || mxids.length != 1 || mxids != supposedMxid) {
|
||||
return;
|
||||
}
|
||||
final input = await showTextInputDialog(
|
||||
|
|
@ -112,22 +104,18 @@ class SettingsSecurityController extends State<SettingsSecurity> {
|
|||
title: L10n.of(context).pleaseEnterYourPassword,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
isDestructiveAction: true,
|
||||
textFields: [
|
||||
const DialogTextField(
|
||||
obscureText: true,
|
||||
hintText: '******',
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
),
|
||||
],
|
||||
isDestructive: true,
|
||||
obscureText: true,
|
||||
hintText: '******',
|
||||
minLines: 1,
|
||||
maxLines: 1,
|
||||
);
|
||||
if (input == null) return;
|
||||
await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => Matrix.of(context).client.deactivateAccount(
|
||||
auth: AuthenticationPassword(
|
||||
password: input.single,
|
||||
password: input,
|
||||
identifier: AuthenticationUserIdentifier(
|
||||
user: Matrix.of(context).client.userID!,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -214,7 +214,9 @@ class SettingsStyleView extends StatelessWidget {
|
|||
),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
color: theme.brightness == Brightness.light
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(
|
||||
AppConfig.borderRadius,
|
||||
),
|
||||
|
|
@ -227,7 +229,11 @@ class SettingsStyleView extends StatelessWidget {
|
|||
child: Text(
|
||||
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor',
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.onPrimary,
|
||||
color:
|
||||
theme.brightness == Brightness.light
|
||||
? theme.colorScheme.onPrimary
|
||||
: theme.colorScheme
|
||||
.onPrimaryContainer,
|
||||
fontSize: AppConfig.messageFontSize *
|
||||
AppConfig.fontSizeFactor,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/permission_slider_dialog.dart';
|
||||
import '../../widgets/matrix.dart';
|
||||
import 'user_bottom_sheet_view.dart';
|
||||
|
||||
enum UserBottomSheetAction {
|
||||
report,
|
||||
// #Pangea
|
||||
// report,
|
||||
// Pangea#
|
||||
mention,
|
||||
ban,
|
||||
kick,
|
||||
|
|
@ -94,55 +96,56 @@ class UserBottomSheetController extends State<UserBottomSheet> {
|
|||
if (userId == null) throw ('user or profile must not be null!');
|
||||
|
||||
switch (action) {
|
||||
case UserBottomSheetAction.report:
|
||||
if (user == null) throw ('User must not be null for this action!');
|
||||
// #Pangea
|
||||
// case UserBottomSheetAction.report:
|
||||
// if (user == null) throw ('User must not be null for this action!');
|
||||
|
||||
final score = await showConfirmationDialog<int>(
|
||||
context: context,
|
||||
title: L10n.of(context).reportUser,
|
||||
message: L10n.of(context).howOffensiveIsThisContent,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
okLabel: L10n.of(context).ok,
|
||||
actions: [
|
||||
AlertDialogAction(
|
||||
key: -100,
|
||||
label: L10n.of(context).extremeOffensive,
|
||||
),
|
||||
AlertDialogAction(
|
||||
key: -50,
|
||||
label: L10n.of(context).offensive,
|
||||
),
|
||||
AlertDialogAction(
|
||||
key: 0,
|
||||
label: L10n.of(context).inoffensive,
|
||||
),
|
||||
],
|
||||
);
|
||||
if (score == null) return;
|
||||
final reason = await showTextInputDialog(
|
||||
useRootNavigator: false,
|
||||
context: context,
|
||||
title: L10n.of(context).whyDoYouWantToReportThis,
|
||||
okLabel: L10n.of(context).ok,
|
||||
cancelLabel: L10n.of(context).cancel,
|
||||
textFields: [DialogTextField(hintText: L10n.of(context).reason)],
|
||||
);
|
||||
if (reason == null || reason.single.isEmpty) return;
|
||||
// final score = await showModalActionPopup<int>(
|
||||
// context: context,
|
||||
// title: L10n.of(context).reportUser,
|
||||
// message: L10n.of(context).howOffensiveIsThisContent,
|
||||
// cancelLabel: L10n.of(context).cancel,
|
||||
// actions: [
|
||||
// AdaptiveModalAction(
|
||||
// value: -100,
|
||||
// label: L10n.of(context).extremeOffensive,
|
||||
// ),
|
||||
// AdaptiveModalAction(
|
||||
// value: -50,
|
||||
// label: L10n.of(context).offensive,
|
||||
// ),
|
||||
// AdaptiveModalAction(
|
||||
// value: 0,
|
||||
// label: L10n.of(context).inoffensive,
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// if (score == null) return;
|
||||
// final reason = await showTextInputDialog(
|
||||
// useRootNavigator: false,
|
||||
// context: context,
|
||||
// title: L10n.of(context).whyDoYouWantToReportThis,
|
||||
// okLabel: L10n.of(context).ok,
|
||||
// cancelLabel: L10n.of(context).cancel,
|
||||
// hintText: L10n.of(context).reason,
|
||||
// );
|
||||
// if (reason == null || reason.isEmpty) return;
|
||||
|
||||
final result = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () => Matrix.of(widget.outerContext).client.reportContent(
|
||||
user.room.id,
|
||||
user.id,
|
||||
reason: reason.single,
|
||||
score: score,
|
||||
),
|
||||
);
|
||||
if (result.error != null) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(L10n.of(context).contentHasBeenReported)),
|
||||
);
|
||||
break;
|
||||
// final result = await showFutureLoadingDialog(
|
||||
// context: context,
|
||||
// future: () => Matrix.of(widget.outerContext).client.reportEvent(
|
||||
// user.room.id,
|
||||
// user.id,
|
||||
// reason: reason,
|
||||
// score: score,
|
||||
// ),
|
||||
// );
|
||||
// if (result.error != null) return;
|
||||
// ScaffoldMessenger.of(context).showSnackBar(
|
||||
// SnackBar(content: Text(L10n.of(context).contentHasBeenReported)),
|
||||
// );
|
||||
// break;
|
||||
// Pangea#
|
||||
case UserBottomSheetAction.mention:
|
||||
if (user == null) throw ('User must not be null for this action!');
|
||||
Navigator.of(context).pop();
|
||||
|
|
@ -237,50 +240,10 @@ class UserBottomSheetController extends State<UserBottomSheet> {
|
|||
}
|
||||
}
|
||||
|
||||
bool isSending = false;
|
||||
|
||||
Object? sendError;
|
||||
|
||||
final TextEditingController sendController = TextEditingController();
|
||||
|
||||
void sendAction([_]) async {
|
||||
final userId = widget.user?.id ?? widget.profile?.userId;
|
||||
final client = Matrix.of(widget.outerContext).client;
|
||||
if (userId == null) throw ('user or profile must not be null!');
|
||||
|
||||
final input = sendController.text.trim();
|
||||
if (input.isEmpty) return;
|
||||
|
||||
setState(() {
|
||||
isSending = true;
|
||||
sendError = null;
|
||||
});
|
||||
try {
|
||||
final roomId = await client.startDirectChat(
|
||||
userId,
|
||||
// #Pangea
|
||||
enableEncryption: false,
|
||||
// Pangea#
|
||||
);
|
||||
if (!mounted) return;
|
||||
final room = client.getRoomById(roomId);
|
||||
if (room == null) {
|
||||
throw ('DM Room found or created but room not found in client');
|
||||
}
|
||||
await room.sendTextEvent(input);
|
||||
setState(() {
|
||||
isSending = false;
|
||||
sendController.clear();
|
||||
});
|
||||
} catch (e, s) {
|
||||
Logs().d('Unable to send message', e, s);
|
||||
setState(() {
|
||||
isSending = false;
|
||||
sendError = e;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void knockAccept() async {
|
||||
final user = widget.user!;
|
||||
final result = await showFutureLoadingDialog(
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue