Compare commits

...

12 Commits

Author SHA1 Message Date
GitHub Actions
60edb063dd chore: bump version to 4.11.133 [skip ci] 2026-07-23 14:44:09 +00:00
0c85c36838 Skip Actions artifact upload for Android APKs.
All checks were successful
Build / increment-version (push) Successful in 6s
Build / build-android (push) Successful in 5m11s
upload-artifact times out against git.ruv.wtf from the runner; publish the APK via the reachable synbox Gitea API in the build job instead.
2026-07-24 00:44:01 +10:00
GitHub Actions
d59646c630 chore: bump version to 4.11.132 [skip ci] 2026-07-23 14:36:29 +00:00
c1985eda15 Use a relative cinny submodule URL so CI can clone it.
Some checks failed
Build / increment-version (push) Successful in 5s
Build / create-release (push) Has been cancelled
Build / build-android (push) Has been cancelled
Absolute git.ruv.wtf URLs fail on runners that only reach the internal Gitea host.
2026-07-24 00:36:23 +10:00
GitHub Actions
2e018b0367 chore: bump version to 4.11.131 [skip ci] 2026-07-23 14:30:39 +00:00
8046474b8a Fix background push notifying for muted and mention-only rooms.
Some checks failed
Build / increment-version (push) Successful in 5s
Build / build-android (push) Failing after 4m37s
Build / create-release (push) Has been skipped
Filter Android sync wakes by push rules/unread counts, align JS system notifications, and point the cinny submodule at git.ruv.wtf.
2026-07-24 00:30:33 +10:00
GitHub Actions
9821b16968 chore: bump version to 4.11.130 [skip ci] 2026-07-22 15:32:50 +00:00
a887cd39f1 Fix Stationery theme on Android by keeping overlay hooks and CSS.
All checks were successful
Build / increment-version (push) Successful in 4s
Build / build-android (push) Successful in 4m22s
Build / create-release (push) Successful in 13s
2026-07-23 01:32:44 +10:00
GitHub Actions
ac863b3c1a chore: bump version to 4.11.129 [skip ci] 2026-07-22 15:22:58 +00:00
a889b67c2e Sync cinny: Stationery and Stationery Dark themes.
All checks were successful
Build / increment-version (push) Successful in 5s
Build / build-android (push) Successful in 5m1s
Build / create-release (push) Successful in 13s
2026-07-23 01:22:50 +10:00
GitHub Actions
4618d59605 chore: bump version to 4.11.128 [skip ci] 2026-07-14 17:20:29 +00:00
697f03bc51 fix: bump cinny for Inter Variable / emoji space metrics
All checks were successful
Build / increment-version (push) Successful in 8s
Build / build-android (push) Successful in 4m52s
Build / create-release (push) Successful in 14s
Point at cinny 154f4dfd (correct Inter Variable name + emoji unicode-range)
and keep the mobile overlay font stack in sync.
2026-07-15 03:20:21 +10:00
10 changed files with 1673 additions and 57 deletions

View File

