mirror of
https://gitlab.gnome.org/World/fractal.git
synced 2025-08-14 00:02:20 -04:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e3c34328ee | ||
|
0e9d34dd9d | ||
|
4597519128 | ||
|
8a7c690d21 | ||
|
07cc8f9787 | ||
|
2a09a76fb0 | ||
|
ddc5001a79 | ||
|
ae53630df3 | ||
|
6f090f3883 | ||
|
b343862f35 | ||
|
c7c9d5d974 | ||
|
10ed8358f9 | ||
|
0be7615056 | ||
|
a0955e225a | ||
|
2ae6f75938 | ||
|
420ec4d24e | ||
|
7bd9e7fa45 | ||
|
e9f6873d8a | ||
|
9dcee3a6ac |
472
Cargo.lock
generated
472
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "fractal"
|
||||
version = "12.0.0-rc"
|
||||
version = "12.0.0"
|
||||
authors = ["Julian Sparber <julian@sparber.net>"]
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
@ -61,7 +61,7 @@ zeroize = "1"
|
||||
|
||||
# gtk-rs project and dependents. These usually need to be updated together.
|
||||
adw = { package = "libadwaita", version = "0.7", features = ["v1_7"] }
|
||||
glycin = { version = "2", default-features = false, features = ["tokio", "gdk4"] }
|
||||
glycin = { version = "3.0.0-beta.1", default-features = false, features = ["tokio", "gdk4"] }
|
||||
gst = { version = "0.23", package = "gstreamer" }
|
||||
gst_app = { version = "0.23", package = "gstreamer-app" }
|
||||
gst_pbutils = { version = "0.23", package = "gstreamer-pbutils" }
|
||||
@ -74,23 +74,23 @@ sourceview = { package = "sourceview5", version = "0.9" }
|
||||
[dependencies.matrix-sdk]
|
||||
# version = "0.13"
|
||||
git = "https://github.com/matrix-org/matrix-rust-sdk.git"
|
||||
rev = "ada68e11144507afc9d178f4264452aae1ff9e27"
|
||||
rev = "a9ce1c6e5822b8eb8411c5bc257049d9a9d15884"
|
||||
features = ["socks", "sso-login", "markdown", "qrcode"]
|
||||
|
||||
[dependencies.matrix-sdk-store-encryption]
|
||||
# version = "0.13"
|
||||
git = "https://github.com/matrix-org/matrix-rust-sdk.git"
|
||||
rev = "ada68e11144507afc9d178f4264452aae1ff9e27"
|
||||
rev = "a9ce1c6e5822b8eb8411c5bc257049d9a9d15884"
|
||||
|
||||
[dependencies.matrix-sdk-ui]
|
||||
# version = "0.13"
|
||||
git = "https://github.com/matrix-org/matrix-rust-sdk.git"
|
||||
rev = "ada68e11144507afc9d178f4264452aae1ff9e27"
|
||||
rev = "a9ce1c6e5822b8eb8411c5bc257049d9a9d15884"
|
||||
|
||||
[dependencies.ruma]
|
||||
# version = "0.12.5"
|
||||
git = "https://github.com/ruma/ruma.git"
|
||||
rev = "de19ebaf71af620eb17abaefd92e43153f9d041d"
|
||||
rev = "a2fe858133ba932b4bda730dc7472c9c985739a0"
|
||||
features = [
|
||||
"client-api-c",
|
||||
"markdown",
|
||||
|
@ -38,7 +38,7 @@ development version while keeping the stable release around for daily use.
|
||||
|
||||
### Stable version
|
||||
|
||||
The current stable version is 11.2 (released June 10th 2025).
|
||||
The current stable version is 12 (released August 11th 2025).
|
||||
|
||||
You can get the official Fractal Flatpak from Flathub.
|
||||
|
||||
@ -53,7 +53,7 @@ You can get the official Fractal Flatpak from Flathub.
|
||||
|
||||
### Beta version
|
||||
|
||||
The current beta version is 12.rc (released July 31st 2025).
|
||||
The current beta version is 12 (same as stable).
|
||||
|
||||
It is available as a Flatpak on Flathub Beta.
|
||||
|
||||
|
@ -95,27 +95,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "glycin-loaders",
|
||||
"buildsystem": "meson",
|
||||
"config-opts": [
|
||||
"-Dtests=false",
|
||||
"-Dlibglycin=false",
|
||||
"-Dintrospection=false",
|
||||
"-Dvapi=false",
|
||||
"-Dcapi_docs=false",
|
||||
"-Dpython_tests=false"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.gnome.org/sophie-h/glycin.git",
|
||||
"tag": "1.2.2",
|
||||
"commit": "c7d362287303944721cf583d4d9e9f7721bfa407",
|
||||
"disable-submodules": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fractal",
|
||||
"buildsystem": "meson",
|
||||
|
@ -35,15 +35,15 @@
|
||||
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image type="source">https://gitlab.gnome.org/World/fractal/raw/fractal-11/screenshots/main.png</image>
|
||||
<image type="source">https://gitlab.gnome.org/World/fractal/raw/fractal-12/screenshots/main.png</image>
|
||||
<caption>Fractal’s main window</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image type="source">https://gitlab.gnome.org/World/fractal/raw/fractal-11/screenshots/media-history.png</image>
|
||||
<image type="source">https://gitlab.gnome.org/World/fractal/raw/fractal-12/screenshots/media-history.png</image>
|
||||
<caption>View the media history of a Matrix room</caption>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image type="source">https://gitlab.gnome.org/World/fractal/raw/fractal-11/screenshots/adaptive.png</image>
|
||||
<image type="source">https://gitlab.gnome.org/World/fractal/raw/fractal-12/screenshots/adaptive.png</image>
|
||||
<caption>Fractal’s interface adapts to small screens</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
@ -71,66 +71,44 @@
|
||||
</content_rating>
|
||||
|
||||
<releases>@development-release@
|
||||
<release version="12~rc" type="development" date="2025-07-31">
|
||||
<release version="12" type="stable" date="2025-08-11">
|
||||
<description>
|
||||
<p>
|
||||
Want to get a head start and try out Fractal 12 before its release? That’s what this
|
||||
Release Candidate is for! New since 12.beta:
|
||||
Knock, knock, knock… on rooms, baby 🎵 Ooh ooh ooh ooh ooh ooh 🎶 That's right, Fractal 12
|
||||
adds support for knocking, among other things. Read all about the improvements since 11.2:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
The upcoming room version 12 is supported, with the special power level of room creators
|
||||
Requesting invites to rooms (aka knocking) is now possible, as is enabling such requests
|
||||
for room admins.
|
||||
</li>
|
||||
<li>
|
||||
Requesting invites to rooms (aka knocking) is now possible
|
||||
</li>
|
||||
<li>
|
||||
Clicking on the name of the sender of a message adds a mention to them in the composer
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
As usual, this release includes other improvements, fixes and new translations thanks to
|
||||
all our contributors, and our upstream projects.
|
||||
</p>
|
||||
<p>
|
||||
As the version implies, it should be mostly stable and we expect to only include minor
|
||||
improvements until the release of Fractal 12.
|
||||
</p>
|
||||
</description>
|
||||
</release>
|
||||
<release version="12~beta" type="development" date="2025-06-26">
|
||||
<description>
|
||||
<p>
|
||||
Hot! Hot! Hot! No, we are not talking about the summer weather in the northern hemisphere,
|
||||
but about the brand new release of Fractal 12.beta! Coming soon to your device:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
The safety setting to hide media previews in rooms is now synced between Matrix
|
||||
clients.
|
||||
</li>
|
||||
<li>
|
||||
We added another safety setting (which is also synced) to hide avatars in invites.
|
||||
The upcoming room version 12 is supported, with the special power level of room
|
||||
creators.
|
||||
</li>
|
||||
<li>
|
||||
A room can be marked as unread via the context menu in the sidebar.
|
||||
</li>
|
||||
<li>
|
||||
We changed the UX a little for tombstoned rooms. Instead of showing a banner at the top
|
||||
of the history, it now replaces the composer at the bottom of the history.
|
||||
</li>
|
||||
<li>
|
||||
You can now see if a section in the sidebar has any notifications or activity when it is
|
||||
collapsed.
|
||||
</li>
|
||||
<li>
|
||||
Clicking on the name of the sender of a message adds a mention to them in the composer.
|
||||
</li>
|
||||
<li>
|
||||
The safety setting to hide media previews in rooms is now synced between Matrix clients
|
||||
and we added another safety setting (which is also synced) to hide avatars in invites.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
As usual, this release includes other improvements, fixes and new translations thanks to
|
||||
all our contributors, and our upstream projects.
|
||||
</p>
|
||||
<p>
|
||||
As the version implies, there might be a slight risk of regressions, but it should be
|
||||
mostly stable. If all goes well the next step is the release candidate!
|
||||
We want to address special thanks to the translators who worked on this version. We know
|
||||
this is a huge undertaking and have a deep appreciation for what you’ve done. If you want
|
||||
to help with this effort, head over to Damned Lies.
|
||||
</p>
|
||||
</description>
|
||||
</release>
|
||||
|
@ -120,8 +120,10 @@ sidebar {
|
||||
font-weight: bold;
|
||||
font-size: 0.8em;
|
||||
border-radius: 9999px;
|
||||
min-width: 0.7em;
|
||||
padding: 2px 5px;
|
||||
min-width: 0.8em;
|
||||
min-height: 0.8em;
|
||||
line-height: 0.8em;
|
||||
padding: 0.4em 5px;
|
||||
color: currentColor;
|
||||
background-color: color-mix(in srgb, currentColor 15%, transparent);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
project('fractal',
|
||||
'rust',
|
||||
version: '12.rc',
|
||||
version: '12',
|
||||
license: 'GPL-3.0-or-later',
|
||||
meson_version: '>= 1.1')
|
||||
|
||||
@ -11,7 +11,7 @@ base_id = 'org.gnome.Fractal'
|
||||
application_id = base_id
|
||||
|
||||
major_version = '12'
|
||||
pre_release_version = 'rc'
|
||||
pre_release_version = ''
|
||||
|
||||
version = major_version
|
||||
if pre_release_version != ''
|
||||
|
1198
po/pt_BR.po
1198
po/pt_BR.po
File diff suppressed because it is too large
Load Diff
123
po/sl.po
123
po/sl.po
@ -9,8 +9,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fractal master\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/fractal/issues\n"
|
||||
"POT-Creation-Date: 2025-07-29 01:25+0000\n"
|
||||
"PO-Revision-Date: 2025-07-29 10:53+0200\n"
|
||||
"POT-Creation-Date: 2025-07-31 20:08+0000\n"
|
||||
"PO-Revision-Date: 2025-08-01 12:38+0200\n"
|
||||
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
|
||||
"Language-Team: Slovenian GNOME Translation Team <gnome-si@googlegroups.com>\n"
|
||||
"Language: sl_SI\n"
|
||||
@ -594,8 +594,8 @@ msgstr "Zapusti"
|
||||
#: src/identity_verification_view/confirm_qr_code_page.ui:52
|
||||
#: src/session/view/account_settings/general_page/mod.rs:391
|
||||
#: src/session/view/content/room_details/edit_details_subpage.rs:267
|
||||
#: src/session/view/content/room_details/general_page.rs:1033
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:116
|
||||
#: src/session/view/content/room_details/general_page.rs:1036
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:112
|
||||
#: src/session/view/content/room_history/event_actions/group.rs:583
|
||||
#: src/session/view/content/room_history/event_actions/group.rs:647
|
||||
#: src/session/view/content/room_history/message_toolbar/mod.ui:76
|
||||
@ -1869,7 +1869,7 @@ msgstr "Člani {room}"
|
||||
msgid "Any registered user"
|
||||
msgstr "Vsak registriran uporabnik"
|
||||
|
||||
#: src/session/model/room/join_rule.rs:288 src/session/view/content/room_details/general_page.rs:939
|
||||
#: src/session/model/room/join_rule.rs:288 src/session/view/content/room_details/general_page.rs:942
|
||||
msgid "Unsupported rule"
|
||||
msgstr "Nepodprto pravilo"
|
||||
|
||||
@ -2903,7 +2903,7 @@ msgid "Remove “{address}”"
|
||||
msgstr "Odstrani »{address}«"
|
||||
|
||||
#: src/session/view/content/room_details/addresses_subpage/mod.rs:431
|
||||
#: src/session/view/content/room_details/general_page.rs:677 src/session/view/create_room_dialog.ui:132
|
||||
#: src/session/view/content/room_details/general_page.rs:680 src/session/view/create_room_dialog.ui:132
|
||||
msgid "Main Address"
|
||||
msgstr "Glavni naslov"
|
||||
|
||||
@ -3048,8 +3048,8 @@ msgstr "Opis"
|
||||
msgid "Save Description"
|
||||
msgstr "Shrani opis"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:462
|
||||
#: src/session/view/content/room_details/general_page.rs:508
|
||||
#: src/session/view/content/room_details/general_page.rs:465
|
||||
#: src/session/view/content/room_details/general_page.rs:511
|
||||
#: src/session/view/content/room_details/invite_subpage/list.rs:260
|
||||
msgid "Member"
|
||||
msgid_plural "Members"
|
||||
@ -3058,71 +3058,71 @@ msgstr[1] "člani"
|
||||
msgstr[2] "člani"
|
||||
msgstr[3] "člani"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:632
|
||||
#: src/session/view/content/room_details/general_page.rs:635
|
||||
msgid "Could not change notifications setting"
|
||||
msgstr "Nastavitve obvestil ni bilo mogoče spremeniti"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:672
|
||||
#: src/session/view/content/room_details/general_page.rs:711
|
||||
#: src/session/view/content/room_details/general_page.rs:675
|
||||
#: src/session/view/content/room_details/general_page.rs:714
|
||||
msgid "Copy address"
|
||||
msgstr "Kopiraj naslov"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:673
|
||||
#: src/session/view/content/room_details/general_page.rs:712
|
||||
#: src/session/view/content/room_details/general_page.rs:676
|
||||
#: src/session/view/content/room_details/general_page.rs:715
|
||||
msgid "Address copied to clipboard"
|
||||
msgstr "Naslov je kopiran v odložišče"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:754
|
||||
#: src/session/view/content/room_details/general_page.rs:757
|
||||
msgid "Room link copied to clipboard"
|
||||
msgstr "Povezava do klepetalnice kopirana v odložišče"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:815
|
||||
#: src/session/view/content/room_details/general_page.rs:818
|
||||
msgid "Could not change guest access"
|
||||
msgstr "Ni bilo mogoče spremeniti dostopa za goste"
|
||||
|
||||
#. Translators: Do NOT translate the content between '{' and '}',
|
||||
#. this is a variable name.
|
||||
#: src/session/view/content/room_details/general_page.rs:832
|
||||
#: src/session/view/content/room_details/general_page.rs:835
|
||||
msgid "Publish in the {homeserver} directory"
|
||||
msgstr "Objavi v imeniku {homeserver}"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:907
|
||||
#: src/session/view/content/room_details/general_page.rs:910
|
||||
msgid "Could not publish room in directory"
|
||||
msgstr "Klepetalnice ni bilo mogoče objaviti v imeniku"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:909
|
||||
#: src/session/view/content/room_details/general_page.rs:912
|
||||
msgid "Could not unpublish room from directory"
|
||||
msgstr "Objave klepetalnice v imeniku ni bilo mogoče preklicati"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:930
|
||||
#: src/session/view/content/room_details/general_page.rs:933
|
||||
#: src/session/view/content/room_details/general_page.ui:274
|
||||
msgid "Anyone, even if they are not in the room"
|
||||
msgstr "Kdorkoli, tudi če ni v klepetalnici"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:933
|
||||
#: src/session/view/content/room_details/general_page.rs:936
|
||||
#: src/session/view/content/room_details/general_page.ui:275
|
||||
msgid "Members only, since this option was selected"
|
||||
msgstr "Samo člani, saj je bila ta možnost izbrana"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:935
|
||||
#: src/session/view/content/room_details/general_page.rs:938
|
||||
#: src/session/view/content/room_details/general_page.ui:277
|
||||
msgid "Members only, since they were invited"
|
||||
msgstr "Samo člani, saj so bili povabljeni"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:937
|
||||
#: src/session/view/content/room_details/general_page.rs:940
|
||||
#: src/session/view/content/room_details/general_page.ui:276
|
||||
msgid "Members only, since they joined the room"
|
||||
msgstr "Samo člani, ker so se pridružili klepetalnici"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:986
|
||||
#: src/session/view/content/room_details/general_page.rs:989
|
||||
msgid "Could not change who can read history"
|
||||
msgstr "Ni bilo mogoče spremeniti, kdo sme brati zgodovino"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1028
|
||||
#: src/session/view/content/room_details/general_page.rs:1031
|
||||
msgid "Enable Encryption?"
|
||||
msgstr "Želite omogočiti šifriranja?"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1029
|
||||
#: src/session/view/content/room_details/general_page.rs:1032
|
||||
msgid ""
|
||||
"Enabling encryption will prevent new members to read the history before they arrived. This cannot be "
|
||||
"disabled later."
|
||||
@ -3130,30 +3130,30 @@ msgstr ""
|
||||
"Če omogočite šifriranje, novi člani ne bodo mogli prebrati zgodovine pred svojo včlanitvijo. Tega "
|
||||
"pozneje ni možno onemogočiti."
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1034 src/session/view/sidebar/mod.rs:320
|
||||
#: src/session/view/content/room_details/general_page.rs:1037 src/session/view/sidebar/mod.rs:320
|
||||
#: src/session/view/sidebar/mod.rs:326
|
||||
msgid "Enable"
|
||||
msgstr "Omogoči"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1045
|
||||
#: src/session/view/content/room_details/general_page.rs:1048
|
||||
msgid "Could not enable encryption"
|
||||
msgstr "Šifriranja ni bilo mogoče omogočiti"
|
||||
|
||||
#. Translators: As in, 'Room federated'.
|
||||
#: src/session/view/content/room_details/general_page.rs:1071
|
||||
#: src/session/view/content/room_details/general_page.rs:1107
|
||||
msgid "Federated"
|
||||
msgstr "Združeno"
|
||||
|
||||
#. Translators: As in, 'Room not federated'.
|
||||
#: src/session/view/content/room_details/general_page.rs:1074
|
||||
#: src/session/view/content/room_details/general_page.rs:1110
|
||||
msgid "Not federated"
|
||||
msgstr "Ni združeno"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1105
|
||||
#: src/session/view/content/room_details/general_page.rs:1144
|
||||
msgid "Room upgraded successfully"
|
||||
msgstr "Klepetalnica uspešno nadgrajena"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1109
|
||||
#: src/session/view/content/room_details/general_page.rs:1148
|
||||
msgid "Could not upgrade room"
|
||||
msgstr "Klepetalnice ni bilo mogoče nadgraditi"
|
||||
|
||||
@ -3570,7 +3570,7 @@ msgstr "Preklopi iskanje članov klepetalnice"
|
||||
msgid "Search for room members"
|
||||
msgstr "Poišči člane klepetalnice"
|
||||
|
||||
#: src/session/view/content/room_details/mod.rs:149
|
||||
#: src/session/view/content/room_details/mod.rs:150
|
||||
msgid "The user is not in the room members list anymore"
|
||||
msgstr "Uporabnika ni več na seznamu članov klepetalnice"
|
||||
|
||||
@ -3680,7 +3680,8 @@ msgid "Change Server Access Control List"
|
||||
msgstr "Spremeni seznam za nadzor dostopa do strežnika"
|
||||
|
||||
#: src/session/view/content/room_details/permissions/permissions_subpage.ui:191
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:111
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:4
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:43
|
||||
msgid "Upgrade Room"
|
||||
msgstr "Nadgradi klepetalnico"
|
||||
|
||||
@ -3710,20 +3711,51 @@ msgid "Default Power Level"
|
||||
msgstr "Privzeta raven moči"
|
||||
|
||||
#. Translators: As in 'Stable version'.
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:85
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:114
|
||||
msgid "Stable"
|
||||
msgstr "Stabilno"
|
||||
|
||||
#. Translators: As in 'Experimental version'.
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:87
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:117
|
||||
msgid "Experimental"
|
||||
msgstr "Poskusno"
|
||||
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:94
|
||||
msgid "Version"
|
||||
msgstr "Različica"
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:196
|
||||
msgid ""
|
||||
"After the upgrade, you will be the only creator in the room. The other creator will be demoted to the "
|
||||
"default power level."
|
||||
msgid_plural ""
|
||||
"After the upgrade, you will be the only creator in the room. The other creators will be demoted to the "
|
||||
"default power level."
|
||||
msgstr[0] ""
|
||||
"Po nadgradnji boste edini avtor v klepetalnici. Drugi avtorji bodo degradirani na privzeto raven moči."
|
||||
msgstr[1] ""
|
||||
"Po nadgradnji boste edini avtor v klepetalnici. Drug avtor bo degradiran na privzeto raven moči."
|
||||
msgstr[2] ""
|
||||
"Po nadgradnji boste edini avtor v klepetalnici. Druga avtorja bosta degradirana na privzeto raven moči."
|
||||
msgstr[3] ""
|
||||
"Po nadgradnji boste edini avtor v klepetalnici. Drugi avtorji bodo degradirani na privzeto raven moči."
|
||||
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:112
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:202
|
||||
msgid ""
|
||||
"After the upgrade, you will be the only creator in the room. The current creator will be demoted to the "
|
||||
"default power level."
|
||||
msgid_plural ""
|
||||
"After the upgrade, you will be the only creator in the room. The current creators will be demoted to "
|
||||
"the default power level."
|
||||
msgstr[0] ""
|
||||
"Po nadgradnji boste edini avtor v klepetalnici. Trenutni avtorji bodo degradirani na privzeto raven "
|
||||
"moči."
|
||||
msgstr[1] ""
|
||||
"Po nadgradnji boste edini avtor v klepetalnici. Trenutni avtor bo degradiran na privzeto raven moči."
|
||||
msgstr[2] ""
|
||||
"Po nadgradnji boste edini avtor v klepetalnici. Trenutna avtorja bosta degradirana na privzeto raven "
|
||||
"moči."
|
||||
msgstr[3] ""
|
||||
"Po nadgradnji boste edini avtor v klepetalnici. Trenutni avtorji bodo degradirani na privzeto raven "
|
||||
"moči."
|
||||
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:59
|
||||
msgid ""
|
||||
"Upgrading a room to a more recent version allows to benefit from new features from the Matrix "
|
||||
"specification. It can also be used to reset the room state, which should make the room faster to join. "
|
||||
@ -3735,8 +3767,12 @@ msgstr ""
|
||||
"klepetalnico hitreje pridružili. Vendar pa je to treba uporabljati zmerno, ker je lahko moteče, saj se "
|
||||
"morajo člani klepetalnice ročno pridružiti novi klepetalnici."
|
||||
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:74
|
||||
msgid "Version"
|
||||
msgstr "Različica"
|
||||
|
||||
#. Translators: In this string, 'Upgrade' is a verb, as in 'Upgrade Room'.
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:118
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:120
|
||||
msgid "Upgrade"
|
||||
msgstr "Nadgradi"
|
||||
|
||||
@ -3964,7 +4000,7 @@ msgstr "Poslano"
|
||||
msgid "Edited"
|
||||
msgstr "Urejeno"
|
||||
|
||||
#: src/session/view/content/room_history/message_row/mod.rs:245
|
||||
#: src/session/view/content/room_history/message_row/mod.rs:243
|
||||
msgid "Sent at {time}"
|
||||
msgstr "Poslano {time}"
|
||||
|
||||
@ -3990,6 +4026,11 @@ msgstr "Odzivi"
|
||||
msgid "In Reply To"
|
||||
msgstr "Odgovor na"
|
||||
|
||||
#. Translators: This is a verb, as in 'Mention user'.
|
||||
#: src/session/view/content/room_history/message_row/sender_name.rs:188
|
||||
msgid "Mention"
|
||||
msgstr "Omeni"
|
||||
|
||||
#. Translators: this is the fallback title for an expander.
|
||||
#: src/session/view/content/room_history/message_row/text/widgets.rs:439
|
||||
msgid "Details"
|
||||
|
79
po/uk.po
79
po/uk.po
@ -8,8 +8,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fractal master\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/fractal/issues\n"
|
||||
"POT-Creation-Date: 2025-07-30 15:22+0000\n"
|
||||
"PO-Revision-Date: 2025-07-30 21:56+0300\n"
|
||||
"POT-Creation-Date: 2025-07-31 17:47+0000\n"
|
||||
"PO-Revision-Date: 2025-07-31 21:28+0300\n"
|
||||
"Last-Translator: Yuri Chornoivan <yurchor@ukr.net>\n"
|
||||
"Language-Team: Ukrainian <trans-uk@lists.fedoraproject.org>\n"
|
||||
"Language: uk\n"
|
||||
@ -687,7 +687,7 @@ msgstr "Полишити"
|
||||
#: src/session/view/account_settings/general_page/mod.rs:391
|
||||
#: src/session/view/content/room_details/edit_details_subpage.rs:267
|
||||
#: src/session/view/content/room_details/general_page.rs:1036
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:98
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:112
|
||||
#: src/session/view/content/room_history/event_actions/group.rs:583
|
||||
#: src/session/view/content/room_history/event_actions/group.rs:647
|
||||
#: src/session/view/content/room_history/message_toolbar/mod.ui:76
|
||||
@ -1115,7 +1115,6 @@ msgid "Confirm Custom Role"
|
||||
msgstr "Підтвердження нетипової ролі"
|
||||
|
||||
#: src/components/power_level_selection/row.ui:101
|
||||
#| msgid "Advanced Information"
|
||||
msgid "More Information"
|
||||
msgstr "Докладніше"
|
||||
|
||||
@ -1127,11 +1126,11 @@ msgid ""
|
||||
"in the room. The only way to demote a creator is to replace the current room "
|
||||
"with another one with a different creator, by upgrading the room for example."
|
||||
msgstr ""
|
||||
"Автор є незмінною роллю, яку можна пов'язати із декількома користувачами під"
|
||||
" час створення кімнати. Автори завжди мають вищий рівень прав доступу за"
|
||||
" будь-якого з учасників кімнати, окрім інших авторів, і мають право роботи у"
|
||||
" кімнаті будь-що. Єдиним способом позбавити автора прав є заміна поточної"
|
||||
" кімнати на іншу із іншим автором, наприклад, шляхом оновлення кімнати."
|
||||
"Автор є незмінною роллю, яку можна пов'язати із декількома користувачами під "
|
||||
"час створення кімнати. Автори завжди мають вищий рівень прав доступу за будь-"
|
||||
"якого з учасників кімнати, окрім інших авторів, і мають право роботи у "
|
||||
"кімнаті будь-що. Єдиним способом позбавити автора прав є заміна поточної "
|
||||
"кімнати на іншу із іншим автором, наприклад, шляхом оновлення кімнати."
|
||||
|
||||
#: src/components/user_page.rs:256
|
||||
#: src/session/view/account_settings/general_page/mod.ui:60
|
||||
@ -3441,20 +3440,20 @@ msgid "Could not enable encryption"
|
||||
msgstr "Не вдалося увімкнути шифрування"
|
||||
|
||||
#. Translators: As in, 'Room federated'.
|
||||
#: src/session/view/content/room_details/general_page.rs:1095
|
||||
#: src/session/view/content/room_details/general_page.rs:1107
|
||||
msgid "Federated"
|
||||
msgstr "Інтегрований"
|
||||
|
||||
#. Translators: As in, 'Room not federated'.
|
||||
#: src/session/view/content/room_details/general_page.rs:1098
|
||||
#: src/session/view/content/room_details/general_page.rs:1110
|
||||
msgid "Not federated"
|
||||
msgstr "Не федеровано"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1132
|
||||
#: src/session/view/content/room_details/general_page.rs:1144
|
||||
msgid "Room upgraded successfully"
|
||||
msgstr "Кімнату успішно оновлено"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1136
|
||||
#: src/session/view/content/room_details/general_page.rs:1148
|
||||
msgid "Could not upgrade room"
|
||||
msgstr "Не вдалося оновити кімнату"
|
||||
|
||||
@ -4019,15 +4018,55 @@ msgid "Default Power Level"
|
||||
msgstr "Типовий рівень повноважень"
|
||||
|
||||
#. Translators: As in 'Stable version'.
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:112
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:114
|
||||
msgid "Stable"
|
||||
msgstr "Стабільна"
|
||||
|
||||
#. Translators: As in 'Experimental version'.
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:115
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:117
|
||||
msgid "Experimental"
|
||||
msgstr "Експериментальна"
|
||||
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:196
|
||||
msgid ""
|
||||
"After the upgrade, you will be the only creator in the room. The other "
|
||||
"creator will be demoted to the default power level."
|
||||
msgid_plural ""
|
||||
"After the upgrade, you will be the only creator in the room. The other "
|
||||
"creators will be demoted to the default power level."
|
||||
msgstr[0] ""
|
||||
"Після оновлення ви станете єдиним автором у кімнаті. Інших авторів буде"
|
||||
" звужено у правах до типового рівня доступу."
|
||||
msgstr[1] ""
|
||||
"Після оновлення ви станете єдиним автором у кімнаті. Інших авторів буде"
|
||||
" звужено у правах до типового рівня доступу."
|
||||
msgstr[2] ""
|
||||
"Після оновлення ви станете єдиним автором у кімнаті. Інших авторів буде"
|
||||
" звужено у правах до типового рівня доступу."
|
||||
msgstr[3] ""
|
||||
"Після оновлення ви станете єдиним автором у кімнаті. Іншого автора буде"
|
||||
" звужено у правах до типового рівня доступу."
|
||||
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:202
|
||||
msgid ""
|
||||
"After the upgrade, you will be the only creator in the room. The current "
|
||||
"creator will be demoted to the default power level."
|
||||
msgid_plural ""
|
||||
"After the upgrade, you will be the only creator in the room. The current "
|
||||
"creators will be demoted to the default power level."
|
||||
msgstr[0] ""
|
||||
"Після оновлення ви станете єдиним автором у кімнаті. Поточних авторів буде"
|
||||
" звужено у правах до типового рівня доступу."
|
||||
msgstr[1] ""
|
||||
"Після оновлення ви станете єдиним автором у кімнаті. Поточних авторів буде"
|
||||
" звужено у правах до типового рівня доступу."
|
||||
msgstr[2] ""
|
||||
"Після оновлення ви станете єдиним автором у кімнаті. Поточних авторів буде"
|
||||
" звужено у правах до типового рівня доступу."
|
||||
msgstr[3] ""
|
||||
"Після оновлення ви станете єдиним автором у кімнаті. Поточного автора буде"
|
||||
" звужено у правах до типового рівня доступу."
|
||||
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:59
|
||||
msgid ""
|
||||
"Upgrading a room to a more recent version allows to benefit from new "
|
||||
@ -4048,7 +4087,7 @@ msgid "Version"
|
||||
msgstr "Версія"
|
||||
|
||||
#. Translators: In this string, 'Upgrade' is a verb, as in 'Upgrade Room'.
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:106
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:120
|
||||
msgid "Upgrade"
|
||||
msgstr "Оновити"
|
||||
|
||||
@ -4283,7 +4322,7 @@ msgstr "Надіслано"
|
||||
msgid "Edited"
|
||||
msgstr "Змінено"
|
||||
|
||||
#: src/session/view/content/room_history/message_row/mod.rs:245
|
||||
#: src/session/view/content/room_history/message_row/mod.rs:243
|
||||
msgid "Sent at {time}"
|
||||
msgstr "Надіслано {time}"
|
||||
|
||||
@ -4309,6 +4348,12 @@ msgstr "Реакції"
|
||||
msgid "In Reply To"
|
||||
msgstr "У відповідь на"
|
||||
|
||||
#. Translators: This is a verb, as in 'Mention user'.
|
||||
#: src/session/view/content/room_history/message_row/sender_name.rs:188
|
||||
#| msgid "_Mention"
|
||||
msgid "Mention"
|
||||
msgstr "Згадка"
|
||||
|
||||
#. Translators: this is the fallback title for an expander.
|
||||
#: src/session/view/content/room_history/message_row/text/widgets.rs:439
|
||||
msgid "Details"
|
||||
|
110
po/zh_CN.po
110
po/zh_CN.po
@ -8,8 +8,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: fractal master\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/fractal/issues\n"
|
||||
"POT-Creation-Date: 2025-07-28 17:58+0000\n"
|
||||
"PO-Revision-Date: 2025-07-29 09:23+0800\n"
|
||||
"POT-Creation-Date: 2025-08-01 15:53+0000\n"
|
||||
"PO-Revision-Date: 2025-08-02 08:09+0800\n"
|
||||
"Last-Translator: lumingzh <lumingzh@qq.com>\n"
|
||||
"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n"
|
||||
"Language: zh_CN\n"
|
||||
@ -637,8 +637,8 @@ msgstr "离开"
|
||||
#: src/identity_verification_view/confirm_qr_code_page.ui:52
|
||||
#: src/session/view/account_settings/general_page/mod.rs:391
|
||||
#: src/session/view/content/room_details/edit_details_subpage.rs:267
|
||||
#: src/session/view/content/room_details/general_page.rs:1033
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:116
|
||||
#: src/session/view/content/room_details/general_page.rs:1036
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:112
|
||||
#: src/session/view/content/room_history/event_actions/group.rs:583
|
||||
#: src/session/view/content/room_history/event_actions/group.rs:647
|
||||
#: src/session/view/content/room_history/message_toolbar/mod.ui:76
|
||||
@ -1938,7 +1938,7 @@ msgid "Any registered user"
|
||||
msgstr "任何注册的用户"
|
||||
|
||||
#: src/session/model/room/join_rule.rs:288
|
||||
#: src/session/view/content/room_details/general_page.rs:939
|
||||
#: src/session/view/content/room_details/general_page.rs:942
|
||||
msgid "Unsupported rule"
|
||||
msgstr "不支持的规则"
|
||||
|
||||
@ -2978,7 +2978,7 @@ msgid "Remove “{address}”"
|
||||
msgstr "移除“{address}”"
|
||||
|
||||
#: src/session/view/content/room_details/addresses_subpage/mod.rs:431
|
||||
#: src/session/view/content/room_details/general_page.rs:677
|
||||
#: src/session/view/content/room_details/general_page.rs:680
|
||||
#: src/session/view/create_room_dialog.ui:132
|
||||
msgid "Main Address"
|
||||
msgstr "主地址"
|
||||
@ -3124,107 +3124,107 @@ msgstr "说明"
|
||||
msgid "Save Description"
|
||||
msgstr "保存说明"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:462
|
||||
#: src/session/view/content/room_details/general_page.rs:508
|
||||
#: src/session/view/content/room_details/general_page.rs:465
|
||||
#: src/session/view/content/room_details/general_page.rs:511
|
||||
#: src/session/view/content/room_details/invite_subpage/list.rs:260
|
||||
msgid "Member"
|
||||
msgid_plural "Members"
|
||||
msgstr[0] "成员"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:632
|
||||
#: src/session/view/content/room_details/general_page.rs:635
|
||||
msgid "Could not change notifications setting"
|
||||
msgstr "无法更改通知设置"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:672
|
||||
#: src/session/view/content/room_details/general_page.rs:711
|
||||
#: src/session/view/content/room_details/general_page.rs:675
|
||||
#: src/session/view/content/room_details/general_page.rs:714
|
||||
msgid "Copy address"
|
||||
msgstr "复制地址"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:673
|
||||
#: src/session/view/content/room_details/general_page.rs:712
|
||||
#: src/session/view/content/room_details/general_page.rs:676
|
||||
#: src/session/view/content/room_details/general_page.rs:715
|
||||
msgid "Address copied to clipboard"
|
||||
msgstr "地址已复制到剪贴板"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:754
|
||||
#: src/session/view/content/room_details/general_page.rs:757
|
||||
msgid "Room link copied to clipboard"
|
||||
msgstr "聊天室链接已复制到剪贴板"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:815
|
||||
#: src/session/view/content/room_details/general_page.rs:818
|
||||
msgid "Could not change guest access"
|
||||
msgstr "无法更改访客访问权限"
|
||||
|
||||
#. Translators: Do NOT translate the content between '{' and '}',
|
||||
#. this is a variable name.
|
||||
#: src/session/view/content/room_details/general_page.rs:832
|
||||
#: src/session/view/content/room_details/general_page.rs:835
|
||||
msgid "Publish in the {homeserver} directory"
|
||||
msgstr "公布在 {homeserver} 目录"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:907
|
||||
#: src/session/view/content/room_details/general_page.rs:910
|
||||
msgid "Could not publish room in directory"
|
||||
msgstr "无法在目录公布聊天室"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:909
|
||||
#: src/session/view/content/room_details/general_page.rs:912
|
||||
msgid "Could not unpublish room from directory"
|
||||
msgstr "无法从目录撤下聊天室"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:930
|
||||
#: src/session/view/content/room_details/general_page.rs:933
|
||||
#: src/session/view/content/room_details/general_page.ui:274
|
||||
msgid "Anyone, even if they are not in the room"
|
||||
msgstr "任何人,即使他们未加入聊天室"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:933
|
||||
#: src/session/view/content/room_details/general_page.rs:936
|
||||
#: src/session/view/content/room_details/general_page.ui:275
|
||||
msgid "Members only, since this option was selected"
|
||||
msgstr "仅成员,在选择该选项之后"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:935
|
||||
#: src/session/view/content/room_details/general_page.rs:938
|
||||
#: src/session/view/content/room_details/general_page.ui:277
|
||||
msgid "Members only, since they were invited"
|
||||
msgstr "仅成员,在他们被邀请之后"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:937
|
||||
#: src/session/view/content/room_details/general_page.rs:940
|
||||
#: src/session/view/content/room_details/general_page.ui:276
|
||||
msgid "Members only, since they joined the room"
|
||||
msgstr "仅成员,在他们加入聊天室后"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:986
|
||||
#: src/session/view/content/room_details/general_page.rs:989
|
||||
msgid "Could not change who can read history"
|
||||
msgstr "无法更改历史查阅权限"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1028
|
||||
#: src/session/view/content/room_details/general_page.rs:1031
|
||||
msgid "Enable Encryption?"
|
||||
msgstr "启用加密吗?"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1029
|
||||
#: src/session/view/content/room_details/general_page.rs:1032
|
||||
msgid ""
|
||||
"Enabling encryption will prevent new members to read the history before they "
|
||||
"arrived. This cannot be disabled later."
|
||||
msgstr "启用加密将阻止新成员在到达聊天室前查阅聊天历史。该选项无法在随后禁用。"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1034
|
||||
#: src/session/view/content/room_details/general_page.rs:1037
|
||||
#: src/session/view/sidebar/mod.rs:320 src/session/view/sidebar/mod.rs:326
|
||||
msgid "Enable"
|
||||
msgstr "启用"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1045
|
||||
#: src/session/view/content/room_details/general_page.rs:1048
|
||||
msgid "Could not enable encryption"
|
||||
msgstr "无法启用加密"
|
||||
|
||||
#. Translators: As in, 'Room federated'.
|
||||
#: src/session/view/content/room_details/general_page.rs:1071
|
||||
#: src/session/view/content/room_details/general_page.rs:1107
|
||||
msgid "Federated"
|
||||
msgstr "联合的"
|
||||
|
||||
#. Translators: As in, 'Room not federated'.
|
||||
#: src/session/view/content/room_details/general_page.rs:1074
|
||||
#: src/session/view/content/room_details/general_page.rs:1110
|
||||
msgid "Not federated"
|
||||
msgstr "未联合"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1105
|
||||
#: src/session/view/content/room_details/general_page.rs:1144
|
||||
msgid "Room upgraded successfully"
|
||||
msgstr "聊天室已成功升级"
|
||||
|
||||
#: src/session/view/content/room_details/general_page.rs:1109
|
||||
#: src/session/view/content/room_details/general_page.rs:1148
|
||||
msgid "Could not upgrade room"
|
||||
msgstr "无法升级聊天室"
|
||||
|
||||
@ -3621,7 +3621,7 @@ msgstr "切换聊天室成员搜索"
|
||||
msgid "Search for room members"
|
||||
msgstr "搜索聊天室成员"
|
||||
|
||||
#: src/session/view/content/room_details/mod.rs:149
|
||||
#: src/session/view/content/room_details/mod.rs:150
|
||||
msgid "The user is not in the room members list anymore"
|
||||
msgstr "该用户不再在聊天室成员列表中"
|
||||
|
||||
@ -3732,7 +3732,8 @@ msgid "Change Server Access Control List"
|
||||
msgstr "更改服务器访问控制列表"
|
||||
|
||||
#: src/session/view/content/room_details/permissions/permissions_subpage.ui:191
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:111
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:4
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:43
|
||||
msgid "Upgrade Room"
|
||||
msgstr "升级聊天室"
|
||||
|
||||
@ -3762,20 +3763,38 @@ msgid "Default Power Level"
|
||||
msgstr "默认权力等级"
|
||||
|
||||
#. Translators: As in 'Stable version'.
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:85
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:114
|
||||
msgid "Stable"
|
||||
msgstr "稳定版"
|
||||
|
||||
#. Translators: As in 'Experimental version'.
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:87
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:117
|
||||
msgid "Experimental"
|
||||
msgstr "体验版"
|
||||
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:94
|
||||
msgid "Version"
|
||||
msgstr "版本"
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:196
|
||||
msgid ""
|
||||
"After the upgrade, you will be the only creator in the room. The other "
|
||||
"creator will be demoted to the default power level."
|
||||
msgid_plural ""
|
||||
"After the upgrade, you will be the only creator in the room. The other "
|
||||
"creators will be demoted to the default power level."
|
||||
msgstr[0] ""
|
||||
"升级之后,您将成为该聊天室中唯一的创建者。其他创建者将被降级为默认的权力级"
|
||||
"别。"
|
||||
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:112
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.rs:202
|
||||
msgid ""
|
||||
"After the upgrade, you will be the only creator in the room. The current "
|
||||
"creator will be demoted to the default power level."
|
||||
msgid_plural ""
|
||||
"After the upgrade, you will be the only creator in the room. The current "
|
||||
"creators will be demoted to the default power level."
|
||||
msgstr[0] ""
|
||||
"升级之后,您将成为该聊天室中唯一的创建者。当前的创建者将被降级为默认的权力级"
|
||||
"别。"
|
||||
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:59
|
||||
msgid ""
|
||||
"Upgrading a room to a more recent version allows to benefit from new "
|
||||
"features from the Matrix specification. It can also be used to reset the "
|
||||
@ -3787,8 +3806,12 @@ msgstr ""
|
||||
"态,可使加入聊天室速度更快。然而由于其可能引起混乱应保守使用,因为聊天室成员"
|
||||
"需要手动加入新聊天室。"
|
||||
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:74
|
||||
msgid "Version"
|
||||
msgstr "版本"
|
||||
|
||||
#. Translators: In this string, 'Upgrade' is a verb, as in 'Upgrade Room'.
|
||||
#: src/session/view/content/room_details/room_upgrade_dialog.rs:118
|
||||
#: src/session/view/content/room_details/upgrade_dialog/mod.ui:120
|
||||
msgid "Upgrade"
|
||||
msgstr "升级"
|
||||
|
||||
@ -4018,7 +4041,7 @@ msgstr "已发送"
|
||||
msgid "Edited"
|
||||
msgstr "已编辑"
|
||||
|
||||
#: src/session/view/content/room_history/message_row/mod.rs:245
|
||||
#: src/session/view/content/room_history/message_row/mod.rs:243
|
||||
msgid "Sent at {time}"
|
||||
msgstr "在 {time} 发送"
|
||||
|
||||
@ -4041,6 +4064,11 @@ msgstr "回应"
|
||||
msgid "In Reply To"
|
||||
msgstr "在回复中"
|
||||
|
||||
#. Translators: This is a verb, as in 'Mention user'.
|
||||
#: src/session/view/content/room_history/message_row/sender_name.rs:188
|
||||
msgid "Mention"
|
||||
msgstr "提到"
|
||||
|
||||
#. Translators: this is the fallback title for an expander.
|
||||
#: src/session/view/content/room_history/message_row/text/widgets.rs:439
|
||||
msgid "Details"
|
||||
|
@ -6,7 +6,7 @@ use tracing::error;
|
||||
|
||||
use crate::{
|
||||
spawn, spawn_tokio,
|
||||
utils::{CountedRef, File},
|
||||
utils::{CountedRef, File, TokioDrop},
|
||||
};
|
||||
|
||||
mod imp {
|
||||
@ -19,9 +19,12 @@ mod imp {
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AnimatedImagePaintable {
|
||||
/// The image loader.
|
||||
image_loader: OnceCell<Arc<Image<'static>>>,
|
||||
/// The image decoder.
|
||||
decoder: OnceCell<Arc<TokioDrop<Image>>>,
|
||||
/// The file of the image.
|
||||
///
|
||||
/// We need to keep a strong reference to the temporary file or it will
|
||||
/// be destroyed.
|
||||
file: OnceCell<File>,
|
||||
/// The current frame that is displayed.
|
||||
pub(super) current_frame: RefCell<Option<Arc<Frame>>>,
|
||||
@ -49,7 +52,7 @@ mod imp {
|
||||
self.current_frame
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map_or_else(|| self.image_loader().info().height, |f| f.height())
|
||||
.map_or_else(|| self.decoder().details().height(), |f| f.height())
|
||||
.try_into()
|
||||
.unwrap_or(i32::MAX)
|
||||
}
|
||||
@ -58,7 +61,7 @@ mod imp {
|
||||
self.current_frame
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map_or_else(|| self.image_loader().info().width, |f| f.width())
|
||||
.map_or_else(|| self.decoder().details().width(), |f| f.width())
|
||||
.try_into()
|
||||
.unwrap_or(i32::MAX)
|
||||
}
|
||||
@ -94,26 +97,27 @@ mod imp {
|
||||
}
|
||||
|
||||
impl AnimatedImagePaintable {
|
||||
/// The image loader.
|
||||
fn image_loader(&self) -> &Arc<Image<'static>> {
|
||||
self.image_loader
|
||||
.get()
|
||||
.expect("image loader is initialized")
|
||||
/// The image decoder.
|
||||
fn decoder(&self) -> &Arc<TokioDrop<Image>> {
|
||||
self.decoder.get().expect("decoder should be initialized")
|
||||
}
|
||||
|
||||
/// Initialize the image.
|
||||
pub(super) fn init(
|
||||
&self,
|
||||
file: File,
|
||||
image_loader: Arc<Image<'static>>,
|
||||
decoder: Arc<TokioDrop<Image>>,
|
||||
first_frame: Arc<Frame>,
|
||||
file: Option<File>,
|
||||
) {
|
||||
self.file.set(file).expect("file is uninitialized");
|
||||
self.image_loader
|
||||
.set(image_loader)
|
||||
.expect("image loader is uninitialized");
|
||||
self.decoder
|
||||
.set(decoder)
|
||||
.expect("decoder should be uninitialized");
|
||||
self.current_frame.replace(Some(first_frame));
|
||||
|
||||
if let Some(file) = file {
|
||||
self.file.set(file).expect("file should be uninitialized");
|
||||
}
|
||||
|
||||
self.update_animation();
|
||||
}
|
||||
|
||||
@ -198,9 +202,9 @@ mod imp {
|
||||
}
|
||||
|
||||
async fn load_next_frame_inner(&self) {
|
||||
let image = self.image_loader().clone();
|
||||
let decoder = self.decoder().clone();
|
||||
|
||||
let result = spawn_tokio!(async move { image.next_frame().await })
|
||||
let result = spawn_tokio!(async move { decoder.next_frame().await })
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@ -229,16 +233,16 @@ glib::wrapper! {
|
||||
}
|
||||
|
||||
impl AnimatedImagePaintable {
|
||||
/// Construct an `AnimatedImagePaintable` with the given loader and first
|
||||
/// frame.
|
||||
/// Construct an `AnimatedImagePaintable` with the given decoder, first
|
||||
/// frame, and the file containing the image, if any.
|
||||
pub(crate) fn new(
|
||||
file: File,
|
||||
image_loader: Arc<Image<'static>>,
|
||||
decoder: Arc<TokioDrop<Image>>,
|
||||
first_frame: Arc<Frame>,
|
||||
file: Option<File>,
|
||||
) -> Self {
|
||||
let obj = glib::Object::new::<Self>();
|
||||
|
||||
obj.imp().init(file, image_loader, first_frame);
|
||||
obj.imp().init(decoder, first_frame, file);
|
||||
|
||||
obj
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ mod imp {
|
||||
|
||||
if let Some(send_state) = item.send_state() {
|
||||
match send_state {
|
||||
EventSendState::NotSentYet => return MessageState::Sending,
|
||||
EventSendState::NotSentYet { .. } => return MessageState::Sending,
|
||||
EventSendState::SendingFailed {
|
||||
error,
|
||||
is_recoverable,
|
||||
|
@ -58,10 +58,6 @@ pub enum SessionState {
|
||||
Ready = 2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, glib::Boxed)]
|
||||
#[boxed_type(name = "BoxedClient")]
|
||||
pub struct BoxedClient(Client);
|
||||
|
||||
mod imp {
|
||||
use std::cell::{Cell, OnceCell, RefCell};
|
||||
|
||||
@ -70,9 +66,8 @@ mod imp {
|
||||
#[derive(Debug, Default, glib::Properties)]
|
||||
#[properties(wrapper_type = super::Session)]
|
||||
pub struct Session {
|
||||
/// The Matrix client.
|
||||
#[property(construct_only)]
|
||||
client: TokioDrop<BoxedClient>,
|
||||
/// The Matrix client for this session.
|
||||
client: OnceCell<TokioDrop<Client>>,
|
||||
/// The list model of the sidebar.
|
||||
#[property(get = Self::sidebar_list_model)]
|
||||
sidebar_list_model: OnceCell<SidebarListModel>,
|
||||
@ -128,27 +123,6 @@ mod imp {
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for Session {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
let obj = self.obj();
|
||||
|
||||
self.ignored_users.set_session(Some(obj.clone()));
|
||||
self.notifications.set_session(Some(obj.clone()));
|
||||
self.user_sessions.init(&obj, obj.user_id().clone());
|
||||
|
||||
let monitor = gio::NetworkMonitor::default();
|
||||
let handler_id = monitor.connect_network_changed(clone!(
|
||||
#[weak(rename_to = imp)]
|
||||
self,
|
||||
move |_, _| {
|
||||
spawn!(async move {
|
||||
imp.update_homeserver_reachable().await;
|
||||
});
|
||||
}
|
||||
));
|
||||
self.network_monitor_handler_id.replace(Some(handler_id));
|
||||
}
|
||||
|
||||
fn dispose(&self) {
|
||||
// Needs to be disconnected or else it may restart the sync
|
||||
if let Some(handler_id) = self.network_monitor_handler_id.take() {
|
||||
@ -176,9 +150,34 @@ mod imp {
|
||||
}
|
||||
|
||||
impl Session {
|
||||
// The Matrix client.
|
||||
/// Set the Matrix client for this session.
|
||||
pub(super) fn set_client(&self, client: Client) {
|
||||
self.client
|
||||
.set(TokioDrop::new(client))
|
||||
.expect("client should be uninitialized");
|
||||
|
||||
let obj = self.obj();
|
||||
|
||||
self.ignored_users.set_session(Some(obj.clone()));
|
||||
self.notifications.set_session(Some(obj.clone()));
|
||||
self.user_sessions.init(&obj, obj.user_id().clone());
|
||||
|
||||
let monitor = gio::NetworkMonitor::default();
|
||||
let handler_id = monitor.connect_network_changed(clone!(
|
||||
#[weak(rename_to = imp)]
|
||||
self,
|
||||
move |_, _| {
|
||||
spawn!(async move {
|
||||
imp.update_homeserver_reachable().await;
|
||||
});
|
||||
}
|
||||
));
|
||||
self.network_monitor_handler_id.replace(Some(handler_id));
|
||||
}
|
||||
|
||||
/// The Matrix client for this session.
|
||||
pub(super) fn client(&self) -> &Client {
|
||||
&self.client.get().expect("session should be restored").0
|
||||
self.client.get().expect("client should be initialized")
|
||||
}
|
||||
|
||||
/// The list model of the sidebar.
|
||||
@ -462,6 +461,7 @@ mod imp {
|
||||
|
||||
let sync_settings = SyncSettings::new()
|
||||
.timeout(Duration::from_secs(30))
|
||||
.ignore_timeout_on_first_sync(true)
|
||||
.filter(filter.into());
|
||||
|
||||
let mut sync_stream = Box::pin(client.sync_stream(sync_settings).await);
|
||||
@ -751,11 +751,13 @@ impl Session {
|
||||
.await
|
||||
.expect("task was not aborted")?;
|
||||
|
||||
Ok(glib::Object::builder()
|
||||
let obj = glib::Object::builder::<Self>()
|
||||
.property("info", stored_session)
|
||||
.property("settings", settings)
|
||||
.property("client", BoxedClient(client))
|
||||
.build())
|
||||
.build();
|
||||
obj.imp().set_client(client);
|
||||
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
/// Create a new session from the session of the given Matrix client.
|
||||
|
@ -953,7 +953,7 @@ mod imp {
|
||||
row.set_read_only(!is_supported || !can_change);
|
||||
}
|
||||
|
||||
/// Set the history_visibility of the room.
|
||||
/// Set the history visibility of the room.
|
||||
#[template_callback]
|
||||
async fn set_history_visibility(&self) {
|
||||
let Some(room) = self.room.obj() else {
|
||||
|
@ -733,7 +733,7 @@ mod imp {
|
||||
self.update_changed();
|
||||
}
|
||||
|
||||
/// Handle when the redact_own row has changed.
|
||||
/// Handle when the `redact_own` row has changed.
|
||||
#[template_callback]
|
||||
fn redact_own_changed(&self) {
|
||||
if self.update_in_progress.get() {
|
||||
@ -757,7 +757,7 @@ mod imp {
|
||||
self.update_changed();
|
||||
}
|
||||
|
||||
/// Handle when the redact_others row has changed.
|
||||
/// Handle when the `redact_others` row has changed.
|
||||
#[template_callback]
|
||||
fn redact_others_changed(&self) {
|
||||
if self.update_in_progress.get() {
|
||||
|
@ -813,8 +813,7 @@ mod imp {
|
||||
}
|
||||
future::Either::Right((response, _)) => {
|
||||
// The linux location stream requires a tokio executor when dropped.
|
||||
let stream_drop = TokioDrop::new();
|
||||
let _ = stream_drop.set(location_stream);
|
||||
let _ = TokioDrop::new(location_stream);
|
||||
|
||||
if response == gtk::ResponseType::Ok {
|
||||
break;
|
||||
|
@ -290,7 +290,10 @@ mod imp {
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
groups.into_iter().for_each(|group| group.process_batch());
|
||||
|
||||
for group in groups {
|
||||
group.process_batch();
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle when items were removed in the underlying model.
|
||||
|
@ -127,7 +127,7 @@ impl AnySyncOrStrippedTimelineEvent {
|
||||
let ev = match raw {
|
||||
RawAnySyncOrStrippedTimelineEvent::Sync(ev) => Self::Sync(ev.deserialize()?.into()),
|
||||
RawAnySyncOrStrippedTimelineEvent::Stripped(ev) => {
|
||||
Self::Stripped(ev.deserialize()?.into())
|
||||
Self::Stripped(Box::new(ev.deserialize_as()?))
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -28,7 +28,10 @@ pub(crate) use queue::{IMAGE_QUEUE, ImageRequestPriority};
|
||||
|
||||
use super::{FrameDimensions, MediaFileError};
|
||||
use crate::{
|
||||
DISABLE_GLYCIN_SANDBOX, RUNTIME, components::AnimatedImagePaintable, spawn_tokio, utils::File,
|
||||
DISABLE_GLYCIN_SANDBOX, RUNTIME,
|
||||
components::AnimatedImagePaintable,
|
||||
spawn_tokio,
|
||||
utils::{File, TokioDrop, save_data_to_tmp_file},
|
||||
};
|
||||
|
||||
/// The maximum dimensions of a thumbnail in the timeline.
|
||||
@ -64,63 +67,111 @@ const THUMBNAIL_DIMENSIONS_THRESHOLD: u32 = 200;
|
||||
/// [supported image formats of glycin]: https://gitlab.gnome.org/GNOME/glycin/-/tree/main?ref_type=heads#supported-image-formats
|
||||
const SUPPORTED_ANIMATED_IMAGE_MIME_TYPES: &[&str] = &["image/gif", "image/png", "image/webp"];
|
||||
|
||||
/// Get an image loader for the given file.
|
||||
async fn image_loader(file: gio::File) -> Result<glycin::Image<'static>, glycin::ErrorCtx> {
|
||||
let mut loader = glycin::Loader::new(file);
|
||||
|
||||
if DISABLE_GLYCIN_SANDBOX {
|
||||
loader.sandbox_selector(glycin::SandboxSelector::NotSandboxed);
|
||||
}
|
||||
|
||||
spawn_tokio!(async move { loader.load().await })
|
||||
.await
|
||||
.unwrap()
|
||||
/// The source for decoding an image.
|
||||
enum ImageDecoderSource {
|
||||
/// The bytes containing the encoded image.
|
||||
Data(Vec<u8>),
|
||||
/// The file containing the encoded image.
|
||||
File(File),
|
||||
}
|
||||
|
||||
/// Load the given file as an image into a `GdkPaintable`.
|
||||
///
|
||||
/// Set `request_dimensions` if the image will be shown at specific dimensions.
|
||||
/// To show the image at its natural size, set it to `None`.
|
||||
async fn load_image(
|
||||
file: File,
|
||||
request_dimensions: Option<FrameDimensions>,
|
||||
) -> Result<Image, glycin::ErrorCtx> {
|
||||
let image_loader = image_loader(file.as_gfile()).await?;
|
||||
impl ImageDecoderSource {
|
||||
/// The maximum size of the `Data` variant. This is 1 MB.
|
||||
const MAX_DATA_SIZE: usize = 1_048_576;
|
||||
|
||||
let frame_request = request_dimensions.map(|request| {
|
||||
let image_info = image_loader.info();
|
||||
|
||||
let original_dimensions = FrameDimensions {
|
||||
width: image_info.width,
|
||||
height: image_info.height,
|
||||
};
|
||||
|
||||
original_dimensions.to_image_loader_request(request)
|
||||
});
|
||||
|
||||
spawn_tokio!(async move {
|
||||
let first_frame = if let Some(frame_request) = frame_request {
|
||||
image_loader.specific_frame(frame_request).await?
|
||||
/// Construct an `ImageSource` from the given bytes.
|
||||
///
|
||||
/// If the size of the bytes are too big to be kept in memory, they are
|
||||
/// written to a temporary file.
|
||||
async fn with_bytes(bytes: Vec<u8>) -> Result<Self, MediaFileError> {
|
||||
if bytes.len() > Self::MAX_DATA_SIZE {
|
||||
Ok(Self::File(save_data_to_tmp_file(bytes).await?))
|
||||
} else {
|
||||
image_loader.next_frame().await?
|
||||
Ok(Self::Data(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this image source into a loader.
|
||||
///
|
||||
/// Returns the created loader, and the image file, if any.
|
||||
fn into_loader(self) -> (glycin::Loader, Option<File>) {
|
||||
let (mut loader, file) = match self {
|
||||
Self::Data(bytes) => (glycin::Loader::new_vec(bytes), None),
|
||||
Self::File(file) => (glycin::Loader::new(file.as_gfile()), Some(file)),
|
||||
};
|
||||
Ok(Image {
|
||||
file,
|
||||
loader: image_loader.into(),
|
||||
first_frame: first_frame.into(),
|
||||
|
||||
if DISABLE_GLYCIN_SANDBOX {
|
||||
loader.sandbox_selector(glycin::SandboxSelector::NotSandboxed);
|
||||
}
|
||||
|
||||
(loader, file)
|
||||
}
|
||||
|
||||
/// Decode this image source into an [`Image`].
|
||||
///
|
||||
/// Set `request_dimensions` if the image will be shown at specific
|
||||
/// dimensions. To show the image at its natural size, set it to `None`.
|
||||
async fn decode_image(
|
||||
self,
|
||||
request_dimensions: Option<FrameDimensions>,
|
||||
) -> Result<Image, ImageError> {
|
||||
let (loader, file) = self.into_loader();
|
||||
|
||||
let decoder = spawn_tokio!(async move { loader.load().await })
|
||||
.await
|
||||
.expect("task was not aborted")?;
|
||||
|
||||
let frame_request = request_dimensions.map(|request| {
|
||||
let image_details = decoder.details();
|
||||
|
||||
let original_dimensions = FrameDimensions {
|
||||
width: image_details.width(),
|
||||
height: image_details.height(),
|
||||
};
|
||||
|
||||
original_dimensions.to_image_loader_request(request)
|
||||
});
|
||||
|
||||
spawn_tokio!(async move {
|
||||
let first_frame = if let Some(frame_request) = frame_request {
|
||||
decoder.specific_frame(frame_request).await?
|
||||
} else {
|
||||
decoder.next_frame().await?
|
||||
};
|
||||
|
||||
Ok(Image {
|
||||
file,
|
||||
decoder: TokioDrop::new(decoder).into(),
|
||||
first_frame: first_frame.into(),
|
||||
})
|
||||
})
|
||||
})
|
||||
.await
|
||||
.expect("task was not aborted")
|
||||
.await
|
||||
.expect("task was not aborted")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<File> for ImageDecoderSource {
|
||||
fn from(value: File) -> Self {
|
||||
Self::File(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<gio::File> for ImageDecoderSource {
|
||||
fn from(value: gio::File) -> Self {
|
||||
Self::File(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// An image that was just loaded.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Image {
|
||||
/// The file of the image.
|
||||
file: File,
|
||||
/// The image loader.
|
||||
loader: Arc<glycin::Image<'static>>,
|
||||
/// The file containing the image, if any.
|
||||
///
|
||||
/// We need to keep a strong reference to the temporary file or it will be
|
||||
/// destroyed.
|
||||
file: Option<File>,
|
||||
/// The image decoder.
|
||||
decoder: Arc<TokioDrop<glycin::Image>>,
|
||||
/// The first frame of the image.
|
||||
first_frame: Arc<glycin::Frame>,
|
||||
}
|
||||
@ -134,7 +185,7 @@ impl fmt::Debug for Image {
|
||||
impl From<Image> for gdk::Paintable {
|
||||
fn from(value: Image) -> Self {
|
||||
if value.first_frame.delay().is_some() {
|
||||
AnimatedImagePaintable::new(value.file, value.loader, value.first_frame).upcast()
|
||||
AnimatedImagePaintable::new(value.decoder, value.first_frame, value.file).upcast()
|
||||
} else {
|
||||
value.first_frame.texture().upcast()
|
||||
}
|
||||
@ -157,9 +208,14 @@ impl ImageInfoLoader {
|
||||
async fn into_first_frame(self) -> Option<Frame> {
|
||||
match self {
|
||||
Self::File(file) => {
|
||||
let image_loader = image_loader(file).await.ok()?;
|
||||
let handle = spawn_tokio!(async move { image_loader.next_frame().await });
|
||||
Some(Frame::Glycin(handle.await.unwrap().ok()?))
|
||||
let (loader, _) = ImageDecoderSource::from(file).into_loader();
|
||||
|
||||
let frame = spawn_tokio!(async move { loader.load().await?.next_frame().await })
|
||||
.await
|
||||
.expect("task was not aborted")
|
||||
.ok()?;
|
||||
|
||||
Some(Frame::Glycin(frame))
|
||||
}
|
||||
Self::Texture(texture) => Some(Frame::Texture(texture)),
|
||||
}
|
||||
|
@ -19,13 +19,12 @@ use tokio::{
|
||||
};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use super::{Image, ImageError, load_image};
|
||||
use super::{Image, ImageDecoderSource, ImageError};
|
||||
use crate::{
|
||||
spawn_tokio,
|
||||
utils::{
|
||||
File,
|
||||
media::{FrameDimensions, MediaFileError},
|
||||
save_data_to_tmp_file,
|
||||
},
|
||||
};
|
||||
|
||||
@ -156,7 +155,7 @@ impl ImageRequestQueueInner {
|
||||
}
|
||||
|
||||
/// Add the given request to the queue.
|
||||
fn add_request(&mut self, request_id: ImageRequestId, request: ImageRequest) {
|
||||
fn queue_request(&mut self, request_id: ImageRequestId, request: ImageRequest) {
|
||||
let is_limit_reached = self.is_limit_reached();
|
||||
if !is_limit_reached || request.priority == ImageRequestPriority::High {
|
||||
// Spawn the request right away.
|
||||
@ -175,6 +174,31 @@ impl ImageRequestQueueInner {
|
||||
self.requests.insert(request_id, request);
|
||||
}
|
||||
|
||||
/// Add the given image request.
|
||||
///
|
||||
/// If another request for the same image already exists, this will reuse
|
||||
/// the same request.
|
||||
fn add_request(
|
||||
&mut self,
|
||||
inner: ImageLoaderRequest,
|
||||
priority: ImageRequestPriority,
|
||||
) -> ImageRequestHandle {
|
||||
let request_id = inner.source.request_id();
|
||||
|
||||
// If the request already exists, use the existing one.
|
||||
if let Some(request) = self.requests.get(&request_id) {
|
||||
let result_receiver = request.result_sender.subscribe();
|
||||
return ImageRequestHandle::new(result_receiver);
|
||||
}
|
||||
|
||||
// Build and add the request.
|
||||
let (request, result_receiver) = ImageRequest::new(inner, priority);
|
||||
|
||||
self.queue_request(request_id.clone(), request);
|
||||
|
||||
ImageRequestHandle::new(result_receiver)
|
||||
}
|
||||
|
||||
/// Add a request to download an image.
|
||||
///
|
||||
/// If another request for the same image already exists, this will reuse
|
||||
@ -186,24 +210,13 @@ impl ImageRequestQueueInner {
|
||||
dimensions: Option<FrameDimensions>,
|
||||
priority: ImageRequestPriority,
|
||||
) -> ImageRequestHandle {
|
||||
let data = DownloadRequestData {
|
||||
client,
|
||||
settings,
|
||||
dimensions,
|
||||
};
|
||||
let request_id = data.request_id();
|
||||
|
||||
// If the request already exists, use the existing one.
|
||||
if let Some(request) = self.requests.get(&request_id) {
|
||||
let result_receiver = request.result_sender.subscribe();
|
||||
return ImageRequestHandle::new(result_receiver);
|
||||
}
|
||||
|
||||
// Build and add the request.
|
||||
let (request, result_receiver) = ImageRequest::new(data, priority);
|
||||
self.add_request(request_id.clone(), request);
|
||||
|
||||
ImageRequestHandle::new(result_receiver)
|
||||
self.add_request(
|
||||
ImageLoaderRequest {
|
||||
source: ImageRequestSource::Download(DownloadRequest { client, settings }),
|
||||
dimensions,
|
||||
},
|
||||
priority,
|
||||
)
|
||||
}
|
||||
|
||||
/// Add a request to load an image from a file.
|
||||
@ -215,23 +228,15 @@ impl ImageRequestQueueInner {
|
||||
file: File,
|
||||
dimensions: Option<FrameDimensions>,
|
||||
) -> ImageRequestHandle {
|
||||
let data = FileRequestData { file, dimensions };
|
||||
let request_id = data.request_id();
|
||||
|
||||
// If the request already exists, use the existing one.
|
||||
if let Some(request) = self.requests.get(&request_id) {
|
||||
let result_receiver = request.result_sender.subscribe();
|
||||
return ImageRequestHandle::new(result_receiver);
|
||||
}
|
||||
|
||||
// Build and add the request.
|
||||
// Always use high priority because file requests should always be for
|
||||
// previewing a local image.
|
||||
let (request, result_receiver) = ImageRequest::new(data, ImageRequestPriority::High);
|
||||
|
||||
self.add_request(request_id.clone(), request);
|
||||
|
||||
ImageRequestHandle::new(result_receiver)
|
||||
self.add_request(
|
||||
ImageLoaderRequest {
|
||||
source: ImageRequestSource::File(file),
|
||||
dimensions,
|
||||
},
|
||||
ImageRequestPriority::High,
|
||||
)
|
||||
}
|
||||
|
||||
/// Mark the request with the given ID as stalled.
|
||||
@ -335,8 +340,8 @@ impl ImageRequestQueueInner {
|
||||
|
||||
/// A request for an image.
|
||||
struct ImageRequest {
|
||||
/// The data of the request.
|
||||
data: ImageRequestData,
|
||||
/// The request to the image loader.
|
||||
inner: ImageLoaderRequest,
|
||||
/// The priority of the request.
|
||||
priority: ImageRequestPriority,
|
||||
/// The sender of the channel to use to send the result.
|
||||
@ -352,13 +357,13 @@ struct ImageRequest {
|
||||
impl ImageRequest {
|
||||
/// Construct an image request with the given data and priority.
|
||||
fn new(
|
||||
data: impl Into<ImageRequestData>,
|
||||
inner: ImageLoaderRequest,
|
||||
priority: ImageRequestPriority,
|
||||
) -> (Self, broadcast::Receiver<Result<Image, ImageError>>) {
|
||||
let (result_sender, result_receiver) = broadcast::channel(1);
|
||||
(
|
||||
Self {
|
||||
data: data.into(),
|
||||
inner,
|
||||
priority,
|
||||
result_sender,
|
||||
retries_count: 0,
|
||||
@ -379,14 +384,14 @@ impl ImageRequest {
|
||||
|
||||
/// Spawn this request.
|
||||
fn spawn(&self) {
|
||||
let data = self.data.clone();
|
||||
let inner = self.inner.clone();
|
||||
let result_sender = self.result_sender.clone();
|
||||
let retries_count = self.retries_count;
|
||||
let task_handle = self.task_handle.clone();
|
||||
let stalled_timeout_source = self.stalled_timeout_source.clone();
|
||||
|
||||
let abort_handle = spawn_tokio!(async move {
|
||||
let request_id = data.request_id();
|
||||
let request_id = inner.source.request_id();
|
||||
|
||||
let stalled_timeout_source_clone = stalled_timeout_source.clone();
|
||||
let request_id_clone = request_id.clone();
|
||||
@ -404,7 +409,7 @@ impl ImageRequest {
|
||||
source.remove();
|
||||
}
|
||||
|
||||
let result = data.await;
|
||||
let result = inner.await;
|
||||
|
||||
// Cancel the timeout.
|
||||
if let Ok(Some(source)) = stalled_timeout_source.lock().map(|mut s| s.take()) {
|
||||
@ -451,7 +456,7 @@ impl Drop for ImageRequest {
|
||||
handle.abort();
|
||||
|
||||
// Broadcast that the request was aborted.
|
||||
let request_id = self.data.request_id();
|
||||
let request_id = self.inner.source.request_id();
|
||||
let result_sender = self.result_sender.clone();
|
||||
spawn_tokio!(async move {
|
||||
if let Err(error) = result_sender.send(Err(ImageError::Aborted)) {
|
||||
@ -462,26 +467,17 @@ impl Drop for ImageRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// The data of a request to download an image.
|
||||
/// A request to download an image.
|
||||
#[derive(Clone)]
|
||||
struct DownloadRequestData {
|
||||
struct DownloadRequest {
|
||||
/// The Matrix client to use to make the request.
|
||||
client: Client,
|
||||
/// The settings of the request.
|
||||
settings: MediaRequestParameters,
|
||||
/// The dimensions to request.
|
||||
dimensions: Option<FrameDimensions>,
|
||||
}
|
||||
|
||||
impl DownloadRequestData {
|
||||
/// The ID of the image request with this data.
|
||||
fn request_id(&self) -> ImageRequestId {
|
||||
ImageRequestId::Download(self.settings.unique_key())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for DownloadRequestData {
|
||||
type Output = Result<File, MediaFileError>;
|
||||
impl IntoFuture for DownloadRequest {
|
||||
type Output = Result<ImageDecoderSource, MediaFileError>;
|
||||
type IntoFuture = BoxFuture<'static, Self::Output>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
@ -491,155 +487,76 @@ impl IntoFuture for DownloadRequestData {
|
||||
|
||||
Box::pin(async move {
|
||||
let media = client.media();
|
||||
let data = match media.get_media_content(&settings, true).await {
|
||||
Ok(data) => data,
|
||||
Err(error) => {
|
||||
return Err(MediaFileError::from(error));
|
||||
}
|
||||
};
|
||||
let data = media
|
||||
.get_media_content(&settings, true)
|
||||
.await
|
||||
.map_err(MediaFileError::from)?;
|
||||
|
||||
let file = ImageDecoderSource::with_bytes(data).await?;
|
||||
|
||||
let file = save_data_to_tmp_file(data).await?;
|
||||
Ok(file)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The data of a request to load an image file into a paintable.
|
||||
/// A request to the image loader.
|
||||
#[derive(Clone)]
|
||||
struct FileRequestData {
|
||||
/// The image file to load.
|
||||
file: File,
|
||||
struct ImageLoaderRequest {
|
||||
/// The source of the image data.
|
||||
source: ImageRequestSource,
|
||||
/// The dimensions to request.
|
||||
dimensions: Option<FrameDimensions>,
|
||||
}
|
||||
|
||||
impl FileRequestData {
|
||||
/// The ID of the image request with this data.
|
||||
fn request_id(&self) -> ImageRequestId {
|
||||
ImageRequestId::File(self.file.path().expect("file has a path"))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for FileRequestData {
|
||||
type Output = Result<Image, glycin::ErrorCtx>;
|
||||
type IntoFuture = BoxFuture<'static, Self::Output>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
let Self { file, dimensions } = self;
|
||||
|
||||
Box::pin(async move { load_image(file, dimensions).await })
|
||||
}
|
||||
}
|
||||
|
||||
/// The data of an image request.
|
||||
#[derive(Clone)]
|
||||
enum ImageRequestData {
|
||||
/// The data for a download request.
|
||||
Download {
|
||||
/// The data to download the image.
|
||||
download_data: DownloadRequestData,
|
||||
/// The data to load the image into a paintable, after it was
|
||||
/// downloaded.
|
||||
file_data: Option<FileRequestData>,
|
||||
},
|
||||
/// The data for a file request.
|
||||
File(FileRequestData),
|
||||
}
|
||||
|
||||
impl ImageRequestData {
|
||||
/// The ID of the image request with this data.
|
||||
fn request_id(&self) -> ImageRequestId {
|
||||
match self {
|
||||
ImageRequestData::Download { download_data, .. } => download_data.request_id(),
|
||||
ImageRequestData::File(file_data) => file_data.request_id(),
|
||||
}
|
||||
}
|
||||
|
||||
/// The data for the next request with this image request data.
|
||||
fn into_next_request_data(self) -> DownloadOrFileRequestData {
|
||||
match self {
|
||||
Self::Download {
|
||||
download_data,
|
||||
file_data,
|
||||
} => {
|
||||
if let Some(file_data) = file_data {
|
||||
file_data.into()
|
||||
} else {
|
||||
download_data.into()
|
||||
}
|
||||
}
|
||||
Self::File(file_data) => file_data.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoFuture for ImageRequestData {
|
||||
impl IntoFuture for ImageLoaderRequest {
|
||||
type Output = Result<Image, ImageError>;
|
||||
type IntoFuture = BoxFuture<'static, Self::Output>;
|
||||
|
||||
fn into_future(self) -> Self::IntoFuture {
|
||||
Box::pin(async move {
|
||||
let file_data = match self.into_next_request_data() {
|
||||
DownloadOrFileRequestData::Download(download_data) => {
|
||||
let dimensions = download_data.dimensions;
|
||||
// Load the data from the source.
|
||||
let source = self.source.try_into_decoder_source().await?;
|
||||
|
||||
// Download the image to a file.
|
||||
match download_data.await {
|
||||
Ok(file) => FileRequestData { file, dimensions },
|
||||
Err(error) => {
|
||||
warn!("Could not retrieve image: {error}");
|
||||
return Err(error.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
DownloadOrFileRequestData::File(file_data) => file_data,
|
||||
};
|
||||
|
||||
// Load the image from the file.
|
||||
match file_data.clone().await {
|
||||
Ok(image) => Ok(image),
|
||||
Err(error) => {
|
||||
warn!("Could not load image from file: {error}");
|
||||
Err(error.into())
|
||||
}
|
||||
}
|
||||
// Decode the image from the data.
|
||||
source
|
||||
.decode_image(self.dimensions)
|
||||
.await
|
||||
.inspect_err(|error| warn!("Could not decode image: {error}"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DownloadRequestData> for ImageRequestData {
|
||||
fn from(download_data: DownloadRequestData) -> Self {
|
||||
Self::Download {
|
||||
download_data,
|
||||
file_data: None,
|
||||
/// The source for an image request.
|
||||
#[derive(Clone)]
|
||||
enum ImageRequestSource {
|
||||
/// The image must be downloaded from the media cache or the server.
|
||||
Download(DownloadRequest),
|
||||
/// The image is in the given file.
|
||||
File(File),
|
||||
}
|
||||
|
||||
impl ImageRequestSource {
|
||||
/// The ID of the image request with this source.
|
||||
fn request_id(&self) -> ImageRequestId {
|
||||
match self {
|
||||
Self::Download(download_request) => {
|
||||
ImageRequestId::Download(download_request.settings.unique_key())
|
||||
}
|
||||
Self::File(file) => ImageRequestId::File(file.path().expect("file should have a path")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileRequestData> for ImageRequestData {
|
||||
fn from(value: FileRequestData) -> Self {
|
||||
Self::File(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// The data of a download request or a file request.
|
||||
#[derive(Clone)]
|
||||
enum DownloadOrFileRequestData {
|
||||
/// The data for a download request.
|
||||
Download(DownloadRequestData),
|
||||
/// The data for a file request.
|
||||
File(FileRequestData),
|
||||
}
|
||||
|
||||
impl From<DownloadRequestData> for DownloadOrFileRequestData {
|
||||
fn from(download_data: DownloadRequestData) -> Self {
|
||||
Self::Download(download_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FileRequestData> for DownloadOrFileRequestData {
|
||||
fn from(value: FileRequestData) -> Self {
|
||||
Self::File(value)
|
||||
/// Try to download the image, if necessary.
|
||||
async fn try_into_decoder_source(self) -> Result<ImageDecoderSource, ImageError> {
|
||||
match self {
|
||||
Self::Download(download_request) => {
|
||||
// Download the image.
|
||||
Ok(download_request
|
||||
.await
|
||||
.inspect_err(|error| warn!("Could not retrieve image: {error}"))?)
|
||||
}
|
||||
Self::File(data) => Ok(data.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,9 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::{Cell, OnceCell, RefCell},
|
||||
fmt, fs, io,
|
||||
io::Write,
|
||||
fmt, fs,
|
||||
io::{self, Write},
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
rc::{Rc, Weak},
|
||||
sync::{Arc, LazyLock},
|
||||
@ -384,36 +385,30 @@ impl<T> AsyncAction<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that requires the tokio runtime to be running when dropped.
|
||||
///
|
||||
/// This is basically usable as a [`OnceCell`].
|
||||
/// A wrapper that requires the tokio runtime to be running when dropped.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TokioDrop<T>(OnceCell<T>);
|
||||
pub struct TokioDrop<T>(Option<T>);
|
||||
|
||||
impl<T> TokioDrop<T> {
|
||||
/// Create a new empty `TokioDrop`;
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Gets a reference to the underlying value.
|
||||
///
|
||||
/// Returns `None` if the cell is empty.
|
||||
pub fn get(&self) -> Option<&T> {
|
||||
self.0.get()
|
||||
}
|
||||
|
||||
/// Sets the contents of this cell to `value`.
|
||||
///
|
||||
/// Returns `Ok(())` if the cell was empty and `Err(value)` if it was full.
|
||||
pub(crate) fn set(&self, value: T) -> Result<(), T> {
|
||||
self.0.set(value)
|
||||
/// Create a new `TokioDrop` wrapping the given type.
|
||||
pub fn new(value: T) -> Self {
|
||||
Self(Some(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for TokioDrop<T> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
impl<T> Deref for TokioDrop<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
.as_ref()
|
||||
.expect("TokioDrop should always contain a value")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for TokioDrop<T> {
|
||||
fn from(value: T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -421,35 +416,12 @@ impl<T> Drop for TokioDrop<T> {
|
||||
fn drop(&mut self) {
|
||||
let _guard = RUNTIME.enter();
|
||||
|
||||
if let Some(inner) = self.0.take() {
|
||||
drop(inner);
|
||||
if let Some(value) = self.0.take() {
|
||||
drop(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: glib::property::Property> glib::property::Property for TokioDrop<T> {
|
||||
type Value = T::Value;
|
||||
}
|
||||
|
||||
impl<T> glib::property::PropertyGet for TokioDrop<T> {
|
||||
type Value = T;
|
||||
|
||||
fn get<R, F: Fn(&Self::Value) -> R>(&self, f: F) -> R {
|
||||
f(self.get().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> glib::property::PropertySet for TokioDrop<T> {
|
||||
type SetValue = T;
|
||||
|
||||
fn set(&self, v: Self::SetValue) {
|
||||
assert!(
|
||||
self.set(v).is_ok(),
|
||||
"TokioDrop value was already initialized"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of a resource that can be loaded.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, glib::Enum)]
|
||||
#[enum_type(name = "LoadingState")]
|
||||
@ -539,6 +511,7 @@ pub(crate) async fn save_data_to_tmp_file(data: Vec<u8>) -> Result<File, std::io
|
||||
}
|
||||
}
|
||||
let mut file = NamedTempFile::new_in(dir)?;
|
||||
tracing::debug!("Created new tmp file: {}", file.path().to_string_lossy());
|
||||
file.write_all(&data)?;
|
||||
|
||||
Ok(file.into())
|
||||
|
Loading…
x
Reference in New Issue
Block a user