mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2026-03-09 13:47:55 +01:00
Compare commits
33 Commits
rc1.12.0-1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4fcb5b0d9 | ||
|
|
1911004d05 | ||
|
|
5d67564445 | ||
|
|
be04c5a46e | ||
|
|
bd7a4c9dfb | ||
|
|
10ee57722e | ||
|
|
ff5f7ab50e | ||
|
|
277885a61e | ||
|
|
6633ebc376 | ||
|
|
b2d9986cd3 | ||
|
|
a0b9bb277f | ||
|
|
d381705cdd | ||
| 3820d4264a | |||
|
|
cf4e2d3fad | ||
|
|
002dc87577 | ||
|
|
922e7ad0ff | ||
|
|
5d7be8a672 | ||
|
|
431b357cfa | ||
|
|
2938acf152 | ||
|
|
4127f70e4d | ||
|
|
c07221cc12 | ||
|
|
33b2f95e3f | ||
|
|
2a5cd9b218 | ||
|
|
a15fed034d | ||
|
|
b9641ac021 | ||
|
|
2145bd8846 | ||
|
|
672b97b310 | ||
|
|
974da6ec90 | ||
|
|
f19bbcd010 | ||
|
|
a1468c92c8 | ||
|
|
6dd125a087 | ||
|
|
842ecc4235 | ||
|
|
db66793d28 |
@ -372,6 +372,7 @@ upload_linux_arm64:
|
|||||||
- tar czf package.tar.gz -C build/linux/arm64/release/bundle/ .
|
- tar czf package.tar.gz -C build/linux/arm64/release/bundle/ .
|
||||||
- |
|
- |
|
||||||
curl --fail-with-body --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file package.tar.gz ${PACKAGE_REGISTRY_URL}/fluffychat-linux-arm64.tar.gz
|
curl --fail-with-body --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file package.tar.gz ${PACKAGE_REGISTRY_URL}/fluffychat-linux-arm64.tar.gz
|
||||||
|
allow_failure: true
|
||||||
|
|
||||||
upload_windows:
|
upload_windows:
|
||||||
extends: .release
|
extends: .release
|
||||||
@ -383,6 +384,7 @@ upload_windows:
|
|||||||
- |
|
- |
|
||||||
curl --fail-with-body --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file package.zip ${PACKAGE_REGISTRY_URL}/fluffychat-windows.zip
|
curl --fail-with-body --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file package.zip ${PACKAGE_REGISTRY_URL}/fluffychat-windows.zip
|
||||||
curl --fail-with-body --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file fluffychat.msix ${PACKAGE_REGISTRY_URL}/fluffychat-windows.msix
|
curl --fail-with-body --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file fluffychat.msix ${PACKAGE_REGISTRY_URL}/fluffychat-windows.msix
|
||||||
|
allow_failure: true
|
||||||
|
|
||||||
deploy_playstore:
|
deploy_playstore:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 44 KiB |
@ -2252,9 +2252,9 @@
|
|||||||
"@hydrateTor": {},
|
"@hydrateTor": {},
|
||||||
"commandHint_googly": "أرسل بعض عيون googly",
|
"commandHint_googly": "أرسل بعض عيون googly",
|
||||||
"@commandHint_googly": {},
|
"@commandHint_googly": {},
|
||||||
"commandHint_cuddle": "إرسال عناق",
|
"commandHint_cuddle": "أرسل عناق",
|
||||||
"@commandHint_cuddle": {},
|
"@commandHint_cuddle": {},
|
||||||
"commandHint_hug": "إرسال عناق",
|
"commandHint_hug": "إرسال حضن",
|
||||||
"@commandHint_hug": {},
|
"@commandHint_hug": {},
|
||||||
"cuddleContent": "{senderName} يحتضنك",
|
"cuddleContent": "{senderName} يحتضنك",
|
||||||
"@cuddleContent": {
|
"@cuddleContent": {
|
||||||
@ -2504,5 +2504,11 @@
|
|||||||
"jumpToLastReadMessage": "الانتقال إلى آخر رسالة مقروءة",
|
"jumpToLastReadMessage": "الانتقال إلى آخر رسالة مقروءة",
|
||||||
"@jumpToLastReadMessage": {},
|
"@jumpToLastReadMessage": {},
|
||||||
"readUpToHere": "اقرأ حتى هنا",
|
"readUpToHere": "اقرأ حتى هنا",
|
||||||
"@readUpToHere": {}
|
"@readUpToHere": {},
|
||||||
|
"signInWithPassword": "سجل الدخول بكلمة السر",
|
||||||
|
"@signInWithPassword": {},
|
||||||
|
"continueWith": "أكمل ب:",
|
||||||
|
"@continueWith": {},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "رجاء حاول مجددا أو اختر خادما مختلفا.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {}
|
||||||
}
|
}
|
||||||
|
|||||||
1
assets/l10n/intl_el.arb
Normal file
1
assets/l10n/intl_el.arb
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@ -2473,5 +2473,8 @@
|
|||||||
"jump": "Jump",
|
"jump": "Jump",
|
||||||
"openLinkInBrowser": "Open link in browser",
|
"openLinkInBrowser": "Open link in browser",
|
||||||
"reportErrorDescription": "Oh no. Something went wrong. Please try again later. If you want, you can report the bug to the developers.",
|
"reportErrorDescription": "Oh no. Something went wrong. Please try again later. If you want, you can report the bug to the developers.",
|
||||||
"report": "report"
|
"report": "report",
|
||||||
|
"signInWithPassword": "Sign in with password",
|
||||||
|
"continueWith": "Continue with:",
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "Please try again later or choose a different server."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2504,5 +2504,11 @@
|
|||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"reportErrorDescription": "Oh appike! Midagi läks valesti. Palun proovi hiljem uuesti. Kui soovid, võid sellest veast arendajatele teatada.",
|
"reportErrorDescription": "Oh appike! Midagi läks valesti. Palun proovi hiljem uuesti. Kui soovid, võid sellest veast arendajatele teatada.",
|
||||||
"@reportErrorDescription": {}
|
"@reportErrorDescription": {},
|
||||||
|
"continueWith": "Jätkamiseks kasuta:",
|
||||||
|
"@continueWith": {},
|
||||||
|
"signInWithPassword": "Logi sisse salasõnaga",
|
||||||
|
"@signInWithPassword": {},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "Palun proovi hiljem uuesti või muuda serveri nime.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -266,12 +266,12 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"compareEmojiMatch": "Konparatu eta egiaztatu ondorengo emojiak beste gailukoarekin bat datozela:",
|
"compareEmojiMatch": "Konparatu ondorengo emojiak:",
|
||||||
"@compareEmojiMatch": {
|
"@compareEmojiMatch": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"compareNumbersMatch": "Konparatu eta egiaztatu ondorengo zenbakiak beste gailukoarekin bat datozela:",
|
"compareNumbersMatch": "Konparatu ondorengo zenbakiak:",
|
||||||
"@compareNumbersMatch": {
|
"@compareNumbersMatch": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -484,12 +484,12 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"fromJoining": "sartzeaz",
|
"fromJoining": "Bat egiteaz geroztik",
|
||||||
"@fromJoining": {
|
"@fromJoining": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"fromTheInvitation": "gonbidapenaz",
|
"fromTheInvitation": "Gonbidapenaz geroztik",
|
||||||
"@fromTheInvitation": {
|
"@fromTheInvitation": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -1470,7 +1470,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"unreadChats": "{unreadCount, plural, =1{irakurri gabeko txat 1} other {{unreadCount} txat irakurri gabe}}",
|
"unreadChats": "{unreadCount, plural, =1{irakurri gabeko txat 1} other {irakurri gabeko {unreadCount} txat}}",
|
||||||
"@unreadChats": {
|
"@unreadChats": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@ -2000,7 +2000,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"noEncryptionForPublicRooms": "Zifraketa aktiba dezakezu soilik gela publikoa ez bada.",
|
"noEncryptionForPublicRooms": "Zifraketa aktiba dezakezu soilik gelak publikoa izateari utzi badio.",
|
||||||
"@noEncryptionForPublicRooms": {
|
"@noEncryptionForPublicRooms": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -2232,7 +2232,7 @@
|
|||||||
"@widgetEtherpad": {},
|
"@widgetEtherpad": {},
|
||||||
"widgetJitsi": "Jitsi Meet",
|
"widgetJitsi": "Jitsi Meet",
|
||||||
"@widgetJitsi": {},
|
"@widgetJitsi": {},
|
||||||
"widgetCustom": "Neurrira egindakoa",
|
"widgetCustom": "Norberak ezarritakoa",
|
||||||
"@widgetCustom": {},
|
"@widgetCustom": {},
|
||||||
"widgetName": "Izena",
|
"widgetName": "Izena",
|
||||||
"@widgetName": {},
|
"@widgetName": {},
|
||||||
@ -2430,7 +2430,7 @@
|
|||||||
"@startFirstChat": {},
|
"@startFirstChat": {},
|
||||||
"newSpaceDescription": "Guneek txatak taldekatzea ahalbidetzen dute eta komunitate pribatu edo publikoak osatzea.",
|
"newSpaceDescription": "Guneek txatak taldekatzea ahalbidetzen dute eta komunitate pribatu edo publikoak osatzea.",
|
||||||
"@newSpaceDescription": {},
|
"@newSpaceDescription": {},
|
||||||
"endToEndEncryption": "Puntuz puntuko zifraketa",
|
"endToEndEncryption": "Ertzetik ertzerako zifratzea",
|
||||||
"@endToEndEncryption": {},
|
"@endToEndEncryption": {},
|
||||||
"disableEncryptionWarning": "Segurtasun arrazoiak direla-eta, ezin duzu lehendik zifratuta zegoen txat bateko zifraketa ezgaitu.",
|
"disableEncryptionWarning": "Segurtasun arrazoiak direla-eta, ezin duzu lehendik zifratuta zegoen txat bateko zifraketa ezgaitu.",
|
||||||
"@disableEncryptionWarning": {},
|
"@disableEncryptionWarning": {},
|
||||||
@ -2450,5 +2450,58 @@
|
|||||||
"sorryThatsNotPossible": "Barka… hori ez da posible",
|
"sorryThatsNotPossible": "Barka… hori ez da posible",
|
||||||
"@sorryThatsNotPossible": {},
|
"@sorryThatsNotPossible": {},
|
||||||
"reopenChat": "Ireki txata berriro",
|
"reopenChat": "Ireki txata berriro",
|
||||||
"@reopenChat": {}
|
"@reopenChat": {},
|
||||||
|
"commandHint_googly": "Bidali begi dibertigarri batzuk",
|
||||||
|
"@commandHint_googly": {},
|
||||||
|
"commandHint_cuddle": "Bidali besarkada goxoa",
|
||||||
|
"@commandHint_cuddle": {},
|
||||||
|
"googlyEyesContent": "{senderName}(e)k begi dibertigarri batzuk bidali dizkizu",
|
||||||
|
"@googlyEyesContent": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {
|
||||||
|
"senderName": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"allRooms": "Talde-txat guztiak",
|
||||||
|
"@allRooms": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"jumpToLastReadMessage": "Joan irakurritako azken mezura",
|
||||||
|
"@jumpToLastReadMessage": {},
|
||||||
|
"reportErrorDescription": "Ez! Zerbaitek huts egin du. Saiatu berriro geroago. Nahi izanez gero, eman garatzaileei errorearen berri.",
|
||||||
|
"@reportErrorDescription": {},
|
||||||
|
"cuddleContent": "{senderName}(e)k samurki besarkatu zaitu",
|
||||||
|
"@cuddleContent": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {
|
||||||
|
"senderName": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"readUpToHere": "Honaino irakurrita",
|
||||||
|
"@readUpToHere": {},
|
||||||
|
"discover": "Deskubritu",
|
||||||
|
"@discover": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"fileHasBeenSavedAt": "Fitxategia {path}(e)n gorde da",
|
||||||
|
"@fileHasBeenSavedAt": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {
|
||||||
|
"path": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jump": "Joan",
|
||||||
|
"@jump": {},
|
||||||
|
"openLinkInBrowser": "Ireki esteka nabigatzailean",
|
||||||
|
"@openLinkInBrowser": {},
|
||||||
|
"report": "eman berri",
|
||||||
|
"@report": {},
|
||||||
|
"signInWithPassword": "Hasi saioa pasahitzarekin",
|
||||||
|
"@signInWithPassword": {},
|
||||||
|
"continueWith": "Jarraitu honekin:",
|
||||||
|
"@continueWith": {},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "Saiatu geroago edo hautatu beste zerbitzari bat.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {}
|
||||||
}
|
}
|
||||||
@ -2480,5 +2480,27 @@
|
|||||||
"storeInSecureStorageDescription": "کلید بازیابی را در محل ذخیرهسازی امن این دستگاه ذخیره کنید.",
|
"storeInSecureStorageDescription": "کلید بازیابی را در محل ذخیرهسازی امن این دستگاه ذخیره کنید.",
|
||||||
"@storeInSecureStorageDescription": {},
|
"@storeInSecureStorageDescription": {},
|
||||||
"jump": "پرش",
|
"jump": "پرش",
|
||||||
"@jump": {}
|
"@jump": {},
|
||||||
|
"discover": "کشف کنید",
|
||||||
|
"@discover": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"allRooms": "تمام چتهای گروهی",
|
||||||
|
"@allRooms": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"report": "گزارش",
|
||||||
|
"@report": {},
|
||||||
|
"openLinkInBrowser": "بازکردن پیوند در مرورگر",
|
||||||
|
"@openLinkInBrowser": {},
|
||||||
|
"reportErrorDescription": "اوه نه. اشتباهی رخ داد. لطفا بعدا تلاش کنید. اگر تمایل دارید، میتوانید این اشکال را با توسعهدهندگان گزارش دهید.",
|
||||||
|
"@reportErrorDescription": {},
|
||||||
|
"signInWithPassword": "ورود با رمزعبور",
|
||||||
|
"@signInWithPassword": {},
|
||||||
|
"continueWith": "ادامه دادن با:",
|
||||||
|
"@continueWith": {},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "لطفا بعدا تلاش کنید یا سرور دیگری انتخاب کنید.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {}
|
||||||
}
|
}
|
||||||
@ -1047,7 +1047,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"description": "Usage hint for the command /me"
|
"description": "Usage hint for the command /me"
|
||||||
},
|
},
|
||||||
"compareEmojiMatch": "Vertaile ja varmista emojien olevan samat molemmilla laitteilla:",
|
"compareEmojiMatch": "Vertaa hymiöitä",
|
||||||
"@compareEmojiMatch": {
|
"@compareEmojiMatch": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -1111,7 +1111,7 @@
|
|||||||
"@commandInvalid": {
|
"@commandInvalid": {
|
||||||
"type": "text"
|
"type": "text"
|
||||||
},
|
},
|
||||||
"compareNumbersMatch": "Vertaile ja varmista numeroiden olevan samat molemmilla laitteilla:",
|
"compareNumbersMatch": "Vertaa numeroita",
|
||||||
"@compareNumbersMatch": {
|
"@compareNumbersMatch": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -2364,7 +2364,7 @@
|
|||||||
"@commandHint_markasgroup": {},
|
"@commandHint_markasgroup": {},
|
||||||
"whyIsThisMessageEncrypted": "Miksei tätä viestiä voida lukea?",
|
"whyIsThisMessageEncrypted": "Miksei tätä viestiä voida lukea?",
|
||||||
"@whyIsThisMessageEncrypted": {},
|
"@whyIsThisMessageEncrypted": {},
|
||||||
"noKeyForThisMessage": "Tämä voi tapahtua mikäli viesti lähetettiin ennen sisäänkirjautumistasi tälle laitteelle.\n\nOn myös mahdollista, että lähettäjä on estänyt tämän laitteen tai jokin meni pieleen verkkoyhteyden kanssa.\n\nPystytkö lukemaan viestin toisella istunnolla? Siinä tapauksessa voit siirtää viestin siltä! Mene Asetukset > Laitteet ja varmista, että laitteesi ovat varmistaneet toisensa. Seuraavankerran avatessasi laitteen ja molempien istuntojen ollessa etualalla, avaimet siirretään automaattisesti.\n\nHaluatko varmistaa ettet menetä avaimia uloskirjautuessa tai laitteita vaihtaessa? Varmista avainvarmuuskopion käytössäolo asetuksista.",
|
"noKeyForThisMessage": "Tämä voi tapahtua mikäli viesti lähetettiin ennen sisäänkirjautumistasi tälle laitteelle.\n\nOn myös mahdollista, että lähettäjä on estänyt tämän laitteen tai jokin meni pieleen verkkoyhteyden kanssa.\n\nPystytkö lukemaan viestin toisella istunnolla? Siinä tapauksessa voit siirtää viestin siltä! Mene Asetukset > Laitteet ja varmista, että laitteesi ovat varmistaneet toisensa. Seuraavankerran avatessasi huoneen ja molempien istuntojen ollessa etualalla, avaimet siirretään automaattisesti.\n\nHaluatko varmistaa ettet menetä avaimia uloskirjautuessa tai laitteita vaihtaessa? Varmista avainvarmuuskopion käytössäolo asetuksista.",
|
||||||
"@noKeyForThisMessage": {},
|
"@noKeyForThisMessage": {},
|
||||||
"commandHint_markasdm": "Merkitse yksityiskeskusteluksi",
|
"commandHint_markasdm": "Merkitse yksityiskeskusteluksi",
|
||||||
"@commandHint_markasdm": {},
|
"@commandHint_markasdm": {},
|
||||||
@ -2433,5 +2433,73 @@
|
|||||||
"hideUnimportantStateEvents": "Piilota ei-niin-tärkeät tilatapahtumat",
|
"hideUnimportantStateEvents": "Piilota ei-niin-tärkeät tilatapahtumat",
|
||||||
"@hideUnimportantStateEvents": {},
|
"@hideUnimportantStateEvents": {},
|
||||||
"doNotShowAgain": "Älä näytä uudelleen",
|
"doNotShowAgain": "Älä näytä uudelleen",
|
||||||
"@doNotShowAgain": {}
|
"@doNotShowAgain": {},
|
||||||
|
"fileHasBeenSavedAt": "Tiedosto on tallennettu sijaintiin {path}",
|
||||||
|
"@fileHasBeenSavedAt": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {
|
||||||
|
"path": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"disableEncryptionWarning": "Turvallisuuden vuoksi et voi poistaa salausta käytöstä huoneista, joissa se on aiemmin otettu käyttöön.",
|
||||||
|
"@disableEncryptionWarning": {},
|
||||||
|
"allRooms": "Kaikki ryhmäkeskustelut",
|
||||||
|
"@allRooms": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"startFirstChat": "Aloita ensimmäinen keskustelusi",
|
||||||
|
"@startFirstChat": {},
|
||||||
|
"newSpaceDescription": "Tilat mahdollistavat keskusteluidesi keräämisen ja yksityisten tai julkisten yhteisöjen rakentamisen.",
|
||||||
|
"@newSpaceDescription": {},
|
||||||
|
"deviceKeys": "Laite-avaimet:",
|
||||||
|
"@deviceKeys": {},
|
||||||
|
"letsStart": "Aloitetaan",
|
||||||
|
"@letsStart": {},
|
||||||
|
"enterInviteLinkOrMatrixId": "Syötä kutsulinkki tai Matrix ID...",
|
||||||
|
"@enterInviteLinkOrMatrixId": {},
|
||||||
|
"reopenChat": "Avaa keskustelu uudelleen",
|
||||||
|
"@reopenChat": {},
|
||||||
|
"noOtherDevicesFound": "Muita laitteita ei löytynyt",
|
||||||
|
"@noOtherDevicesFound": {},
|
||||||
|
"jumpToLastReadMessage": "Hyppää viimeiseen luettuun viestiin",
|
||||||
|
"@jumpToLastReadMessage": {},
|
||||||
|
"readUpToHere": "Luettu tähän asti",
|
||||||
|
"@readUpToHere": {},
|
||||||
|
"jump": "Hyppää",
|
||||||
|
"@jump": {},
|
||||||
|
"openLinkInBrowser": "Avaa linkki selaimessa",
|
||||||
|
"@openLinkInBrowser": {},
|
||||||
|
"report": "ilmoita",
|
||||||
|
"@report": {},
|
||||||
|
"encryptThisChat": "Salaa tämä keskustelu",
|
||||||
|
"@encryptThisChat": {},
|
||||||
|
"endToEndEncryption": "Päästä-päähän salaus",
|
||||||
|
"@endToEndEncryption": {},
|
||||||
|
"discover": "Löydä",
|
||||||
|
"@discover": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"noBackupWarning": "Varoitus! Ilman avainvarmuuskopion käyttöönottoa menetät pääsyn salattuihin viesteihisi. Suosittelemme ehdottomasti avainvarmuuskopion käyttöönottoa ennen uloskirjautumista.",
|
||||||
|
"@noBackupWarning": {},
|
||||||
|
"fileIsTooBigForServer": "Palvelimen mukaan tiedosto on liian suuri lähetettäväksi.",
|
||||||
|
"@fileIsTooBigForServer": {},
|
||||||
|
"reportErrorDescription": "Voi ei. Jokin meni pieleen. Yritäthän myöhemmin uudelleen. Halutessasi voit ilmoittaa ongelman kehittäjille.",
|
||||||
|
"@reportErrorDescription": {},
|
||||||
|
"wasDirectChatDisplayName": "Tyhjä keskustelu (oli {oldDisplayName})",
|
||||||
|
"@wasDirectChatDisplayName": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {
|
||||||
|
"oldDisplayName": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sorryThatsNotPossible": "Anteeksi... se ei ole mahdollista",
|
||||||
|
"@sorryThatsNotPossible": {},
|
||||||
|
"signInWithPassword": "Kirjaudu sisään salasanalla",
|
||||||
|
"@signInWithPassword": {},
|
||||||
|
"continueWith": "Jatka käyttäen:",
|
||||||
|
"@continueWith": {},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "Yritä myöhemmin uudelleen tai valitse toinen palvelin.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {}
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"activatedEndToEndEncryption": "🔐 {username} activou o cifrado extremo-a-extremo",
|
"activatedEndToEndEncryption": "🔐 {username} activou a cifraxe extremo-a-extremo",
|
||||||
"@activatedEndToEndEncryption": {
|
"@activatedEndToEndEncryption": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
@ -314,7 +314,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"channelCorruptedDecryptError": "O cifrado está corrompido",
|
"channelCorruptedDecryptError": "A cifraxe está estragada",
|
||||||
"@channelCorruptedDecryptError": {
|
"@channelCorruptedDecryptError": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -704,12 +704,12 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"enableEncryption": "Activar cifrado",
|
"enableEncryption": "Activar cifraxe",
|
||||||
"@enableEncryption": {
|
"@enableEncryption": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"enableEncryptionWarning": "Non poderás desactivar o cifrado posteriormente, tes certeza?",
|
"enableEncryptionWarning": "Non poderás desactivar a cifraxe posteriormente, tes certeza?",
|
||||||
"@enableEncryptionWarning": {
|
"@enableEncryptionWarning": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -719,12 +719,12 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"encryption": "Cifrado",
|
"encryption": "Cifraxe",
|
||||||
"@encryption": {
|
"@encryption": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"encryptionNotEnabled": "O cifrado non está activado",
|
"encryptionNotEnabled": "A cifraxe non está activada",
|
||||||
"@encryptionNotEnabled": {
|
"@encryptionNotEnabled": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -1119,7 +1119,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"needPantalaimonWarning": "Ten en conta que polo de agora precisas Pantalaimon para o cifrado extremo-a-extremo.",
|
"needPantalaimonWarning": "Ten en conta que polo de agora precisas Pantalaimon para a cifraxe extremo-a-extremo.",
|
||||||
"@needPantalaimonWarning": {
|
"@needPantalaimonWarning": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -1159,7 +1159,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"noEncryptionForPublicRooms": "Só podes activar o cifrado tan pronto como a sala non sexa públicamente accesible.",
|
"noEncryptionForPublicRooms": "Só podes activar a cifraxe tan pronto como a sala non sexa públicamente accesible.",
|
||||||
"@noEncryptionForPublicRooms": {
|
"@noEncryptionForPublicRooms": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -1824,7 +1824,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"unknownEncryptionAlgorithm": "Algoritmo de cifrado descoñecido",
|
"unknownEncryptionAlgorithm": "Algoritmo de cifraxe descoñecido",
|
||||||
"@unknownEncryptionAlgorithm": {
|
"@unknownEncryptionAlgorithm": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
@ -2103,7 +2103,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"description": "Usage hint for the command /discardsession"
|
"description": "Usage hint for the command /discardsession"
|
||||||
},
|
},
|
||||||
"commandHint_create": "Crear un grupo de conversa baleiro\nUsa --no-encryption para desactivar o cifrado",
|
"commandHint_create": "Crear un grupo de conversa baleiro\nUsa --no-encryption para desactivar a cifraxe",
|
||||||
"@commandHint_create": {
|
"@commandHint_create": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"description": "Usage hint for the command /create"
|
"description": "Usage hint for the command /create"
|
||||||
@ -2113,7 +2113,7 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"description": "Usage hint for the command /clearcache"
|
"description": "Usage hint for the command /clearcache"
|
||||||
},
|
},
|
||||||
"commandHint_dm": "Iniciar un chat directo\nUsa --no-encryption para desactivar o cifrado",
|
"commandHint_dm": "Iniciar un chat directo\nUsa --no-encryption para desactivar a cifraxe",
|
||||||
"@commandHint_dm": {
|
"@commandHint_dm": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"description": "Usage hint for the command /dm"
|
"description": "Usage hint for the command /dm"
|
||||||
@ -2447,9 +2447,9 @@
|
|||||||
"@enterInviteLinkOrMatrixId": {},
|
"@enterInviteLinkOrMatrixId": {},
|
||||||
"encryptThisChat": "Cifrar esta conversa",
|
"encryptThisChat": "Cifrar esta conversa",
|
||||||
"@encryptThisChat": {},
|
"@encryptThisChat": {},
|
||||||
"endToEndEncryption": "Cifrado de extremo a extremo",
|
"endToEndEncryption": "Cifraxe de extremo a extremo",
|
||||||
"@endToEndEncryption": {},
|
"@endToEndEncryption": {},
|
||||||
"disableEncryptionWarning": "Por razóns de seguridade non podes desactivar o cifrado dunha conversa onde foi activado previamente.",
|
"disableEncryptionWarning": "Por razóns de seguridade non podes desactivar a cifraxe dunha conversa onde foi activada previamente.",
|
||||||
"@disableEncryptionWarning": {},
|
"@disableEncryptionWarning": {},
|
||||||
"sorryThatsNotPossible": "Lamentámolo... iso non é posible",
|
"sorryThatsNotPossible": "Lamentámolo... iso non é posible",
|
||||||
"@sorryThatsNotPossible": {},
|
"@sorryThatsNotPossible": {},
|
||||||
@ -2504,5 +2504,11 @@
|
|||||||
"@discover": {
|
"@discover": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
}
|
},
|
||||||
|
"signInWithPassword": "Accede con contrasinal",
|
||||||
|
"@signInWithPassword": {},
|
||||||
|
"continueWith": "Continuar con:",
|
||||||
|
"@continueWith": {},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "Inténtao máis tarde ou elixe un servidor diferente.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2503,6 +2503,12 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"reportErrorDescription": "Dogodila se greška. Pokušaj ponovo kasnije. Ako želiš grešku možeš prijaviti programerima.",
|
"reportErrorDescription": "Dogodila se greška. Pokušaj ponovo kasnije. Ako želiš, grešku možeš prijaviti programerima.",
|
||||||
"@reportErrorDescription": {}
|
"@reportErrorDescription": {},
|
||||||
|
"signInWithPassword": "Prijavi se s lozinkom",
|
||||||
|
"@signInWithPassword": {},
|
||||||
|
"continueWith": "Nastavi sa:",
|
||||||
|
"@continueWith": {},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "Pokušaj ponovo kasnije ili odaberi jedan drugi poslužitelj.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2503,5 +2503,11 @@
|
|||||||
"report": "laporkan",
|
"report": "laporkan",
|
||||||
"@report": {},
|
"@report": {},
|
||||||
"reportErrorDescription": "Aduh. Ada yang salah. Silakan coba lahi nanti. Jika kamu mau, kamu bisa melaporkan kutu ini kepada para pengembang.",
|
"reportErrorDescription": "Aduh. Ada yang salah. Silakan coba lahi nanti. Jika kamu mau, kamu bisa melaporkan kutu ini kepada para pengembang.",
|
||||||
"@reportErrorDescription": {}
|
"@reportErrorDescription": {},
|
||||||
|
"signInWithPassword": "Masuk dengan kata sandi",
|
||||||
|
"@signInWithPassword": {},
|
||||||
|
"continueWith": "Lanjutkan dengan:",
|
||||||
|
"@continueWith": {},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "Silakan coba lagi nanti atau pilih server yang lain.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2496,5 +2496,11 @@
|
|||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
"reopenChat": "Deschide din nou chatul",
|
"reopenChat": "Deschide din nou chatul",
|
||||||
"@reopenChat": {}
|
"@reopenChat": {},
|
||||||
|
"continueWith": "Continuați cu:",
|
||||||
|
"@continueWith": {},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "Vă rugăm să încercați din nou mai târziu sau să alegeți un server diferit.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {},
|
||||||
|
"signInWithPassword": "Conectați-vă cu parolă",
|
||||||
|
"@signInWithPassword": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2490,5 +2490,25 @@
|
|||||||
"jump": "Atla",
|
"jump": "Atla",
|
||||||
"@jump": {},
|
"@jump": {},
|
||||||
"openLinkInBrowser": "Bağlantıyı tarayıcıda aç",
|
"openLinkInBrowser": "Bağlantıyı tarayıcıda aç",
|
||||||
"@openLinkInBrowser": {}
|
"@openLinkInBrowser": {},
|
||||||
|
"allRooms": "Tüm Grup Sohbetleri",
|
||||||
|
"@allRooms": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"discover": "Keşfet",
|
||||||
|
"@discover": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"reportErrorDescription": "Olamaz. Bir şeyler yanlış gitti. Lütfen daha sonra tekrar deneyin. İsterseniz hatayı geliştiricilere bildirebilirsiniz.",
|
||||||
|
"@reportErrorDescription": {},
|
||||||
|
"report": "bildir",
|
||||||
|
"@report": {},
|
||||||
|
"signInWithPassword": "Parola ile oturum aç",
|
||||||
|
"@signInWithPassword": {},
|
||||||
|
"continueWith": "Devam et:",
|
||||||
|
"@continueWith": {},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "Lütfen daha sonra tekrar deneyin veya farklı bir sunucu seçin.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {}
|
||||||
}
|
}
|
||||||
@ -2504,5 +2504,11 @@
|
|||||||
"@discover": {
|
"@discover": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
}
|
},
|
||||||
|
"pleaseTryAgainLaterOrChooseDifferentServer": "Спробуйте пізніше або виберіть інший сервер.",
|
||||||
|
"@pleaseTryAgainLaterOrChooseDifferentServer": {},
|
||||||
|
"signInWithPassword": "Увійти за допомогою пароля",
|
||||||
|
"@signInWithPassword": {},
|
||||||
|
"continueWith": "Продовжити за допомогою:",
|
||||||
|
"@continueWith": {}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 146 KiB |
@ -55,7 +55,7 @@
|
|||||||
<div class="flex mb-8 justify-center content-center">
|
<div class="flex mb-8 justify-center content-center">
|
||||||
<a rel="me"
|
<a rel="me"
|
||||||
class="inline-block text-indigo-500 no-underline hover:text-indigo-900 hover:scale-105 transition-all text-center h-auto p-4"
|
class="inline-block text-indigo-500 no-underline hover:text-indigo-900 hover:scale-105 transition-all text-center h-auto p-4"
|
||||||
rel="me" href="https://metalhead.club/@krille">
|
rel="me" href="https://mastodon.art/@krille">
|
||||||
<svg class="fill-current h-6" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
<svg class="fill-current h-6" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"
|
xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"
|
||||||
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import 'package:fluffychat/pages/chat_details/chat_details.dart';
|
|||||||
import 'package:fluffychat/pages/chat_encryption_settings/chat_encryption_settings.dart';
|
import 'package:fluffychat/pages/chat_encryption_settings/chat_encryption_settings.dart';
|
||||||
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
import 'package:fluffychat/pages/chat_list/chat_list.dart';
|
||||||
import 'package:fluffychat/pages/chat_permissions_settings/chat_permissions_settings.dart';
|
import 'package:fluffychat/pages/chat_permissions_settings/chat_permissions_settings.dart';
|
||||||
import 'package:fluffychat/pages/connect/connect_page.dart';
|
|
||||||
import 'package:fluffychat/pages/device_settings/device_settings.dart';
|
import 'package:fluffychat/pages/device_settings/device_settings.dart';
|
||||||
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart';
|
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart';
|
||||||
import 'package:fluffychat/pages/invitation_selection/invitation_selection.dart';
|
import 'package:fluffychat/pages/invitation_selection/invitation_selection.dart';
|
||||||
@ -27,7 +26,6 @@ import 'package:fluffychat/pages/settings_notifications/settings_notifications.d
|
|||||||
import 'package:fluffychat/pages/settings_security/settings_security.dart';
|
import 'package:fluffychat/pages/settings_security/settings_security.dart';
|
||||||
import 'package:fluffychat/pages/settings_stories/settings_stories.dart';
|
import 'package:fluffychat/pages/settings_stories/settings_stories.dart';
|
||||||
import 'package:fluffychat/pages/settings_style/settings_style.dart';
|
import 'package:fluffychat/pages/settings_style/settings_style.dart';
|
||||||
import 'package:fluffychat/pages/sign_up/signup.dart';
|
|
||||||
import 'package:fluffychat/pages/story/story_page.dart';
|
import 'package:fluffychat/pages/story/story_page.dart';
|
||||||
import 'package:fluffychat/widgets/layouts/empty_page.dart';
|
import 'package:fluffychat/widgets/layouts/empty_page.dart';
|
||||||
import 'package:fluffychat/widgets/layouts/loading_view.dart';
|
import 'package:fluffychat/widgets/layouts/loading_view.dart';
|
||||||
@ -266,23 +264,6 @@ class AppRoutes {
|
|||||||
widget: const Login(),
|
widget: const Login(),
|
||||||
buildTransition: _fadeTransition,
|
buildTransition: _fadeTransition,
|
||||||
),
|
),
|
||||||
VWidget(
|
|
||||||
path: 'connect',
|
|
||||||
widget: const ConnectPage(),
|
|
||||||
buildTransition: _fadeTransition,
|
|
||||||
stackedRoutes: [
|
|
||||||
VWidget(
|
|
||||||
path: 'login',
|
|
||||||
widget: const Login(),
|
|
||||||
buildTransition: _fadeTransition,
|
|
||||||
),
|
|
||||||
VWidget(
|
|
||||||
path: 'signup',
|
|
||||||
widget: const SignupPage(),
|
|
||||||
buildTransition: _fadeTransition,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
VWidget(
|
VWidget(
|
||||||
path: 'logs',
|
path: 'logs',
|
||||||
widget: const LogViewer(),
|
widget: const LogViewer(),
|
||||||
@ -358,23 +339,6 @@ class AppRoutes {
|
|||||||
widget: const Login(),
|
widget: const Login(),
|
||||||
buildTransition: _fadeTransition,
|
buildTransition: _fadeTransition,
|
||||||
),
|
),
|
||||||
VWidget(
|
|
||||||
path: 'connect',
|
|
||||||
widget: const ConnectPage(),
|
|
||||||
buildTransition: _fadeTransition,
|
|
||||||
stackedRoutes: [
|
|
||||||
VWidget(
|
|
||||||
path: 'login',
|
|
||||||
widget: const Login(),
|
|
||||||
buildTransition: _fadeTransition,
|
|
||||||
),
|
|
||||||
VWidget(
|
|
||||||
path: 'signup',
|
|
||||||
widget: const SignupPage(),
|
|
||||||
buildTransition: _fadeTransition,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
VWidget(
|
VWidget(
|
||||||
|
|||||||
@ -41,6 +41,22 @@ abstract class FluffyThemes {
|
|||||||
titleSmall: fallbackTextStyle,
|
titleSmall: fallbackTextStyle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
static LinearGradient backgroundGradient(
|
||||||
|
BuildContext context,
|
||||||
|
int alpha,
|
||||||
|
) {
|
||||||
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
return LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
colors: [
|
||||||
|
colorScheme.primaryContainer.withAlpha(alpha),
|
||||||
|
colorScheme.secondaryContainer.withAlpha(alpha),
|
||||||
|
colorScheme.tertiaryContainer.withAlpha(alpha),
|
||||||
|
colorScheme.primaryContainer.withAlpha(alpha),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static const Duration animationDuration = Duration(milliseconds: 250);
|
static const Duration animationDuration = Duration(milliseconds: 250);
|
||||||
static const Curve animationCurve = Curves.easeInOut;
|
static const Curve animationCurve = Curves.easeInOut;
|
||||||
|
|
||||||
|
|||||||
@ -204,6 +204,7 @@ class ChatController extends State<ChatPageWithRoom> {
|
|||||||
|
|
||||||
void requestHistory() async {
|
void requestHistory() async {
|
||||||
if (!timeline!.canRequestHistory) return;
|
if (!timeline!.canRequestHistory) return;
|
||||||
|
Logs().v('Requesting history...');
|
||||||
try {
|
try {
|
||||||
await timeline!.requestHistory(historyCount: _loadHistoryCount);
|
await timeline!.requestHistory(historyCount: _loadHistoryCount);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -222,6 +223,7 @@ class ChatController extends State<ChatPageWithRoom> {
|
|||||||
final timeline = this.timeline;
|
final timeline = this.timeline;
|
||||||
if (timeline == null) return;
|
if (timeline == null) return;
|
||||||
if (!timeline.canRequestFuture) return;
|
if (!timeline.canRequestFuture) return;
|
||||||
|
Logs().v('Requesting future...');
|
||||||
try {
|
try {
|
||||||
final mostRecentEventId = timeline.events.first.eventId;
|
final mostRecentEventId = timeline.events.first.eventId;
|
||||||
await timeline.requestFuture(historyCount: _loadHistoryCount);
|
await timeline.requestFuture(historyCount: _loadHistoryCount);
|
||||||
@ -244,12 +246,6 @@ class ChatController extends State<ChatPageWithRoom> {
|
|||||||
}
|
}
|
||||||
setReadMarker();
|
setReadMarker();
|
||||||
if (!scrollController.hasClients) return;
|
if (!scrollController.hasClients) return;
|
||||||
if (scrollController.position.pixels ==
|
|
||||||
scrollController.position.maxScrollExtent) {
|
|
||||||
requestHistory();
|
|
||||||
} else if (scrollController.position.pixels == 0) {
|
|
||||||
requestFuture();
|
|
||||||
}
|
|
||||||
if (timeline?.allowNewEvent == false ||
|
if (timeline?.allowNewEvent == false ||
|
||||||
scrollController.position.pixels > 0 && _scrolledUp == false) {
|
scrollController.position.pixels > 0 && _scrolledUp == false) {
|
||||||
setState(() => _scrolledUp = true);
|
setState(() => _scrolledUp = true);
|
||||||
@ -873,8 +869,11 @@ class ChatController extends State<ChatPageWithRoom> {
|
|||||||
setState(() => showEmojiPicker = false);
|
setState(() => showEmojiPicker = false);
|
||||||
if (emoji == null) return;
|
if (emoji == null) return;
|
||||||
// make sure we don't send the same emoji twice
|
// make sure we don't send the same emoji twice
|
||||||
if (_allReactionEvents
|
if (_allReactionEvents.any(
|
||||||
.any((e) => e.content['m.relates_to']['key'] == emoji.emoji)) return;
|
(e) => e.content.tryGetMap('m.relates_to')?['key'] == emoji.emoji,
|
||||||
|
)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
return sendEmojiAction(emoji.emoji);
|
return sendEmojiAction(emoji.emoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -53,12 +53,19 @@ class ChatEventList extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (controller.timeline!.canRequestFuture) {
|
if (controller.timeline!.canRequestFuture) {
|
||||||
|
return Builder(
|
||||||
|
builder: (context) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
|
(_) => controller.requestFuture(),
|
||||||
|
);
|
||||||
return Center(
|
return Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: controller.requestFuture,
|
onPressed: controller.requestFuture,
|
||||||
icon: const Icon(Icons.refresh_outlined),
|
icon: const Icon(Icons.refresh_outlined),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -77,12 +84,19 @@ class ChatEventList extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (controller.timeline!.canRequestHistory) {
|
if (controller.timeline!.canRequestHistory) {
|
||||||
|
return Builder(
|
||||||
|
builder: (context) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
|
(_) => controller.requestHistory(),
|
||||||
|
);
|
||||||
return Center(
|
return Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: controller.requestHistory,
|
onPressed: controller.requestHistory,
|
||||||
icon: const Icon(Icons.refresh_outlined),
|
icon: const Icon(Icons.refresh_outlined),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -146,7 +146,6 @@ class ChatView extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0;
|
final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0;
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
|
||||||
|
|
||||||
return VWidgetGuard(
|
return VWidgetGuard(
|
||||||
onSystemPop: (redirector) async {
|
onSystemPop: (redirector) async {
|
||||||
@ -220,14 +219,9 @@ class ChatView extends StatelessWidget {
|
|||||||
else
|
else
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: FluffyThemes.backgroundGradient(
|
||||||
begin: Alignment.topCenter,
|
context,
|
||||||
colors: [
|
64,
|
||||||
colorScheme.primaryContainer.withAlpha(64),
|
|
||||||
colorScheme.secondaryContainer.withAlpha(64),
|
|
||||||
colorScheme.tertiaryContainer.withAlpha(64),
|
|
||||||
colorScheme.primaryContainer.withAlpha(64),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -36,8 +36,6 @@ class _CuteContentState extends State<CuteContent> {
|
|||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: addOverlay,
|
onTap: addOverlay,
|
||||||
child: SizedBox.square(
|
|
||||||
dimension: 300,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@ -49,7 +47,6 @@ class _CuteContentState extends State<CuteContent> {
|
|||||||
if (label != null) Text(label)
|
if (label != null) Text(label)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -144,6 +141,7 @@ class _CuteEventOverlayState extends State<CuteEventOverlay>
|
|||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: constraints.maxHeight,
|
height: constraints.maxHeight,
|
||||||
width: constraints.maxWidth,
|
width: constraints.maxWidth,
|
||||||
|
child: OverflowBox(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.bottomLeft,
|
alignment: Alignment.bottomLeft,
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
@ -163,6 +161,7 @@ class _CuteEventOverlayState extends State<CuteEventOverlay>
|
|||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -61,7 +61,7 @@ class MessageReactions extends StatelessWidget {
|
|||||||
final evt = allReactionEvents.firstWhereOrNull(
|
final evt = allReactionEvents.firstWhereOrNull(
|
||||||
(e) =>
|
(e) =>
|
||||||
e.senderId == e.room.client.userID &&
|
e.senderId == e.room.client.userID &&
|
||||||
e.content['m.relates_to']['key'] == r.key,
|
e.content.tryGetMap('m.relates_to')?['key'] == r.key,
|
||||||
);
|
);
|
||||||
if (evt != null) {
|
if (evt != null) {
|
||||||
showFutureLoadingDialog(
|
showFutureLoadingDialog(
|
||||||
|
|||||||
@ -52,7 +52,8 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
|
|||||||
final networkUri = _networkUri;
|
final networkUri = _networkUri;
|
||||||
if (kIsWeb && networkUri != null && _chewieManager == null) {
|
if (kIsWeb && networkUri != null && _chewieManager == null) {
|
||||||
_chewieManager ??= ChewieController(
|
_chewieManager ??= ChewieController(
|
||||||
videoPlayerController: VideoPlayerController.network(networkUri),
|
videoPlayerController:
|
||||||
|
VideoPlayerController.networkUrl(Uri.parse(networkUri)),
|
||||||
autoPlay: true,
|
autoPlay: true,
|
||||||
autoInitialize: true,
|
autoInitialize: true,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -183,12 +183,13 @@ class InputBar extends StatelessWidget {
|
|||||||
final state = r.getState(EventTypes.RoomCanonicalAlias);
|
final state = r.getState(EventTypes.RoomCanonicalAlias);
|
||||||
if ((state != null &&
|
if ((state != null &&
|
||||||
((state.content['alias'] is String &&
|
((state.content['alias'] is String &&
|
||||||
state.content['alias']
|
state.content
|
||||||
|
.tryGet<String>('alias')!
|
||||||
.split(':')[0]
|
.split(':')[0]
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(roomSearch)) ||
|
.contains(roomSearch)) ||
|
||||||
(state.content['alt_aliases'] is List &&
|
(state.content['alt_aliases'] is List &&
|
||||||
state.content['alt_aliases'].any(
|
(state.content['alt_aliases'] as List).any(
|
||||||
(l) =>
|
(l) =>
|
||||||
l is String &&
|
l is String &&
|
||||||
l
|
l
|
||||||
|
|||||||
@ -52,7 +52,7 @@ class ReactionsPicker extends StatelessWidget {
|
|||||||
|
|
||||||
for (final event in allReactionEvents) {
|
for (final event in allReactionEvents) {
|
||||||
try {
|
try {
|
||||||
emojis.remove(event.content['m.relates_to']['key']);
|
emojis.remove(event.content.tryGetMap('m.relates_to')!['key']);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
return Row(
|
return Row(
|
||||||
|
|||||||
@ -83,7 +83,9 @@ class ChatDetailsController extends State<ChatDetails> {
|
|||||||
RequestType.GET,
|
RequestType.GET,
|
||||||
'/client/unstable/org.matrix.msc2432/rooms/${Uri.encodeComponent(room.id)}/aliases',
|
'/client/unstable/org.matrix.msc2432/rooms/${Uri.encodeComponent(room.id)}/aliases',
|
||||||
)
|
)
|
||||||
.then((response) => List<String>.from(response['aliases'])),
|
.then(
|
||||||
|
(response) => List<String>.from(response['aliases'] as Iterable),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
// Switch to the stable api once it is implemented.
|
// Switch to the stable api once it is implemented.
|
||||||
|
|
||||||
|
|||||||
@ -73,8 +73,10 @@ class ChatPermissionsSettingsController extends State<ChatPermissionsSettings> {
|
|||||||
|
|
||||||
void updateRoomAction(Capabilities capabilities) async {
|
void updateRoomAction(Capabilities capabilities) async {
|
||||||
final room = Matrix.of(context).client.getRoomById(roomId!)!;
|
final room = Matrix.of(context).client.getRoomById(roomId!)!;
|
||||||
final String roomVersion =
|
final roomVersion = room
|
||||||
room.getState(EventTypes.RoomCreate)!.content['room_version'] ?? '1';
|
.getState(EventTypes.RoomCreate)!
|
||||||
|
.content['room_version'] as String? ??
|
||||||
|
'1';
|
||||||
final newVersion = await showConfirmationDialog<String>(
|
final newVersion = await showConfirmationDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context)!.replaceRoomWithNewerVersion,
|
title: L10n.of(context)!.replaceRoomWithNewerVersion,
|
||||||
|
|||||||
@ -127,9 +127,9 @@ class ChatPermissionsSettingsView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final String roomVersion = room
|
final roomVersion = room
|
||||||
.getState(EventTypes.RoomCreate)!
|
.getState(EventTypes.RoomCreate)!
|
||||||
.content['room_version'] ??
|
.content['room_version'] as String? ??
|
||||||
'1';
|
'1';
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
|
|||||||
@ -1,197 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
|
||||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
|
||||||
import 'package:image_picker/image_picker.dart';
|
|
||||||
import 'package:matrix/matrix.dart';
|
|
||||||
import 'package:universal_html/html.dart' as html;
|
|
||||||
import 'package:vrouter/vrouter.dart';
|
|
||||||
|
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
|
||||||
import 'package:fluffychat/pages/connect/connect_page_view.dart';
|
|
||||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
|
||||||
import 'package:fluffychat/utils/platform_infos.dart';
|
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
|
||||||
|
|
||||||
class ConnectPage extends StatefulWidget {
|
|
||||||
const ConnectPage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ConnectPage> createState() => ConnectPageController();
|
|
||||||
}
|
|
||||||
|
|
||||||
class ConnectPageController extends State<ConnectPage> {
|
|
||||||
final TextEditingController usernameController = TextEditingController();
|
|
||||||
String? signupError;
|
|
||||||
bool loading = false;
|
|
||||||
|
|
||||||
void pickAvatar() async {
|
|
||||||
final source = !PlatformInfos.isMobile
|
|
||||||
? ImageSource.gallery
|
|
||||||
: await showModalActionSheet<ImageSource>(
|
|
||||||
context: context,
|
|
||||||
title: L10n.of(context)!.changeYourAvatar,
|
|
||||||
actions: [
|
|
||||||
SheetAction(
|
|
||||||
key: ImageSource.camera,
|
|
||||||
label: L10n.of(context)!.openCamera,
|
|
||||||
isDefaultAction: true,
|
|
||||||
icon: Icons.camera_alt_outlined,
|
|
||||||
),
|
|
||||||
SheetAction(
|
|
||||||
key: ImageSource.gallery,
|
|
||||||
label: L10n.of(context)!.openGallery,
|
|
||||||
icon: Icons.photo_outlined,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if (source == null) return;
|
|
||||||
final picked = await ImagePicker().pickImage(
|
|
||||||
source: source,
|
|
||||||
imageQuality: 50,
|
|
||||||
maxWidth: 512,
|
|
||||||
maxHeight: 512,
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
Matrix.of(context).loginAvatar = picked;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void signUp() async {
|
|
||||||
usernameController.text = usernameController.text.trim();
|
|
||||||
final localpart =
|
|
||||||
usernameController.text.toLowerCase().replaceAll(' ', '_');
|
|
||||||
if (localpart.isEmpty) {
|
|
||||||
setState(() {
|
|
||||||
signupError = L10n.of(context)!.pleaseChooseAUsername;
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
signupError = null;
|
|
||||||
loading = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
try {
|
|
||||||
await Matrix.of(context).getLoginClient().register(username: localpart);
|
|
||||||
} on MatrixException catch (e) {
|
|
||||||
if (!e.requireAdditionalAuthentication) rethrow;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
loading = false;
|
|
||||||
});
|
|
||||||
Matrix.of(context).loginUsername = usernameController.text;
|
|
||||||
VRouter.of(context).to('signup');
|
|
||||||
} catch (e, s) {
|
|
||||||
Logs().d('Sign up failed', e, s);
|
|
||||||
setState(() {
|
|
||||||
signupError = e.toLocalizedString(context);
|
|
||||||
loading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _supportsFlow(String flowType) =>
|
|
||||||
Matrix.of(context)
|
|
||||||
.loginHomeserverSummary
|
|
||||||
?.loginFlows
|
|
||||||
.any((flow) => flow.type == flowType) ??
|
|
||||||
false;
|
|
||||||
|
|
||||||
bool get supportsSso => _supportsFlow('m.login.sso');
|
|
||||||
|
|
||||||
bool isDefaultPlatform =
|
|
||||||
(PlatformInfos.isMobile || PlatformInfos.isWeb || PlatformInfos.isMacOS);
|
|
||||||
|
|
||||||
bool get supportsLogin => _supportsFlow('m.login.password');
|
|
||||||
|
|
||||||
void login() => VRouter.of(context).to('login');
|
|
||||||
|
|
||||||
Map<String, dynamic>? _rawLoginTypes;
|
|
||||||
|
|
||||||
List<IdentityProvider>? get identityProviders {
|
|
||||||
final loginTypes = _rawLoginTypes;
|
|
||||||
if (loginTypes == null) return null;
|
|
||||||
final rawProviders = loginTypes.tryGetList('flows')!.singleWhere(
|
|
||||||
(flow) => flow['type'] == AuthenticationTypes.sso,
|
|
||||||
)['identity_providers'];
|
|
||||||
final list = (rawProviders as List)
|
|
||||||
.map((json) => IdentityProvider.fromJson(json))
|
|
||||||
.toList();
|
|
||||||
if (PlatformInfos.isCupertinoStyle) {
|
|
||||||
list.sort((a, b) => a.brand == 'apple' ? -1 : 1);
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ssoLoginAction(String id) async {
|
|
||||||
final redirectUrl = kIsWeb
|
|
||||||
? '${html.window.origin!}/web/auth.html'
|
|
||||||
: isDefaultPlatform
|
|
||||||
? '${AppConfig.appOpenUrlScheme.toLowerCase()}://login'
|
|
||||||
: 'http://localhost:3001//login';
|
|
||||||
final url =
|
|
||||||
'${Matrix.of(context).getLoginClient().homeserver?.toString()}/_matrix/client/r0/login/sso/redirect/${Uri.encodeComponent(id)}?redirectUrl=${Uri.encodeQueryComponent(redirectUrl)}';
|
|
||||||
final urlScheme = isDefaultPlatform
|
|
||||||
? Uri.parse(redirectUrl).scheme
|
|
||||||
: "http://localhost:3001";
|
|
||||||
final result = await FlutterWebAuth2.authenticate(
|
|
||||||
url: url,
|
|
||||||
callbackUrlScheme: urlScheme,
|
|
||||||
);
|
|
||||||
final token = Uri.parse(result).queryParameters['loginToken'];
|
|
||||||
if (token?.isEmpty ?? false) return;
|
|
||||||
|
|
||||||
await showFutureLoadingDialog(
|
|
||||||
context: context,
|
|
||||||
future: () => Matrix.of(context).getLoginClient().login(
|
|
||||||
LoginType.mLoginToken,
|
|
||||||
token: token,
|
|
||||||
initialDeviceDisplayName: PlatformInfos.clientName,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
if (supportsSso) {
|
|
||||||
Matrix.of(context)
|
|
||||||
.getLoginClient()
|
|
||||||
.request(
|
|
||||||
RequestType.GET,
|
|
||||||
'/client/r0/login',
|
|
||||||
)
|
|
||||||
.then(
|
|
||||||
(loginTypes) => setState(() {
|
|
||||||
_rawLoginTypes = loginTypes;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => ConnectPageView(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
class IdentityProvider {
|
|
||||||
final String? id;
|
|
||||||
final String? name;
|
|
||||||
final String? icon;
|
|
||||||
final String? brand;
|
|
||||||
|
|
||||||
IdentityProvider({this.id, this.name, this.icon, this.brand});
|
|
||||||
|
|
||||||
factory IdentityProvider.fromJson(Map<String, dynamic> json) =>
|
|
||||||
IdentityProvider(
|
|
||||||
id: json['id'],
|
|
||||||
name: json['name'],
|
|
||||||
icon: json['icon'],
|
|
||||||
brand: json['brand'],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,226 +0,0 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
import 'package:matrix/matrix.dart';
|
|
||||||
|
|
||||||
import 'package:fluffychat/pages/connect/connect_page.dart';
|
|
||||||
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
|
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
|
||||||
import 'sso_button.dart';
|
|
||||||
|
|
||||||
class ConnectPageView extends StatelessWidget {
|
|
||||||
final ConnectPageController controller;
|
|
||||||
const ConnectPageView(this.controller, {Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final avatar = Matrix.of(context).loginAvatar;
|
|
||||||
final identityProviders = controller.identityProviders;
|
|
||||||
return LoginScaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: controller.loading ? null : const BackButton(),
|
|
||||||
automaticallyImplyLeading: !controller.loading,
|
|
||||||
centerTitle: true,
|
|
||||||
title: Text(
|
|
||||||
Matrix.of(context).getLoginClient().homeserver?.host ?? '',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: ListView(
|
|
||||||
key: const Key('ConnectPageListView'),
|
|
||||||
children: [
|
|
||||||
if (Matrix.of(context).loginRegistrationSupported ?? false) ...[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Center(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Material(
|
|
||||||
borderRadius: BorderRadius.circular(64),
|
|
||||||
elevation: Theme.of(context)
|
|
||||||
.appBarTheme
|
|
||||||
.scrolledUnderElevation ??
|
|
||||||
10,
|
|
||||||
color: Colors.transparent,
|
|
||||||
shadowColor: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onBackground
|
|
||||||
.withAlpha(64),
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
child: CircleAvatar(
|
|
||||||
radius: 64,
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
child: avatar == null
|
|
||||||
? const Icon(
|
|
||||||
Icons.person,
|
|
||||||
color: Colors.black,
|
|
||||||
size: 64,
|
|
||||||
)
|
|
||||||
: FutureBuilder<Uint8List>(
|
|
||||||
future: avatar.readAsBytes(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
final bytes = snapshot.data;
|
|
||||||
if (bytes == null) {
|
|
||||||
return const CircularProgressIndicator
|
|
||||||
.adaptive();
|
|
||||||
}
|
|
||||||
return Image.memory(
|
|
||||||
bytes,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
width: 128,
|
|
||||||
height: 128,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
child: FloatingActionButton(
|
|
||||||
mini: true,
|
|
||||||
onPressed: controller.pickAvatar,
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
foregroundColor: Colors.black,
|
|
||||||
child: const Icon(Icons.camera_alt_outlined),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: TextField(
|
|
||||||
controller: controller.usernameController,
|
|
||||||
onSubmitted: (_) => controller.signUp(),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.account_box_outlined),
|
|
||||||
hintText: L10n.of(context)!.chooseAUsername,
|
|
||||||
errorText: controller.signupError,
|
|
||||||
errorStyle: const TextStyle(color: Colors.orange),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Hero(
|
|
||||||
tag: 'loginButton',
|
|
||||||
child: ElevatedButton.icon(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
|
||||||
),
|
|
||||||
onPressed: controller.loading ? () {} : controller.signUp,
|
|
||||||
icon: const Icon(Icons.person_add_outlined),
|
|
||||||
label: controller.loading
|
|
||||||
? const LinearProgressIndicator()
|
|
||||||
: Text(L10n.of(context)!.signUp),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Divider(
|
|
||||||
thickness: 1,
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Text(
|
|
||||||
L10n.of(context)!.or,
|
|
||||||
style: const TextStyle(fontSize: 18),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Divider(
|
|
||||||
thickness: 1,
|
|
||||||
color: Theme.of(context).dividerColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
if (controller.supportsSso)
|
|
||||||
identityProviders == null
|
|
||||||
? const SizedBox(
|
|
||||||
height: 74,
|
|
||||||
child: Center(child: CircularProgressIndicator.adaptive()),
|
|
||||||
)
|
|
||||||
: Center(
|
|
||||||
child: identityProviders.length == 1
|
|
||||||
? Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: ElevatedButton.icon(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primaryContainer,
|
|
||||||
foregroundColor: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
icon: identityProviders.single.icon == null
|
|
||||||
? const Icon(
|
|
||||||
Icons.web_outlined,
|
|
||||||
size: 16,
|
|
||||||
)
|
|
||||||
: Image.network(
|
|
||||||
Uri.parse(identityProviders.single.icon!)
|
|
||||||
.getDownloadLink(
|
|
||||||
Matrix.of(context).getLoginClient(),
|
|
||||||
)
|
|
||||||
.toString(),
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
),
|
|
||||||
onPressed: () => controller
|
|
||||||
.ssoLoginAction(identityProviders.single.id!),
|
|
||||||
label: Text(
|
|
||||||
identityProviders.single.name ??
|
|
||||||
identityProviders.single.brand ??
|
|
||||||
L10n.of(context)!.loginWithOneClick,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Wrap(
|
|
||||||
children: [
|
|
||||||
for (final identityProvider in identityProviders)
|
|
||||||
SsoButton(
|
|
||||||
onPressed: () => controller
|
|
||||||
.ssoLoginAction(identityProvider.id!),
|
|
||||||
identityProvider: identityProvider,
|
|
||||||
),
|
|
||||||
].toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (controller.supportsLogin)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Hero(
|
|
||||||
tag: 'signinButton',
|
|
||||||
child: ElevatedButton.icon(
|
|
||||||
icon: const Icon(Icons.login_outlined),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).colorScheme.primaryContainer,
|
|
||||||
foregroundColor:
|
|
||||||
Theme.of(context).colorScheme.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
onPressed: controller.loading ? () {} : controller.login,
|
|
||||||
label: Text(L10n.of(context)!.login),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
import 'package:matrix/matrix.dart';
|
|
||||||
|
|
||||||
import 'package:fluffychat/pages/connect/connect_page.dart';
|
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
|
||||||
|
|
||||||
class SsoButton extends StatelessWidget {
|
|
||||||
final IdentityProvider identityProvider;
|
|
||||||
final void Function()? onPressed;
|
|
||||||
const SsoButton({
|
|
||||||
Key? key,
|
|
||||||
required this.identityProvider,
|
|
||||||
this.onPressed,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InkWell(
|
|
||||||
onTap: onPressed,
|
|
||||||
borderRadius: BorderRadius.circular(7),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Material(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(4.0),
|
|
||||||
child: identityProvider.icon == null
|
|
||||||
? const Icon(Icons.web_outlined)
|
|
||||||
: Image.network(
|
|
||||||
Uri.parse(identityProvider.icon!)
|
|
||||||
.getDownloadLink(
|
|
||||||
Matrix.of(context).getLoginClient(),
|
|
||||||
)
|
|
||||||
.toString(),
|
|
||||||
width: 32,
|
|
||||||
height: 32,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(
|
|
||||||
identityProvider.name ??
|
|
||||||
identityProvider.brand ??
|
|
||||||
L10n.of(context)!.singlesignon,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||||
|
import 'package:matrix_homeserver_recommendations/matrix_homeserver_recommendations.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
|
import 'homeserver_bottom_sheet.dart';
|
||||||
import 'homeserver_picker.dart';
|
import 'homeserver_picker.dart';
|
||||||
|
|
||||||
class HomeserverAppBar extends StatelessWidget {
|
class HomeserverAppBar extends StatelessWidget {
|
||||||
@ -13,10 +16,42 @@ class HomeserverAppBar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextField(
|
return TypeAheadField<HomeserverBenchmarkResult>(
|
||||||
focusNode: controller.homeserverFocusNode,
|
suggestionsBoxDecoration: SuggestionsBoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||||
|
elevation: Theme.of(context).appBarTheme.scrolledUnderElevation ?? 4,
|
||||||
|
shadowColor: Theme.of(context).appBarTheme.shadowColor ?? Colors.black,
|
||||||
|
constraints: const BoxConstraints(maxHeight: 256),
|
||||||
|
),
|
||||||
|
itemBuilder: (context, homeserver) => ListTile(
|
||||||
|
title: Text(homeserver.homeserver.baseUrl.toString()),
|
||||||
|
subtitle: Text(homeserver.homeserver.description ?? ''),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(Icons.info_outlined),
|
||||||
|
onPressed: () => showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => HomeserverBottomSheet(
|
||||||
|
homeserver: homeserver,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
suggestionsCallback: (pattern) async {
|
||||||
|
final homeserverList =
|
||||||
|
await const JoinmatrixOrgParser().fetchHomeservers();
|
||||||
|
final benchmark = await HomeserverListProvider.benchmarkHomeserver(
|
||||||
|
homeserverList,
|
||||||
|
timeout: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
return benchmark;
|
||||||
|
},
|
||||||
|
onSuggestionSelected: (suggestion) {
|
||||||
|
controller.homeserverController.text =
|
||||||
|
suggestion.homeserver.baseUrl.host;
|
||||||
|
controller.checkHomeserverAction();
|
||||||
|
},
|
||||||
|
textFieldConfiguration: TextFieldConfiguration(
|
||||||
controller: controller.homeserverController,
|
controller: controller.homeserverController,
|
||||||
onChanged: controller.onChanged,
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefixIcon: Navigator.of(context).canPop()
|
prefixIcon: Navigator.of(context).canPop()
|
||||||
? IconButton(
|
? IconButton(
|
||||||
@ -27,11 +62,11 @@ class HomeserverAppBar extends StatelessWidget {
|
|||||||
prefixText: '${L10n.of(context)!.homeserver}: ',
|
prefixText: '${L10n.of(context)!.homeserver}: ',
|
||||||
hintText: L10n.of(context)!.enterYourHomeserver,
|
hintText: L10n.of(context)!.enterYourHomeserver,
|
||||||
suffixIcon: const Icon(Icons.search),
|
suffixIcon: const Icon(Icons.search),
|
||||||
errorText: controller.error,
|
|
||||||
),
|
),
|
||||||
readOnly: !AppConfig.allowOtherHomeservers,
|
textInputAction: TextInputAction.search,
|
||||||
onSubmitted: (_) => controller.checkHomeserverAction(),
|
onSubmitted: controller.checkHomeserverAction,
|
||||||
autocorrect: false,
|
autocorrect: false,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,16 +7,16 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:flutter_web_auth_2/flutter_web_auth_2.dart';
|
||||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:matrix_homeserver_recommendations/matrix_homeserver_recommendations.dart';
|
import 'package:universal_html/html.dart' as html;
|
||||||
import 'package:vrouter/vrouter.dart';
|
import 'package:vrouter/vrouter.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
import 'package:fluffychat/pages/homeserver_picker/homeserver_bottom_sheet.dart';
|
|
||||||
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart';
|
import 'package:fluffychat/pages/homeserver_picker/homeserver_picker_view.dart';
|
||||||
import 'package:fluffychat/utils/adaptive_bottom_sheet.dart';
|
import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import '../../utils/localized_exception_extension.dart';
|
import '../../utils/localized_exception_extension.dart';
|
||||||
|
|
||||||
@ -35,14 +35,8 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||||||
final TextEditingController homeserverController = TextEditingController(
|
final TextEditingController homeserverController = TextEditingController(
|
||||||
text: AppConfig.defaultHomeserver,
|
text: AppConfig.defaultHomeserver,
|
||||||
);
|
);
|
||||||
final FocusNode homeserverFocusNode = FocusNode();
|
|
||||||
String? error;
|
|
||||||
List<HomeserverBenchmarkResult>? benchmarkResults;
|
|
||||||
bool displayServerList = false;
|
|
||||||
|
|
||||||
bool get loadingHomeservers =>
|
String? error;
|
||||||
AppConfig.allowOtherHomeservers && benchmarkResults == null;
|
|
||||||
String searchTerm = '';
|
|
||||||
|
|
||||||
bool isTorBrowser = false;
|
bool isTorBrowser = false;
|
||||||
|
|
||||||
@ -65,98 +59,34 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||||||
isTorBrowser = isTor;
|
isTorBrowser = isTor;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateFocus() {
|
String? _lastCheckedUrl;
|
||||||
if (benchmarkResults == null) _loadHomeserverList();
|
|
||||||
if (homeserverFocusNode.hasFocus) {
|
|
||||||
setState(() {
|
|
||||||
displayServerList = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void showServerInfo(HomeserverBenchmarkResult server) =>
|
|
||||||
showAdaptiveBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => HomeserverBottomSheet(
|
|
||||||
homeserver: server,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
void onChanged(String text) => setState(() {
|
|
||||||
searchTerm = text;
|
|
||||||
});
|
|
||||||
|
|
||||||
List<HomeserverBenchmarkResult> get filteredHomeservers => benchmarkResults!
|
|
||||||
.where(
|
|
||||||
(element) =>
|
|
||||||
element.homeserver.baseUrl.host.contains(searchTerm) ||
|
|
||||||
(element.homeserver.description?.contains(searchTerm) ?? false),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
void _loadHomeserverList() async {
|
|
||||||
try {
|
|
||||||
final homeserverList =
|
|
||||||
await const JoinmatrixOrgParser().fetchHomeservers();
|
|
||||||
final benchmark = await HomeserverListProvider.benchmarkHomeserver(
|
|
||||||
homeserverList,
|
|
||||||
timeout: const Duration(seconds: 10),
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
|
||||||
benchmarkResults = benchmark;
|
|
||||||
});
|
|
||||||
} catch (e, s) {
|
|
||||||
Logs().e('Homeserver benchmark failed', e, s);
|
|
||||||
benchmarkResults = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setServer(String server) => setState(() {
|
|
||||||
homeserverController.text = server;
|
|
||||||
searchTerm = '';
|
|
||||||
homeserverFocusNode.unfocus();
|
|
||||||
displayServerList = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Starts an analysis of the given homeserver. It uses the current domain and
|
/// 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
|
/// 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
|
/// well-known information and forwards to the login page depending on the
|
||||||
/// login type.
|
/// login type.
|
||||||
Future<void> checkHomeserverAction() async {
|
Future<void> checkHomeserverAction([_]) async {
|
||||||
|
homeserverController.text =
|
||||||
|
homeserverController.text.trim().toLowerCase().replaceAll(' ', '-');
|
||||||
|
if (homeserverController.text == _lastCheckedUrl) return;
|
||||||
|
_lastCheckedUrl = homeserverController.text;
|
||||||
setState(() {
|
setState(() {
|
||||||
homeserverFocusNode.unfocus();
|
error = _rawLoginTypes = loginHomeserverSummary = null;
|
||||||
error = null;
|
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
searchTerm = '';
|
|
||||||
displayServerList = false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
homeserverController.text =
|
|
||||||
homeserverController.text.trim().toLowerCase().replaceAll(' ', '-');
|
|
||||||
var homeserver = Uri.parse(homeserverController.text);
|
var homeserver = Uri.parse(homeserverController.text);
|
||||||
if (homeserver.scheme.isEmpty) {
|
if (homeserver.scheme.isEmpty) {
|
||||||
homeserver = Uri.https(homeserverController.text, '');
|
homeserver = Uri.https(homeserverController.text, '');
|
||||||
}
|
}
|
||||||
final matrix = Matrix.of(context);
|
final client = Matrix.of(context).getLoginClient();
|
||||||
matrix.loginHomeserverSummary =
|
loginHomeserverSummary = await client.checkHomeserver(homeserver);
|
||||||
await matrix.getLoginClient().checkHomeserver(homeserver);
|
if (supportsSso) {
|
||||||
final ssoSupported = matrix.loginHomeserverSummary!.loginFlows
|
_rawLoginTypes = await client.request(
|
||||||
.any((flow) => flow.type == 'm.login.sso');
|
RequestType.GET,
|
||||||
|
'/client/r0/login',
|
||||||
try {
|
);
|
||||||
await Matrix.of(context).getLoginClient().register();
|
|
||||||
matrix.loginRegistrationSupported = true;
|
|
||||||
} on MatrixException catch (e) {
|
|
||||||
matrix.loginRegistrationSupported = e.requireAdditionalAuthentication;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ssoSupported && matrix.loginRegistrationSupported == false) {
|
|
||||||
// Server does not support SSO or registration. We can skip to login page:
|
|
||||||
VRouter.of(context).to('login');
|
|
||||||
} else {
|
|
||||||
VRouter.of(context).to('connect');
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setState(() => error = (e).toLocalizedString(context));
|
setState(() => error = (e).toLocalizedString(context));
|
||||||
@ -167,17 +97,71 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
HomeserverSummary? loginHomeserverSummary;
|
||||||
void dispose() {
|
|
||||||
homeserverFocusNode.removeListener(_updateFocus);
|
bool _supportsFlow(String flowType) =>
|
||||||
super.dispose();
|
loginHomeserverSummary?.loginFlows.any((flow) => flow.type == flowType) ??
|
||||||
|
false;
|
||||||
|
|
||||||
|
bool get supportsSso => _supportsFlow('m.login.sso');
|
||||||
|
|
||||||
|
bool isDefaultPlatform =
|
||||||
|
(PlatformInfos.isMobile || PlatformInfos.isWeb || PlatformInfos.isMacOS);
|
||||||
|
|
||||||
|
bool get supportsPasswordLogin => _supportsFlow('m.login.password');
|
||||||
|
|
||||||
|
Map<String, dynamic>? _rawLoginTypes;
|
||||||
|
|
||||||
|
void ssoLoginAction(String id) async {
|
||||||
|
final redirectUrl = kIsWeb
|
||||||
|
? '${html.window.origin!}/web/auth.html'
|
||||||
|
: isDefaultPlatform
|
||||||
|
? '${AppConfig.appOpenUrlScheme.toLowerCase()}://login'
|
||||||
|
: 'http://localhost:3001//login';
|
||||||
|
final url =
|
||||||
|
'${Matrix.of(context).getLoginClient().homeserver?.toString()}/_matrix/client/r0/login/sso/redirect/${Uri.encodeComponent(id)}?redirectUrl=${Uri.encodeQueryComponent(redirectUrl)}';
|
||||||
|
final urlScheme = isDefaultPlatform
|
||||||
|
? Uri.parse(redirectUrl).scheme
|
||||||
|
: "http://localhost:3001";
|
||||||
|
final result = await FlutterWebAuth2.authenticate(
|
||||||
|
url: url,
|
||||||
|
callbackUrlScheme: urlScheme,
|
||||||
|
);
|
||||||
|
final token = Uri.parse(result).queryParameters['loginToken'];
|
||||||
|
if (token?.isEmpty ?? false) return;
|
||||||
|
|
||||||
|
await showFutureLoadingDialog(
|
||||||
|
context: context,
|
||||||
|
future: () => Matrix.of(context).getLoginClient().login(
|
||||||
|
LoginType.mLoginToken,
|
||||||
|
token: token,
|
||||||
|
initialDeviceDisplayName: PlatformInfos.clientName,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<IdentityProvider>? get identityProviders {
|
||||||
|
final loginTypes = _rawLoginTypes;
|
||||||
|
if (loginTypes == null) return null;
|
||||||
|
final rawProviders = loginTypes.tryGetList('flows')!.singleWhere(
|
||||||
|
(flow) => flow['type'] == AuthenticationTypes.sso,
|
||||||
|
)['identity_providers'];
|
||||||
|
final list = (rawProviders as List)
|
||||||
|
.map((json) => IdentityProvider.fromJson(json))
|
||||||
|
.toList();
|
||||||
|
if (PlatformInfos.isCupertinoStyle) {
|
||||||
|
list.sort((a, b) => a.brand == 'apple' ? -1 : 1);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void login() => VRouter.of(context).to('login');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
homeserverFocusNode.addListener(_updateFocus);
|
|
||||||
_checkTorBrowser();
|
_checkTorBrowser();
|
||||||
super.initState();
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(checkHomeserverAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -204,3 +188,20 @@ class HomeserverPickerController extends State<HomeserverPicker> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IdentityProvider {
|
||||||
|
final String? id;
|
||||||
|
final String? name;
|
||||||
|
final String? icon;
|
||||||
|
final String? brand;
|
||||||
|
|
||||||
|
IdentityProvider({this.id, this.name, this.icon, this.brand});
|
||||||
|
|
||||||
|
factory IdentityProvider.fromJson(Map<String, dynamic> json) =>
|
||||||
|
IdentityProvider(
|
||||||
|
id: json['id'],
|
||||||
|
name: json['name'],
|
||||||
|
icon: json['icon'],
|
||||||
|
brand: json['brand'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
|
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
|
||||||
import '../../config/themes.dart';
|
import '../../config/themes.dart';
|
||||||
|
import '../../widgets/mxc_image.dart';
|
||||||
import 'homeserver_app_bar.dart';
|
import 'homeserver_app_bar.dart';
|
||||||
import 'homeserver_picker.dart';
|
import 'homeserver_picker.dart';
|
||||||
|
|
||||||
@ -16,15 +16,19 @@ class HomeserverPickerView extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final benchmarkResults = controller.benchmarkResults;
|
final identityProviders = controller.identityProviders;
|
||||||
|
final errorText = controller.error;
|
||||||
return LoginScaffold(
|
return LoginScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
titleSpacing: 12,
|
||||||
|
title: Padding(
|
||||||
|
padding: const EdgeInsets.all(0.0),
|
||||||
|
child: HomeserverAppBar(controller: controller),
|
||||||
|
),
|
||||||
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: HomeserverAppBar(controller: controller),
|
|
||||||
),
|
|
||||||
// display a prominent banner to import session for TOR browser
|
// display a prominent banner to import session for TOR browser
|
||||||
// users. This feature is just some UX sugar as TOR users are
|
// users. This feature is just some UX sugar as TOR users are
|
||||||
// usually forced to logout as TOR browser is non-persistent
|
// usually forced to logout as TOR browser is non-persistent
|
||||||
@ -49,112 +53,153 @@ class HomeserverPickerView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: controller.displayServerList
|
child: controller.isLoading
|
||||||
? ListView(
|
? const Center(child: CircularProgressIndicator.adaptive())
|
||||||
|
: ListView(
|
||||||
children: [
|
children: [
|
||||||
if (controller.displayServerList)
|
Image.asset(
|
||||||
|
'assets/info-logo.png',
|
||||||
|
height: 96,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Divider(
|
||||||
|
thickness: 1,
|
||||||
|
height: 1,
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Material(
|
child: Text(
|
||||||
borderRadius:
|
L10n.of(context)!.continueWith,
|
||||||
BorderRadius.circular(AppConfig.borderRadius),
|
style: const TextStyle(fontSize: 12),
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onInverseSurface,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
child: benchmarkResults == null
|
|
||||||
? const Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.all(12.0),
|
|
||||||
child: CircularProgressIndicator
|
|
||||||
.adaptive(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Column(
|
|
||||||
children: controller.filteredHomeservers
|
|
||||||
.map(
|
|
||||||
(server) => ListTile(
|
|
||||||
trailing: IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.info_outlined,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
onPressed: () => controller
|
|
||||||
.showServerInfo(server),
|
|
||||||
),
|
|
||||||
onTap: () => controller.setServer(
|
|
||||||
server.homeserver.baseUrl.host,
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
server.homeserver.baseUrl.host,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
Expanded(
|
||||||
server.homeserver.description ??
|
child: Divider(
|
||||||
'',
|
thickness: 1,
|
||||||
style: TextStyle(
|
height: 1,
|
||||||
color: Colors.grey.shade700,
|
color: Theme.of(context).dividerColor,
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
: Container(
|
),
|
||||||
alignment: Alignment.topCenter,
|
if (errorText != null) ...[
|
||||||
child: Image.asset(
|
const Center(
|
||||||
'assets/banner_transparent.png',
|
child: Icon(
|
||||||
filterQuality: FilterQuality.medium,
|
Icons.error_outline,
|
||||||
|
size: 48,
|
||||||
|
color: Colors.orange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
errorText,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
fontSize: 18,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SafeArea(
|
Center(
|
||||||
child: Container(
|
child: Text(
|
||||||
padding: const EdgeInsets.all(12),
|
L10n.of(context)!
|
||||||
width: double.infinity,
|
.pleaseTryAgainLaterOrChooseDifferentServer,
|
||||||
child: Column(
|
textAlign: TextAlign.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
style: TextStyle(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
color: Theme.of(context).colorScheme.error,
|
||||||
children: [
|
fontSize: 12,
|
||||||
TextButton(
|
),
|
||||||
onPressed: () => launchUrlString(AppConfig.privacyUrl),
|
),
|
||||||
child: Text(L10n.of(context)!.privacy),
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
],
|
||||||
|
if (identityProviders != null) ...[
|
||||||
|
...identityProviders.map(
|
||||||
|
(provider) => _LoginButton(
|
||||||
|
icon: provider.icon == null
|
||||||
|
? const Icon(Icons.open_in_new_outlined)
|
||||||
|
: Material(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
AppConfig.borderRadius,
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: MxcImage(
|
||||||
|
placeholder: (_) =>
|
||||||
|
const Icon(Icons.web_outlined),
|
||||||
|
uri: Uri.parse(provider.icon!),
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
label: provider.name ??
|
||||||
|
provider.brand ??
|
||||||
|
L10n.of(context)!.singlesignon,
|
||||||
|
onPressed: () =>
|
||||||
|
controller.ssoLoginAction(provider.id!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (controller.supportsPasswordLogin)
|
||||||
|
_LoginButton(
|
||||||
|
onPressed: controller.login,
|
||||||
|
icon: const Icon(Icons.login_outlined),
|
||||||
|
label: L10n.of(context)!.signInWithPassword,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
),
|
),
|
||||||
TextButton(
|
|
||||||
onPressed: controller.restoreBackup,
|
onPressed: controller.restoreBackup,
|
||||||
child: Text(L10n.of(context)!.hydrate),
|
child: Text(L10n.of(context)!.hydrate),
|
||||||
),
|
),
|
||||||
Hero(
|
|
||||||
tag: 'loginButton',
|
|
||||||
child: ElevatedButton.icon(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(context).colorScheme.primary,
|
|
||||||
foregroundColor:
|
|
||||||
Theme.of(context).colorScheme.onPrimary,
|
|
||||||
),
|
|
||||||
onPressed: controller.isLoading
|
|
||||||
? null
|
|
||||||
: controller.checkHomeserverAction,
|
|
||||||
icon: const Icon(Icons.start_outlined),
|
|
||||||
label: controller.isLoading
|
|
||||||
? const LinearProgressIndicator()
|
|
||||||
: Text(L10n.of(context)!.letsStart),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _LoginButton extends StatelessWidget {
|
||||||
|
final Widget icon;
|
||||||
|
final String label;
|
||||||
|
final void Function() onPressed;
|
||||||
|
|
||||||
|
const _LoginButton({
|
||||||
|
required this.icon,
|
||||||
|
required this.label,
|
||||||
|
required this.onPressed,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
margin: const EdgeInsets.only(bottom: 16),
|
||||||
|
width: double.infinity,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: onPressed,
|
||||||
|
icon: icon,
|
||||||
|
label: Text(label),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -109,6 +109,10 @@ class KeyVerificationPageState extends State<KeyVerificationDialog> {
|
|||||||
Widget body;
|
Widget body;
|
||||||
final buttons = <Widget>[];
|
final buttons = <Widget>[];
|
||||||
switch (widget.request.state) {
|
switch (widget.request.state) {
|
||||||
|
case KeyVerificationState.showQRSuccess:
|
||||||
|
case KeyVerificationState.confirmQRScan:
|
||||||
|
case KeyVerificationState.askChoice:
|
||||||
|
throw 'Not implemented';
|
||||||
case KeyVerificationState.askSSSS:
|
case KeyVerificationState.askSSSS:
|
||||||
// prompt the user for their ssss passphrase / key
|
// prompt the user for their ssss passphrase / key
|
||||||
final textEditingController = TextEditingController();
|
final textEditingController = TextEditingController();
|
||||||
|
|||||||
@ -59,7 +59,7 @@ class NewPrivateChatView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
QrImage(
|
QrImageView(
|
||||||
data:
|
data:
|
||||||
'https://matrix.to/#/${Matrix.of(context).client.userID}',
|
'https://matrix.to/#/${Matrix.of(context).client.userID}',
|
||||||
version: QrVersions.auto,
|
version: QrVersions.auto,
|
||||||
|
|||||||
@ -151,12 +151,11 @@ class EmotesSettingsController extends State<EmotesSettings> {
|
|||||||
|
|
||||||
bool isGloballyActive(Client? client) =>
|
bool isGloballyActive(Client? client) =>
|
||||||
room != null &&
|
room != null &&
|
||||||
client!.accountData['im.ponies.emote_rooms']?.content is Map &&
|
client!.accountData['im.ponies.emote_rooms']?.content
|
||||||
client.accountData['im.ponies.emote_rooms']!.content['rooms'] is Map &&
|
.tryGetMap<String, Object?>('rooms')
|
||||||
client.accountData['im.ponies.emote_rooms']!.content['rooms'][room!.id]
|
?.tryGetMap<String, Object?>(room!.id)
|
||||||
is Map &&
|
?.tryGetMap<String, Object?>(stateKey ?? '') !=
|
||||||
client.accountData['im.ponies.emote_rooms']!.content['rooms'][room!.id]
|
null;
|
||||||
[stateKey ?? ''] is Map;
|
|
||||||
|
|
||||||
bool get readonly =>
|
bool get readonly =>
|
||||||
room == null ? false : !(room!.canSendEvent('im.ponies.room_emotes'));
|
room == null ? false : !(room!.canSendEvent('im.ponies.room_emotes'));
|
||||||
|
|||||||
@ -40,16 +40,14 @@ class MultipleEmotesSettingsView extends StatelessWidget {
|
|||||||
itemCount: keys.length,
|
itemCount: keys.length,
|
||||||
itemBuilder: (BuildContext context, int i) {
|
itemBuilder: (BuildContext context, int i) {
|
||||||
final event = packs[keys[i]];
|
final event = packs[keys[i]];
|
||||||
String? packName = keys[i].isNotEmpty ? keys[i] : 'Default Pack';
|
final eventPack =
|
||||||
if (event != null && event.content['pack'] is Map) {
|
event?.content.tryGetMap<String, Object?>('pack');
|
||||||
if (event.content['pack']['displayname'] is String) {
|
final packName = eventPack?.tryGet<String>('displayname') ??
|
||||||
packName = event.content['pack']['displayname'];
|
eventPack?.tryGet<String>('name') ??
|
||||||
} else if (event.content['pack']['name'] is String) {
|
(keys[i].isNotEmpty ? keys[i] : 'Default Pack');
|
||||||
packName = event.content['pack']['name'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(packName!),
|
title: Text(packName),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
VRouter.of(context).toSegments(
|
VRouter.of(context).toSegments(
|
||||||
['rooms', room.id, 'details', 'emotes', keys[i]],
|
['rooms', room.id, 'details', 'emotes', keys[i]],
|
||||||
|
|||||||
@ -1,129 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
import 'package:vrouter/vrouter.dart';
|
|
||||||
|
|
||||||
import 'package:fluffychat/pages/sign_up/signup_view.dart';
|
|
||||||
import 'package:fluffychat/utils/platform_infos.dart';
|
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
|
||||||
import '../../utils/localized_exception_extension.dart';
|
|
||||||
|
|
||||||
class SignupPage extends StatefulWidget {
|
|
||||||
const SignupPage({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
SignupPageController createState() => SignupPageController();
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignupPageController extends State<SignupPage> {
|
|
||||||
final TextEditingController passwordController = TextEditingController();
|
|
||||||
final TextEditingController password2Controller = TextEditingController();
|
|
||||||
final TextEditingController emailController = TextEditingController();
|
|
||||||
String? error;
|
|
||||||
bool loading = false;
|
|
||||||
bool showPassword = false;
|
|
||||||
bool noEmailWarningConfirmed = false;
|
|
||||||
bool displaySecondPasswordField = false;
|
|
||||||
|
|
||||||
static const int minPassLength = 8;
|
|
||||||
|
|
||||||
void toggleShowPassword() => setState(() => showPassword = !showPassword);
|
|
||||||
|
|
||||||
String? get domain => VRouter.of(context).queryParameters['domain'];
|
|
||||||
|
|
||||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
|
||||||
|
|
||||||
void onPasswordType(String text) {
|
|
||||||
if (text.length >= minPassLength && !displaySecondPasswordField) {
|
|
||||||
setState(() {
|
|
||||||
displaySecondPasswordField = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String? password1TextFieldValidator(String? value) {
|
|
||||||
if (value!.isEmpty) {
|
|
||||||
return L10n.of(context)!.chooseAStrongPassword;
|
|
||||||
}
|
|
||||||
if (value.length < minPassLength) {
|
|
||||||
return L10n.of(context)!
|
|
||||||
.pleaseChooseAtLeastChars(minPassLength.toString());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? password2TextFieldValidator(String? value) {
|
|
||||||
if (value!.isEmpty) {
|
|
||||||
return L10n.of(context)!.repeatPassword;
|
|
||||||
}
|
|
||||||
if (value != passwordController.text) {
|
|
||||||
return L10n.of(context)!.passwordsDoNotMatch;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? emailTextFieldValidator(String? value) {
|
|
||||||
if (value!.isEmpty && !noEmailWarningConfirmed) {
|
|
||||||
noEmailWarningConfirmed = true;
|
|
||||||
return L10n.of(context)!.noEmailWarning;
|
|
||||||
}
|
|
||||||
if (value.isNotEmpty && !value.contains('@')) {
|
|
||||||
return L10n.of(context)!.pleaseEnterValidEmail;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void signup([_]) async {
|
|
||||||
setState(() {
|
|
||||||
error = null;
|
|
||||||
});
|
|
||||||
if (!formKey.currentState!.validate()) return;
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
loading = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
final client = Matrix.of(context).getLoginClient();
|
|
||||||
final email = emailController.text;
|
|
||||||
if (email.isNotEmpty) {
|
|
||||||
Matrix.of(context).currentClientSecret =
|
|
||||||
DateTime.now().millisecondsSinceEpoch.toString();
|
|
||||||
Matrix.of(context).currentThreepidCreds =
|
|
||||||
await client.requestTokenToRegisterEmail(
|
|
||||||
Matrix.of(context).currentClientSecret,
|
|
||||||
email,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final displayname = Matrix.of(context).loginUsername!;
|
|
||||||
final localPart = displayname.toLowerCase().replaceAll(' ', '_');
|
|
||||||
|
|
||||||
await client.uiaRequestBackground(
|
|
||||||
(auth) => client.register(
|
|
||||||
username: localPart,
|
|
||||||
password: passwordController.text,
|
|
||||||
initialDeviceDisplayName: PlatformInfos.clientName,
|
|
||||||
auth: auth,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
// Set displayname
|
|
||||||
if (displayname != localPart) {
|
|
||||||
await client.setDisplayName(
|
|
||||||
client.userID!,
|
|
||||||
displayname,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
error = (e).toLocalizedString(context);
|
|
||||||
} finally {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() => loading = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => SignupPageView(this);
|
|
||||||
}
|
|
||||||
@ -1,115 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
|
||||||
|
|
||||||
import 'package:fluffychat/widgets/layouts/login_scaffold.dart';
|
|
||||||
import 'signup.dart';
|
|
||||||
|
|
||||||
class SignupPageView extends StatelessWidget {
|
|
||||||
final SignupPageController controller;
|
|
||||||
const SignupPageView(this.controller, {Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return LoginScaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: controller.loading ? null : const BackButton(),
|
|
||||||
automaticallyImplyLeading: !controller.loading,
|
|
||||||
title: Text(L10n.of(context)!.signUp),
|
|
||||||
),
|
|
||||||
body: Form(
|
|
||||||
key: controller.formKey,
|
|
||||||
child: ListView(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: TextFormField(
|
|
||||||
readOnly: controller.loading,
|
|
||||||
autocorrect: false,
|
|
||||||
onChanged: controller.onPasswordType,
|
|
||||||
autofillHints:
|
|
||||||
controller.loading ? null : [AutofillHints.newPassword],
|
|
||||||
controller: controller.passwordController,
|
|
||||||
obscureText: !controller.showPassword,
|
|
||||||
validator: controller.password1TextFieldValidator,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.vpn_key_outlined),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
tooltip: L10n.of(context)!.showPassword,
|
|
||||||
icon: Icon(
|
|
||||||
controller.showPassword
|
|
||||||
? Icons.visibility_off_outlined
|
|
||||||
: Icons.visibility_outlined,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
onPressed: controller.toggleShowPassword,
|
|
||||||
),
|
|
||||||
errorStyle: const TextStyle(color: Colors.orange),
|
|
||||||
hintText: L10n.of(context)!.chooseAStrongPassword,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (controller.displaySecondPasswordField)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: TextFormField(
|
|
||||||
readOnly: controller.loading,
|
|
||||||
autocorrect: false,
|
|
||||||
autofillHints:
|
|
||||||
controller.loading ? null : [AutofillHints.newPassword],
|
|
||||||
controller: controller.password2Controller,
|
|
||||||
obscureText: !controller.showPassword,
|
|
||||||
validator: controller.password2TextFieldValidator,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.repeat_outlined),
|
|
||||||
hintText: L10n.of(context)!.repeatPassword,
|
|
||||||
errorStyle: const TextStyle(color: Colors.orange),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: TextFormField(
|
|
||||||
readOnly: controller.loading,
|
|
||||||
autocorrect: false,
|
|
||||||
controller: controller.emailController,
|
|
||||||
keyboardType: TextInputType.emailAddress,
|
|
||||||
autofillHints:
|
|
||||||
controller.loading ? null : [AutofillHints.username],
|
|
||||||
validator: controller.emailTextFieldValidator,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: const Icon(Icons.mail_outlined),
|
|
||||||
hintText: L10n.of(context)!.enterAnEmailAddress,
|
|
||||||
errorText: controller.error,
|
|
||||||
errorMaxLines: 4,
|
|
||||||
errorStyle: TextStyle(
|
|
||||||
color: controller.emailController.text.isEmpty
|
|
||||||
? Colors.orangeAccent
|
|
||||||
: Colors.orange,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Hero(
|
|
||||||
tag: 'loginButton',
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: ElevatedButton.icon(
|
|
||||||
icon: const Icon(Icons.person_add_outlined),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
onPressed: controller.loading ? () {} : controller.signup,
|
|
||||||
label: controller.loading
|
|
||||||
? const LinearProgressIndicator()
|
|
||||||
: Text(L10n.of(context)!.signUp),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -182,8 +182,12 @@ class MatrixLocals extends MatrixLocalizations {
|
|||||||
String get noPermission => l10n.noKeyForThisMessage;
|
String get noPermission => l10n.noKeyForThisMessage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String redactedAnEvent(String senderName) {
|
String redactedAnEvent(Event redactedEvent) {
|
||||||
return l10n.redactedAnEvent(senderName);
|
return l10n.redactedAnEvent(
|
||||||
|
redactedEvent.redactedBecause?.senderFromMemoryOrFallback
|
||||||
|
.calcLocalizedBodyFallback(this) ??
|
||||||
|
l10n.user,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -192,8 +196,10 @@ class MatrixLocals extends MatrixLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String removedBy(String calcDisplayname) {
|
String removedBy(Event redactedEvent) {
|
||||||
return l10n.removedBy(calcDisplayname);
|
return l10n.removedBy(
|
||||||
|
redactedEvent.senderFromMemoryOrFallback.calcLocalizedBodyFallback(this),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -198,11 +198,33 @@ Future<void> _tryPushHelper(
|
|||||||
|
|
||||||
final roomName = event.room.getLocalizedDisplayname(MatrixLocals(l10n));
|
final roomName = event.room.getLocalizedDisplayname(MatrixLocals(l10n));
|
||||||
|
|
||||||
|
final notificationGroupId =
|
||||||
|
event.room.isDirectChat ? 'directChats' : 'groupChats';
|
||||||
|
final groupName = event.room.isDirectChat ? l10n.directChats : l10n.groups;
|
||||||
|
|
||||||
|
final messageRooms = AndroidNotificationChannelGroup(
|
||||||
|
notificationGroupId,
|
||||||
|
groupName,
|
||||||
|
);
|
||||||
|
final roomsChannel = AndroidNotificationChannel(
|
||||||
|
event.room.id,
|
||||||
|
roomName,
|
||||||
|
groupId: notificationGroupId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin
|
||||||
|
.resolvePlatformSpecificImplementation<
|
||||||
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
|
?.createNotificationChannelGroup(messageRooms);
|
||||||
|
await flutterLocalNotificationsPlugin
|
||||||
|
.resolvePlatformSpecificImplementation<
|
||||||
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
|
?.createNotificationChannel(roomsChannel);
|
||||||
|
|
||||||
final androidPlatformChannelSpecifics = AndroidNotificationDetails(
|
final androidPlatformChannelSpecifics = AndroidNotificationDetails(
|
||||||
event.room.id,
|
event.room.id,
|
||||||
roomName,
|
roomName,
|
||||||
channelDescription:
|
channelDescription: groupName,
|
||||||
event.room.isDirectChat ? l10n.directChats : l10n.groups,
|
|
||||||
number: notification.counts?.unread,
|
number: notification.counts?.unread,
|
||||||
category: AndroidNotificationCategory.message,
|
category: AndroidNotificationCategory.message,
|
||||||
styleInformation: messagingStyleInformation ??
|
styleInformation: messagingStyleInformation ??
|
||||||
@ -215,7 +237,7 @@ Future<void> _tryPushHelper(
|
|||||||
ticker: l10n.unreadChats(notification.counts?.unread ?? 1),
|
ticker: l10n.unreadChats(notification.counts?.unread ?? 1),
|
||||||
importance: Importance.max,
|
importance: Importance.max,
|
||||||
priority: Priority.max,
|
priority: Priority.max,
|
||||||
groupKey: event.room.id,
|
groupKey: notificationGroupId,
|
||||||
);
|
);
|
||||||
const iOSPlatformChannelSpecifics = DarwinNotificationDetails();
|
const iOSPlatformChannelSpecifics = DarwinNotificationDetails();
|
||||||
final platformChannelSpecifics = NotificationDetails(
|
final platformChannelSpecifics = NotificationDetails(
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||||
import 'package:collection/collection.dart' show IterableExtension;
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
@ -37,14 +38,34 @@ class UrlLauncher {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final consent = await showOkCancelAlertDialog(
|
final consent = await showModalActionSheet<_LaunchUrlResponse>(
|
||||||
context: context,
|
context: context,
|
||||||
title: L10n.of(context)!.openLinkInBrowser,
|
title: url,
|
||||||
message: url,
|
style: AdaptiveStyle.material,
|
||||||
okLabel: L10n.of(context)!.ok,
|
actions: [
|
||||||
cancelLabel: L10n.of(context)!.cancel,
|
SheetAction(
|
||||||
|
key: _LaunchUrlResponse.copy,
|
||||||
|
icon: Icons.copy_outlined,
|
||||||
|
label: L10n.of(context)!.copy,
|
||||||
|
),
|
||||||
|
SheetAction(
|
||||||
|
key: _LaunchUrlResponse.launch,
|
||||||
|
icon: Icons.launch_outlined,
|
||||||
|
label: L10n.of(context)!.openLinkInBrowser,
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
if (consent != OkCancelResult.ok) return;
|
if (consent == _LaunchUrlResponse.copy) {
|
||||||
|
await Clipboard.setData(ClipboardData(text: uri.toString()));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(L10n.of(context)!.copiedToClipboard),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (consent != _LaunchUrlResponse.launch) return;
|
||||||
|
|
||||||
if (!{'https', 'http'}.contains(uri.scheme)) {
|
if (!{'https', 'http'}.contains(uri.scheme)) {
|
||||||
// just launch non-https / non-http uris directly
|
// just launch non-https / non-http uris directly
|
||||||
@ -215,3 +236,8 @@ class UrlLauncher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum _LaunchUrlResponse {
|
||||||
|
launch,
|
||||||
|
copy,
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
import 'package:fluffychat/config/themes.dart';
|
import 'package:fluffychat/config/themes.dart';
|
||||||
|
import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
|
|
||||||
class LoginScaffold extends StatelessWidget {
|
class LoginScaffold extends StatelessWidget {
|
||||||
final Widget body;
|
final Widget body;
|
||||||
@ -17,6 +21,7 @@ class LoginScaffold extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isMobileMode = !FluffyThemes.isColumnMode(context);
|
final isMobileMode = !FluffyThemes.isColumnMode(context);
|
||||||
final scaffold = Scaffold(
|
final scaffold = Scaffold(
|
||||||
|
key: const Key('LoginScaffold'),
|
||||||
backgroundColor: isMobileMode ? null : Colors.transparent,
|
backgroundColor: isMobileMode ? null : Colors.transparent,
|
||||||
appBar: appBar == null
|
appBar: appBar == null
|
||||||
? null
|
? null
|
||||||
@ -33,31 +38,93 @@ class LoginScaffold extends StatelessWidget {
|
|||||||
extendBodyBehindAppBar: true,
|
extendBodyBehindAppBar: true,
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
body: body,
|
body: body,
|
||||||
|
bottomNavigationBar: isMobileMode
|
||||||
|
? Material(
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
child: const _PrivacyButtons(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
if (isMobileMode) return scaffold;
|
if (isMobileMode) return scaffold;
|
||||||
return Container(
|
return Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
gradient: FluffyThemes.backgroundGradient(context, 156),
|
||||||
image: AssetImage('assets/login_wallpaper.png'),
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 64),
|
||||||
|
Expanded(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.925),
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
elevation: 10,
|
elevation:
|
||||||
shadowColor: Colors.black,
|
Theme.of(context).appBarTheme.scrolledUnderElevation ?? 4,
|
||||||
|
shadowColor: Theme.of(context).appBarTheme.shadowColor,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: isMobileMode
|
constraints: isMobileMode
|
||||||
? const BoxConstraints()
|
? const BoxConstraints()
|
||||||
: const BoxConstraints(maxWidth: 480, maxHeight: 640),
|
: const BoxConstraints(maxWidth: 960, maxHeight: 640),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/login_wallpaper.png',
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 1,
|
||||||
|
color: Theme.of(context).dividerTheme.color,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: scaffold,
|
child: scaffold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const _PrivacyButtons(mainAxisAlignment: MainAxisAlignment.end),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PrivacyButtons extends StatelessWidget {
|
||||||
|
final MainAxisAlignment mainAxisAlignment;
|
||||||
|
const _PrivacyButtons({required this.mainAxisAlignment});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 64,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: mainAxisAlignment,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => PlatformInfos.showDialog(context),
|
||||||
|
child: Text(L10n.of(context)!.about),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => launchUrlString(AppConfig.privacyUrl),
|
||||||
|
child: Text(L10n.of(context)!.privacy),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -10,7 +10,9 @@
|
|||||||
#include <desktop_lifecycle/desktop_lifecycle_plugin.h>
|
#include <desktop_lifecycle/desktop_lifecycle_plugin.h>
|
||||||
#include <dynamic_color/dynamic_color_plugin.h>
|
#include <dynamic_color/dynamic_color_plugin.h>
|
||||||
#include <emoji_picker_flutter/emoji_picker_flutter_plugin.h>
|
#include <emoji_picker_flutter/emoji_picker_flutter_plugin.h>
|
||||||
|
#include <file_selector_linux/file_selector_plugin.h>
|
||||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||||
|
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||||
#include <handy_window/handy_window_plugin.h>
|
#include <handy_window/handy_window_plugin.h>
|
||||||
#include <record_linux/record_linux_plugin.h>
|
#include <record_linux/record_linux_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
@ -29,9 +31,15 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) emoji_picker_flutter_registrar =
|
g_autoptr(FlPluginRegistrar) emoji_picker_flutter_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "EmojiPickerFlutterPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "EmojiPickerFlutterPlugin");
|
||||||
emoji_picker_flutter_plugin_register_with_registrar(emoji_picker_flutter_registrar);
|
emoji_picker_flutter_plugin_register_with_registrar(emoji_picker_flutter_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
|
||||||
|
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
|
||||||
|
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) handy_window_registrar =
|
g_autoptr(FlPluginRegistrar) handy_window_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "HandyWindowPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "HandyWindowPlugin");
|
||||||
handy_window_plugin_register_with_registrar(handy_window_registrar);
|
handy_window_plugin_register_with_registrar(handy_window_registrar);
|
||||||
|
|||||||
@ -7,7 +7,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
desktop_lifecycle
|
desktop_lifecycle
|
||||||
dynamic_color
|
dynamic_color
|
||||||
emoji_picker_flutter
|
emoji_picker_flutter
|
||||||
|
file_selector_linux
|
||||||
flutter_secure_storage_linux
|
flutter_secure_storage_linux
|
||||||
|
flutter_webrtc
|
||||||
handy_window
|
handy_window
|
||||||
record_linux
|
record_linux
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import desktop_lifecycle
|
|||||||
import device_info_plus
|
import device_info_plus
|
||||||
import dynamic_color
|
import dynamic_color
|
||||||
import emoji_picker_flutter
|
import emoji_picker_flutter
|
||||||
|
import file_selector_macos
|
||||||
import flutter_app_badger
|
import flutter_app_badger
|
||||||
import flutter_local_notifications
|
import flutter_local_notifications
|
||||||
import flutter_secure_storage_macos
|
import flutter_secure_storage_macos
|
||||||
@ -19,15 +20,18 @@ import flutter_web_auth_2
|
|||||||
import flutter_webrtc
|
import flutter_webrtc
|
||||||
import geolocator_apple
|
import geolocator_apple
|
||||||
import just_audio
|
import just_audio
|
||||||
|
import macos_ui
|
||||||
|
import macos_window_utils
|
||||||
import package_info_plus
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import record_macos
|
import record_macos
|
||||||
import share_plus
|
import share_plus
|
||||||
import shared_preferences_macos
|
import shared_preferences_foundation
|
||||||
import sqflite
|
import sqflite
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import video_compress
|
import video_compress
|
||||||
import wakelock_macos
|
import wakelock_macos
|
||||||
|
import wakelock_plus
|
||||||
import window_to_front
|
import window_to_front
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
@ -38,6 +42,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||||
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
|
EmojiPickerFlutterPlugin.register(with: registry.registrar(forPlugin: "EmojiPickerFlutterPlugin"))
|
||||||
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FlutterAppBadgerPlugin.register(with: registry.registrar(forPlugin: "FlutterAppBadgerPlugin"))
|
FlutterAppBadgerPlugin.register(with: registry.registrar(forPlugin: "FlutterAppBadgerPlugin"))
|
||||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||||
@ -45,6 +50,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||||
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
|
||||||
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
|
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
|
||||||
|
MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin"))
|
||||||
|
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
||||||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
RecordMacosPlugin.register(with: registry.registrar(forPlugin: "RecordMacosPlugin"))
|
RecordMacosPlugin.register(with: registry.registrar(forPlugin: "RecordMacosPlugin"))
|
||||||
@ -54,5 +61,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))
|
VideoCompressPlugin.register(with: registry.registrar(forPlugin: "VideoCompressPlugin"))
|
||||||
WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))
|
WakelockMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockMacosPlugin"))
|
||||||
|
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
|
||||||
WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin"))
|
WindowToFrontPlugin.register(with: registry.registrar(forPlugin: "WindowToFrontPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
617
pubspec.lock
617
pubspec.lock
File diff suppressed because it is too large
Load Diff
36
pubspec.yaml
36
pubspec.yaml
@ -7,19 +7,19 @@ environment:
|
|||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
adaptive_dialog: ^1.9.0-no-macos.2
|
adaptive_dialog: ^1.9.0-x-macos-beta.1
|
||||||
animations: ^2.0.7
|
animations: ^2.0.7
|
||||||
badges: ^2.0.3
|
badges: ^2.0.3
|
||||||
blurhash_dart: ^1.1.0
|
blurhash_dart: ^1.1.0
|
||||||
callkeep: ^0.3.2
|
callkeep: ^0.3.2
|
||||||
chewie: ^1.3.6
|
chewie: ^1.3.6
|
||||||
collection: ^1.16.0
|
collection: ^1.16.0
|
||||||
connectivity_plus: ^3.0.2
|
connectivity_plus: ^4.0.1
|
||||||
cupertino_icons: any
|
cupertino_icons: any
|
||||||
desktop_drop: ^0.4.0
|
desktop_drop: ^0.4.0
|
||||||
desktop_lifecycle: ^0.1.0
|
desktop_lifecycle: ^0.1.0
|
||||||
desktop_notifications: ^0.6.3
|
desktop_notifications: ^0.6.3
|
||||||
device_info_plus: ^8.0.0
|
device_info_plus: ^9.0.2
|
||||||
dynamic_color: ^1.6.0
|
dynamic_color: ^1.6.0
|
||||||
emoji_picker_flutter: ^1.5.1
|
emoji_picker_flutter: ^1.5.1
|
||||||
emoji_proposal: ^0.0.1
|
emoji_proposal: ^0.0.1
|
||||||
@ -32,37 +32,37 @@ dependencies:
|
|||||||
flutter_app_lock: ^3.0.0
|
flutter_app_lock: ^3.0.0
|
||||||
flutter_blurhash: ^0.7.0
|
flutter_blurhash: ^0.7.0
|
||||||
flutter_cache_manager: ^3.3.0
|
flutter_cache_manager: ^3.3.0
|
||||||
flutter_foreground_task: ^3.10.0
|
flutter_foreground_task: ^6.0.0+1
|
||||||
flutter_highlighter: ^0.1.1
|
flutter_highlighter: ^0.1.1
|
||||||
flutter_html: ^3.0.0-beta.2
|
flutter_html: ^3.0.0-beta.2
|
||||||
flutter_html_table: ^3.0.0-beta.2
|
flutter_html_table: ^3.0.0-beta.2
|
||||||
flutter_linkify: ^6.0.0
|
flutter_linkify: ^6.0.0
|
||||||
flutter_local_notifications: ^12.0.2
|
flutter_local_notifications: ^15.1.0+1
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_map: ^3.1.0
|
flutter_map: ^4.0.0
|
||||||
flutter_math_fork: ^0.7.1
|
flutter_math_fork: ^0.7.1
|
||||||
flutter_olm: ^1.2.0
|
flutter_olm: ^1.2.0
|
||||||
flutter_openssl_crypto: ^0.1.0
|
flutter_openssl_crypto: ^0.1.0
|
||||||
flutter_ringtone_player: ^3.1.1
|
flutter_ringtone_player: ^3.1.1
|
||||||
flutter_secure_storage: ^7.0.1
|
flutter_secure_storage: ^8.0.0
|
||||||
flutter_typeahead: ^4.3.2
|
flutter_typeahead: ^4.3.2
|
||||||
flutter_web_auth_2: ^2.1.1
|
flutter_web_auth_2: ^2.1.1
|
||||||
flutter_webrtc: ^0.9.30+hotfix.2
|
flutter_webrtc: ^0.9.35
|
||||||
future_loading_dialog: ^0.2.3
|
future_loading_dialog: ^0.2.3
|
||||||
geolocator: ^7.6.2
|
geolocator: ^7.6.2
|
||||||
handy_window: ^0.1.9
|
handy_window: ^0.3.1
|
||||||
hive: ^2.2.3
|
hive: ^2.2.3
|
||||||
hive_flutter: ^1.1.0
|
hive_flutter: ^1.1.0
|
||||||
http: ^0.13.4
|
http: ^0.13.4
|
||||||
image_picker: ^0.8.4+8
|
image_picker: ^1.0.0
|
||||||
intl: any
|
intl: any
|
||||||
just_audio: ^0.9.30
|
just_audio: ^0.9.30
|
||||||
just_audio_mpv: ^0.1.6
|
just_audio_mpv: ^0.1.6
|
||||||
keyboard_shortcuts: ^0.1.4
|
keyboard_shortcuts: ^0.1.4
|
||||||
latlong2: ^0.8.1
|
latlong2: ^0.8.1
|
||||||
linkify: ^5.0.0
|
linkify: ^5.0.0
|
||||||
matrix: ^0.20.5
|
matrix: ^0.22.0
|
||||||
matrix_homeserver_recommendations: ^0.3.0
|
matrix_homeserver_recommendations: ^0.3.0
|
||||||
native_imaging: ^0.1.0
|
native_imaging: ^0.1.0
|
||||||
package_info_plus: ^4.0.0
|
package_info_plus: ^4.0.0
|
||||||
@ -77,12 +77,12 @@ dependencies:
|
|||||||
record: ^4.4.4
|
record: ^4.4.4
|
||||||
scroll_to_index: ^3.0.1
|
scroll_to_index: ^3.0.1
|
||||||
share_plus: ^7.0.0
|
share_plus: ^7.0.0
|
||||||
shared_preferences: 2.0.15 # Pinned because https://github.com/flutter/flutter/issues/118401
|
shared_preferences: ^2.2.0 # Pinned because https://github.com/flutter/flutter/issues/118401
|
||||||
slugify: ^2.0.0
|
slugify: ^2.0.0
|
||||||
swipe_to_action: ^0.2.0
|
swipe_to_action: ^0.2.0
|
||||||
tor_detector_web: ^1.1.0
|
tor_detector_web: ^1.1.0
|
||||||
uni_links: ^0.5.1
|
uni_links: ^0.5.1
|
||||||
unifiedpush: ^4.0.3
|
unifiedpush: ^5.0.0
|
||||||
universal_html: ^2.0.8
|
universal_html: ^2.0.8
|
||||||
url_launcher: ^6.0.20
|
url_launcher: ^6.0.20
|
||||||
vibration: ^1.7.4-nullsafety.0
|
vibration: ^1.7.4-nullsafety.0
|
||||||
@ -93,7 +93,7 @@ dependencies:
|
|||||||
webrtc_interface: ^1.0.13
|
webrtc_interface: ^1.0.13
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
dart_code_metrics: ^4.10.1
|
dart_code_metrics: ^5.7.5
|
||||||
flutter_lints: ^2.0.1
|
flutter_lints: ^2.0.1
|
||||||
flutter_native_splash: ^2.0.3+1
|
flutter_native_splash: ^2.0.3+1
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -153,8 +153,6 @@ dependency_overrides:
|
|||||||
git:
|
git:
|
||||||
url: https://gitlab.com/TheOneWithTheBraid/flutter_secure_storage_windows.git
|
url: https://gitlab.com/TheOneWithTheBraid/flutter_secure_storage_windows.git
|
||||||
ref: main
|
ref: main
|
||||||
flutter_webrtc:
|
|
||||||
git: https://github.com/krille-chan/flutter-webrtc.git
|
|
||||||
geolocator_android:
|
geolocator_android:
|
||||||
hosted:
|
hosted:
|
||||||
name: geolocator_android
|
name: geolocator_android
|
||||||
@ -169,6 +167,6 @@ dependency_overrides:
|
|||||||
# https://github.com/creativecreatorormaybenot/wakelock/pull/203
|
# https://github.com/creativecreatorormaybenot/wakelock/pull/203
|
||||||
wakelock_windows:
|
wakelock_windows:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/timsneath/wakelock.git
|
url: https://github.com/chandrabezzo/wakelock.git
|
||||||
ref: 2a9bca63a540771f241d688562351482b2cf234c
|
ref: main
|
||||||
path: wakelock_windows
|
path: wakelock_windows/
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
#include <desktop_lifecycle/desktop_lifecycle_plugin.h>
|
#include <desktop_lifecycle/desktop_lifecycle_plugin.h>
|
||||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||||
#include <emoji_picker_flutter/emoji_picker_flutter_plugin_c_api.h>
|
#include <emoji_picker_flutter/emoji_picker_flutter_plugin_c_api.h>
|
||||||
|
#include <file_selector_windows/file_selector_windows.h>
|
||||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
#include <record_windows/record_windows_plugin_c_api.h>
|
#include <record_windows/record_windows_plugin_c_api.h>
|
||||||
@ -29,6 +30,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
||||||
EmojiPickerFlutterPluginCApiRegisterWithRegistrar(
|
EmojiPickerFlutterPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi"));
|
registry->GetRegistrarForPlugin("EmojiPickerFlutterPluginCApi"));
|
||||||
|
FileSelectorWindowsRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("FileSelectorWindows"));
|
||||||
FlutterWebRTCPluginRegisterWithRegistrar(
|
FlutterWebRTCPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
|
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
|
||||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||||
|
|||||||
@ -8,6 +8,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
desktop_lifecycle
|
desktop_lifecycle
|
||||||
dynamic_color
|
dynamic_color
|
||||||
emoji_picker_flutter
|
emoji_picker_flutter
|
||||||
|
file_selector_windows
|
||||||
flutter_webrtc
|
flutter_webrtc
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
record_windows
|
record_windows
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user