@@ -102,29 +102,7 @@ jobs:
- name: Build Android APK - name: Build Android APK
run: npm run android:apk:linux run: npm run android:apk:linux
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: Paarrot-Android.apk
path: android/app/build/outputs/apk/debug/Paarrot-*.apk
create-release:
runs-on: ubuntu-latest
needs: [increment-version, build-android]
if: always() && needs.build-android.result == 'success'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Pull latest (after version bump)
if: github.ref == 'refs/heads/master'
run: git pull origin master
- name: Get version - name: Get version
id: version id: version
run: | run: |
@@ -135,7 +113,14 @@ jobs:
fi fi
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION" echo "Version: $VERSION"
- name: Prepare release files
run: |
mkdir -p release-files
find android/app/build/outputs/apk/debug -type f -name "Paarrot-*.apk" -exec cp {} release-files/ \;
ls -la release-files/
test -n "$(ls -A release-files/*.apk 2>/dev/null)"
- name: Check if tag exists - name: Check if tag exists
id: tag_check id: tag_check
run: | run: |
@@ -147,7 +132,7 @@ jobs:
echo "Tag v$VERSION does not exist" echo "Tag v$VERSION does not exist"
echo "exists=false" >> $GITHUB_OUTPUT echo "exists=false" >> $GITHUB_OUTPUT
fi fi
- name: Create and push tag - name: Create and push tag
if: steps.tag_check.outputs.exists == 'false' if: steps.tag_check.outputs.exists == 'false'
run: | run: |
@@ -155,18 +140,9 @@ jobs:
git config user.email "actions@github.com" git config user.email "actions@github.com"
git tag -a "v${{ steps.version.outputs.VERSION }}" -m "Release v${{ steps.version.outputs.VERSION }}" git tag -a "v${{ steps.version.outputs.VERSION }}" -m "Release v${{ steps.version.outputs.VERSION }}"
git push origin "v${{ steps.version.outputs.VERSION }}" git push origin "v${{ steps.version.outputs.VERSION }}"
- name: Download all artifacts # Upload via the internal Gitea API. actions/upload-artifact times out against
uses: actions/download-artifact@v3 # git.ruv.wtf:443 from this runner; synbox:8418 is reachable on the LAN.
with:
path: artifacts
- name: Prepare release files
run: |
mkdir -p release-files
find artifacts -type f -name "*.apk" -exec cp {} release-files/ \;
ls -la release-files/
- name: Create or Update Gitea Release - name: Create or Update Gitea Release
env: env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -175,10 +151,10 @@ jobs:
TAG_NAME="v${VERSION}" TAG_NAME="v${VERSION}"
API_BASE="http://synbox.ruv.wtf:8418/api/v1" API_BASE="http://synbox.ruv.wtf:8418/api/v1"
REPO="litruv/cinny-mobile" REPO="litruv/cinny-mobile"
RELEASE_ID=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \ RELEASE_ID=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \
"${API_BASE}/repos/${REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty') "${API_BASE}/repos/${REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty')
if [ -z "$RELEASE_ID" ]; then if [ -z "$RELEASE_ID" ]; then
echo "Creating new release for ${TAG_NAME}..." echo "Creating new release for ${TAG_NAME}..."
RELEASE_ID=$(curl -s -X POST \ RELEASE_ID=$(curl -s -X POST \
@@ -198,7 +174,7 @@ jobs:
"${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets/${ASSET_ID}" "${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets/${ASSET_ID}"
done done
fi fi
echo "Uploading assets..." echo "Uploading assets..."
for FILE in release-files/*; do for FILE in release-files/*; do
FILENAME="Paarrot-${VERSION}.apk" FILENAME="Paarrot-${VERSION}.apk"
@@ -210,9 +186,9 @@ jobs:
"${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${FILENAME}" "${API_BASE}/repos/${REPO}/releases/${RELEASE_ID}/assets?name=${FILENAME}"
echo "" echo ""
done done
echo "Gitea release complete!" echo "Gitea release complete!"
- name: Create GitHub Release - name: Create GitHub Release
env: env:
GH_TOKEN: ${{ secrets.GHTOKEN }} GH_TOKEN: ${{ secrets.GHTOKEN }}
@@ -220,10 +196,10 @@ jobs:
VERSION="${{ steps.version.outputs.VERSION }}" VERSION="${{ steps.version.outputs.VERSION }}"
TAG_NAME="v${VERSION}" TAG_NAME="v${VERSION}"
GITHUB_REPO="Paarrot/Paarrot-Mobile" GITHUB_REPO="Paarrot/Paarrot-Mobile"
RELEASE_ID=$(curl -s -H "Authorization: token ${GH_TOKEN}" \ RELEASE_ID=$(curl -s -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty') "https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${TAG_NAME}" | jq -r '.id // empty')
if [ -z "$RELEASE_ID" ]; then if [ -z "$RELEASE_ID" ]; then
echo "Creating new GitHub release for ${TAG_NAME}..." echo "Creating new GitHub release for ${TAG_NAME}..."
RELEASE_ID=$(curl -s -X POST \ RELEASE_ID=$(curl -s -X POST \
@@ -235,11 +211,11 @@ jobs:
else else
echo "GitHub release exists with ID: ${RELEASE_ID}" echo "GitHub release exists with ID: ${RELEASE_ID}"
fi fi
echo "Uploading assets to GitHub..." echo "Uploading assets to GitHub..."
UPLOAD_URL=$(curl -s -H "Authorization: token ${GH_TOKEN}" \ UPLOAD_URL=$(curl -s -H "Authorization: token ${GH_TOKEN}" \
"https://api.github.com/repos/${GITHUB_REPO}/releases/${RELEASE_ID}" | jq -r '.upload_url' | sed 's/{?name,label}//') "https://api.github.com/repos/${GITHUB_REPO}/releases/${RELEASE_ID}" | jq -r '.upload_url' | sed 's/{?name,label}//')
for FILE in release-files/*; do for FILE in release-files/*; do
FILENAME="Paarrot-${VERSION}.apk" FILENAME="Paarrot-${VERSION}.apk"
echo "Uploading ${FILENAME} to GitHub..." echo "Uploading ${FILENAME} to GitHub..."
@@ -250,5 +226,5 @@ jobs:
"${UPLOAD_URL}?name=${FILENAME}" "${UPLOAD_URL}?name=${FILENAME}"
echo "" echo ""
done done
echo "GitHub release complete!" echo "GitHub release complete!"

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "cinny"] [submodule "cinny"]
path = cinny path = cinny
url = http://synbox.ruv.wtf:8418/litruv/cinny.git url = ../cinny.git

View File

@@ -151,6 +151,21 @@ class MatrixSyncService : Service() {
} }
} }
private enum class RoomNotifyMode {
MUTE,
ALL_MESSAGES,
MENTIONS_AND_KEYWORDS,
}
private data class NotifyContext(
val myUserId: String,
val myDisplayName: String?,
val myLocalpart: String?,
val keywords: List<String>,
val directRoomIds: Set<String>,
val pushRules: JSONObject?,
)
private fun processRoomEvents(sync: JSONObject, myUserId: String) { private fun processRoomEvents(sync: JSONObject, myUserId: String) {
val prefs = applicationContext.getSharedPreferences(SyncServicePlugin.PREFS, Context.MODE_PRIVATE) val prefs = applicationContext.getSharedPreferences(SyncServicePlugin.PREFS, Context.MODE_PRIVATE)
val homeserver = prefs.getString(EXTRA_HOMESERVER, null)?.trimEnd('/') ?: return val homeserver = prefs.getString(EXTRA_HOMESERVER, null)?.trimEnd('/') ?: return
@@ -159,10 +174,20 @@ class MatrixSyncService : Service() {
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
ensureMessageChannel(nm) ensureMessageChannel(nm)
val notifyCtx = loadNotifyContext(homeserver, token, myUserId)
val roomIds = joinedRooms.keys().asSequence().toList() val roomIds = joinedRooms.keys().asSequence().toList()
for (roomId in roomIds) { for (roomId in roomIds) {
val timeline = joinedRooms.optJSONObject(roomId) val roomData = joinedRooms.optJSONObject(roomId) ?: continue
?.optJSONObject("timeline") ?: continue val mode = resolveRoomNotifyMode(roomId, notifyCtx)
// Homeserver unread counts already apply push rules — skip rooms with nothing to notify.
val unread = roomData.optJSONObject("unread_notifications")
val notificationCount = unread?.optInt("notification_count", 0) ?: 0
if (mode == RoomNotifyMode.MUTE || notificationCount <= 0) continue
val mentionsOnly = mode == RoomNotifyMode.MENTIONS_AND_KEYWORDS
val timeline = roomData.optJSONObject("timeline") ?: continue
val events = timeline.optJSONArray("events") ?: continue val events = timeline.optJSONArray("events") ?: continue
for (i in 0 until events.length()) { for (i in 0 until events.length()) {
@@ -174,6 +199,8 @@ class MatrixSyncService : Service() {
if (eventId.isNotBlank() && !shownEventIds.add(eventId)) continue if (eventId.isNotBlank() && !shownEventIds.add(eventId)) continue
val content = event.optJSONObject("content") ?: continue val content = event.optJSONObject("content") ?: continue
if (mentionsOnly && !isSpecialMessage(content, notifyCtx)) continue
val msgtype = content.optString("msgtype") val msgtype = content.optString("msgtype")
val rawBody = content.optString("body") val rawBody = content.optString("body")
val body = when (msgtype) { val body = when (msgtype) {
@@ -203,6 +230,144 @@ class MatrixSyncService : Service() {
} }
} }
/** Load push rules, DM list, keywords, and own display name for notification filtering. */
private fun loadNotifyContext(homeserver: String, token: String, myUserId: String): NotifyContext {
val pushRules = fetchJson("$homeserver/_matrix/client/v3/pushrules/", token)
val accountDataDirect = fetchJson("$homeserver/_matrix/client/v3/user/${urlEncode(myUserId)}/account_data/m.direct", token)
val profile = fetchJson("$homeserver/_matrix/client/v3/profile/${urlEncode(myUserId)}", token)
val directRoomIds = mutableSetOf<String>()
accountDataDirect?.keys()?.forEach { key ->
val rooms = accountDataDirect.optJSONArray(key) ?: return@forEach
for (i in 0 until rooms.length()) {
rooms.optString(i).takeIf { it.isNotBlank() }?.let { directRoomIds.add(it) }
}
}
val keywords = mutableListOf<String>()
val contentRules = pushRules?.optJSONObject("global")?.optJSONArray("content")
if (contentRules != null) {
for (i in 0 until contentRules.length()) {
val rule = contentRules.optJSONObject(i) ?: continue
if (rule.optBoolean("enabled", true).not()) continue
if (!actionsIncludeNotify(rule.optJSONArray("actions"))) continue
rule.optString("pattern").takeIf { it.isNotBlank() }?.let { keywords.add(it) }
}
}
return NotifyContext(
myUserId = myUserId,
myDisplayName = profile?.optString("displayname")?.takeIf { it.isNotBlank() },
myLocalpart = myUserId.substringAfter("@").substringBefore(":").takeIf { it.isNotBlank() },
keywords = keywords,
directRoomIds = directRoomIds,
pushRules = pushRules,
)
}
/**
* Mirrors Cinny/Paarrot room notification modes:
* Mute override → mute; room notify → all; room dont_notify → mentions;
* unset DM → all; unset room → mentions (app Default).
*/
private fun resolveRoomNotifyMode(roomId: String, ctx: NotifyContext): RoomNotifyMode {
val global = ctx.pushRules?.optJSONObject("global")
// If push rules are unavailable, rely on homeserver unread_notifications only.
?: return RoomNotifyMode.ALL_MESSAGES
val overrides = global.optJSONArray("override")
if (overrides != null) {
for (i in 0 until overrides.length()) {
val rule = overrides.optJSONObject(i) ?: continue
if (rule.optString("rule_id") != roomId) continue
if (rule.optBoolean("enabled", true).not()) continue
if (!actionsIncludeNotify(rule.optJSONArray("actions"))) {
return RoomNotifyMode.MUTE
}
}
}
val roomRules = global.optJSONArray("room")
if (roomRules != null) {
for (i in 0 until roomRules.length()) {
val rule = roomRules.optJSONObject(i) ?: continue
if (rule.optString("rule_id") != roomId) continue
if (rule.optBoolean("enabled", true).not()) continue
return if (actionsIncludeNotify(rule.optJSONArray("actions"))) {
RoomNotifyMode.ALL_MESSAGES
} else {
RoomNotifyMode.MENTIONS_AND_KEYWORDS
}
}
}
return if (ctx.directRoomIds.contains(roomId)) {
RoomNotifyMode.ALL_MESSAGES
} else {
RoomNotifyMode.MENTIONS_AND_KEYWORDS
}
}
private fun actionsIncludeNotify(actions: org.json.JSONArray?): Boolean {
if (actions == null) return false
for (i in 0 until actions.length()) {
when (val action = actions.opt(i)) {
is String -> if (action == "notify") return true
is JSONObject -> if (action.has("set_tweak")) { /* tweaks only */ }
}
}
return false
}
private fun isSpecialMessage(content: JSONObject, ctx: NotifyContext): Boolean {
val mentions = content.optJSONObject("m.mentions")
if (mentions != null) {
val userIds = mentions.optJSONArray("user_ids")
if (userIds != null) {
for (i in 0 until userIds.length()) {
if (userIds.optString(i) == ctx.myUserId) return true
}
}
if (mentions.optBoolean("room", false)) return true
}
val body = content.optString("body").lowercase()
if (body.isBlank()) return false
if (body.contains("@room")) return true
ctx.myDisplayName?.lowercase()?.takeIf { it.isNotBlank() }?.let {
if (body.contains(it)) return true
}
ctx.myLocalpart?.lowercase()?.let {
if (body.contains(it)) return true
}
for (keyword in ctx.keywords) {
if (body.contains(keyword.lowercase())) return true
}
return false
}
private fun urlEncode(value: String): String = URLEncoder.encode(value, "UTF-8")
private fun fetchJson(urlString: String, token: String): JSONObject? {
return try {
val conn = URL(urlString).openConnection() as HttpURLConnection
try {
conn.requestMethod = "GET"
conn.setRequestProperty("Authorization", "Bearer $token")
conn.setRequestProperty("Accept", "application/json")
conn.connectTimeout = 4_000
conn.readTimeout = 6_000
if (conn.responseCode != 200) return null
JSONObject(conn.inputStream.bufferedReader().readText())
} finally {
conn.disconnect()
}
} catch (e: Exception) {
Log.w(TAG, "Failed to fetch $urlString: ${e.message}")
null
}
}
private fun resolveProfile(mxid: String, homeserver: String, token: String): UserProfile { private fun resolveProfile(mxid: String, homeserver: String, token: String): UserProfile {
val cachedName = displayNameCache[mxid] val cachedName = displayNameCache[mxid]
if (cachedName != null) return UserProfile(cachedName, avatarCache[mxid]) if (cachedName != null) return UserProfile(cachedName, avatarCache[mxid])

2
cinny

Submodule cinny updated: 9c7f71896c...b52926f7d8

View File

@@ -7,6 +7,7 @@ import * as swipeCss from '../mobile/mobile-gestures.css';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize'; import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
import { SidebarDockedCallPanel } from '../../features/call/SidebarDockedCallPanel'; import { SidebarDockedCallPanel } from '../../features/call/SidebarDockedCallPanel';
import { useShowCompactMasterView } from '../../hooks/useCompactNav'; import { useShowCompactMasterView } from '../../hooks/useCompactNav';
import { isStationeryTheme, useTheme } from '../../hooks/useTheme';
type PageRootProps = { type PageRootProps = {
nav: ReactNode; nav: ReactNode;
@@ -74,6 +75,7 @@ export function PageNav({
grow={isMobile ? 'Yes' : undefined} grow={isMobile ? 'Yes' : undefined}
className={classNames(css.PageNav({ size }), className)} className={classNames(css.PageNav({ size }), className)}
shrink={isMobile ? 'Yes' : 'No'} shrink={isMobile ? 'Yes' : 'No'}
data-page-nav=""
> >
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
{children} {children}
@@ -89,6 +91,7 @@ export const PageNavHeader = as<'header', css.PageNavHeaderVariants>(
className={classNames(css.PageNavHeader({ outlined }), className)} className={classNames(css.PageNavHeader({ outlined }), className)}
variant="Background" variant="Background"
size="600" size="600"
data-folder-tab="nav"
{...props} {...props}
ref={ref} ref={ref}
/> />
@@ -104,13 +107,16 @@ export function PageNavContent({
scrollRef?: MutableRefObject<HTMLDivElement | null>; scrollRef?: MutableRefObject<HTMLDivElement | null>;
scrollProps?: React.ComponentProps<typeof Scroll>; scrollProps?: React.ComponentProps<typeof Scroll>;
}) { }) {
const theme = useTheme();
const hideScrollbar = isStationeryTheme(theme);
return ( return (
<Box grow="Yes" direction="Column"> <Box grow="Yes" direction="Column">
<Scroll <Scroll
ref={scrollRef} ref={scrollRef}
variant="Background" variant="Background"
direction="Vertical" direction="Vertical"
size="300" size={hideScrollbar ? '0' : '300'}
hideTrack hideTrack
visibility="Hover" visibility="Hover"
{...scrollProps} {...scrollProps}
@@ -126,6 +132,7 @@ export const Page = as<'div'>(({ className, ...props }, ref) => (
grow="Yes" grow="Yes"
direction="Column" direction="Column"
className={classNames(ContainerColor({ variant: 'Surface' }), className)} className={classNames(ContainerColor({ variant: 'Surface' }), className)}
data-page=""
{...props} {...props}
ref={ref} ref={ref}
/> />
@@ -137,6 +144,7 @@ export const PageHeader = as<'div', css.PageHeaderVariants>(
as="header" as="header"
size="600" size="600"
className={classNames(css.PageHeader({ balance, outlined }), className)} className={classNames(css.PageHeader({ balance, outlined }), className)}
data-folder-tab-bar=""
{...props} {...props}
ref={ref} ref={ref}
/> />

View File

@@ -802,8 +802,9 @@ export const Message = as<'div', MessageProps>(
gap="300" gap="300"
direction={messageLayout === MessageLayout.Compact ? 'RowReverse' : 'Row'} direction={messageLayout === MessageLayout.Compact ? 'RowReverse' : 'Row'}
justifyContent="SpaceBetween" justifyContent="SpaceBetween"
alignItems="Baseline" alignItems="Center"
grow="Yes" grow="Yes"
data-message-header=""
> >
<Box alignItems="Center" gap="200"> <Box alignItems="Center" gap="200">
<Username <Username

View File

@@ -376,6 +376,14 @@ function MessageNotifications() {
return; return;
} }
// Match TitleBar / room bell settings: Default and Mentions rooms only notify on highlights.
const notificationType = getNotificationType(mx, room.roomId);
const isDm = mDirects.has(room.roomId);
const mentionsOnly =
notificationType === NotificationType.MentionsAndKeywords ||
(notificationType === NotificationType.Default && !isDm);
if (mentionsOnly && unreadInfo.highlight === 0) return;
if ( if (
showNotifications && showNotifications &&
((isTauri() && !isElectron()) || isCapacitorNative() || notificationPermission('granted')) ((isTauri() && !isElectron()) || isCapacitorNative() || notificationPermission('granted'))
@@ -397,7 +405,6 @@ function MessageNotifications() {
messageBody = typeof content.body === 'string' ? content.body : undefined; messageBody = typeof content.body === 'string' ? content.body : undefined;
} }
const isDm = room.getJoinedMemberCount() === 2 && !room.isSpaceRoom();
notify({ notify({
roomName: room.name ?? 'Unknown', roomName: room.name ?? 'Unknown',
roomAvatar: avatarMxc roomAvatar: avatarMxc
@@ -428,6 +435,7 @@ function MessageNotifications() {
notify, notify,
selectedRoomId, selectedRoomId,
useAuthentication, useAuthentication,
mDirects,
]); ]);
return null; return null;

View File

@@ -7,6 +7,11 @@ export const isCapacitorAndroid = Boolean(
cap?.isNativePlatform?.() && cap?.getPlatform?.() === 'android' cap?.isNativePlatform?.() && cap?.getPlatform?.() === 'android'
); );
// Stationery signatures use Caveat on all platforms (including Android WebView).
await import('@fontsource/caveat/500.css');
await import('@fontsource/caveat/600.css');
await import('@fontsource/caveat/700.css');
if (isCapacitorAndroid) { if (isCapacitorAndroid) {
document.documentElement.classList.add('android-capacitor'); document.documentElement.classList.add('android-capacitor');
document.documentElement.style.setProperty( document.documentElement.style.setProperty(

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "paarrot", "name": "paarrot",
"version": "4.11.127", "version": "4.11.133",
"description": "Paarrot - A Matrix client based on Cinny", "description": "Paarrot - A Matrix client based on Cinny",
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=18.0.0"