diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 16714946..917795b9 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,10 +1,10 @@ -name: Android CI +name: Build Android APKs on: workflow_dispatch: jobs: - build: + buildReleaseAPK: runs-on: ubuntu-latest steps: @@ -18,7 +18,6 @@ jobs: distribution: 'oracle' cache: gradle - - name: Setup Android SDK uses: android-actions/setup-android@v2 @@ -37,7 +36,7 @@ jobs: - name: Add revoltbuild.properties from the revoltbuildPropertiesbase64 secret run: echo "${{ secrets.REVOLTBUILD_PROPERTIES_BASE64 }}" | base64 --decode > revoltbuild.properties - - name: Build with Gradle + - name: Build Release APK run: ./gradlew --no-daemon assembleRelease -x app:uploadSentryProguardMappingsRelease - name: Archive release APK @@ -46,4 +45,41 @@ jobs: name: release-apk retention-days: 5 path: | - app/build/outputs/apk/release/app-release.apk \ No newline at end of file + app/build/outputs/apk/release/app-release.apk + + buildDebugAPK: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'oracle' + cache: gradle + + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + + - uses: denoland/setup-deno@v2 + with: + # setting `cache-hash` implies `cache: true` and will replace + # the default cache-hash of `${{ hashFiles('**/deno.lock') }}` + cache-hash: ${{ hashFiles('**/deno.json') }} + + - name: Set up repo + run: chmod +x gradlew && deno run -A scripts/download_deps.ts --yes && cp revoltbuild.properties.example revoltbuild.properties && cp sentry.properties.example sentry.properties && cp app/google-services.json.example app/google-services.json && git submodule update --init --recursive + + - name: Build Debug APK + run: ./gradlew --no-daemon assembleDebug + + - name: Archive debug APK + uses: actions/upload-artifact@v4 + with: + name: debug-apk + retention-days: 5 + path: | + app/build/outputs/apk/debug/app-debug.apk diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9e832e4b..a929169a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -80,7 +80,7 @@ android { minSdk = 24 targetSdk = 36 versionCode = Integer.parseInt("001_003_206".replace("_", ""), 10) - versionName = "1.3.6bb-forked" + versionName = "1.3.6bc-forked" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt b/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt index 0450008c..c3269eb9 100644 --- a/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt +++ b/app/src/main/java/chat/revolt/markdown/jbm/JBMRenderer.kt @@ -156,18 +156,29 @@ val avatarPadding = 2.dp @JBM fun JBMRenderer(content: String, modifier: Modifier = Modifier) { val state = LocalJBMarkdownTreeState.current - val flavor = if (state.enhanced) RSMEnhancedFlavourDescriptor() else RSMFlavourDescriptor() + android.util.Log.d("JBMRenderer", "JBMRenderer called with content: '${content.take(50)}...', enhanced: ${state.enhanced}") - var tree by remember { mutableStateOf(JBMApi.parse(content, flavor)) } + val preprocessedContent = content.replace(Regex("<@([0-9A-HJKMNP-TV-Z]{26})>")) { match -> + "ZZUSERMENTION${match.groupValues[1]}ZZ" + } + android.util.Log.d("JBMRenderer", "Preprocessed content: '${preprocessedContent.take(50)}...'") + + val flavor = if (state.enhanced) RSMEnhancedFlavourDescriptor() else RSMFlavourDescriptor() + android.util.Log.d("JBMRenderer", "Using flavor: ${flavor.javaClass.simpleName}") + + var tree by remember { mutableStateOf(JBMApi.parse(preprocessedContent, flavor)) } var revealedSpoilers by remember { mutableStateOf(setOf()) } LaunchedEffect(content, state.enhanced) { - tree = JBMApi.parse(content, flavor) + val preprocessedContent = content.replace(Regex("<@([0-9A-HJKMNP-TV-Z]{26})>")) { match -> + "ZZUSERMENTION${match.groupValues[1]}ZZ" + } + tree = JBMApi.parse(preprocessedContent, flavor) } CompositionLocalProvider( LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy( - sourceText = content, + sourceText = preprocessedContent, revealedSpoilers = revealedSpoilers, onSpoilerToggle = { spoilerId -> revealedSpoilers = if (revealedSpoilers.contains(spoilerId)) { @@ -213,6 +224,7 @@ private fun annotateText( val sourceText = state.sourceText return buildAnnotatedString { + android.util.Log.d("JBMRenderer", "annotateText called with node type: ${node.type}") if (node.type.toString().contains("SPOILER", ignoreCase = true) || sourceText.contains("||")) { android.util.Log.d("JBMRenderer", "Processing node: ${node.type}, text contains spoilers: ${sourceText.contains("||")}") @@ -224,18 +236,67 @@ private fun annotateText( } else { node.getTextInNode(sourceText) } - append(source) + + val text = source.toString() + val mentionRegex = Regex("(ZZUSERMENTION([0-9A-HJKMNP-TV-Z]{26})ZZ)") + + android.util.Log.d("JBMRenderer", "Processing TEXT: '$text'") + android.util.Log.d("JBMRenderer", "Contains mention: ${mentionRegex.containsMatchIn(text)}") + + if (mentionRegex.containsMatchIn(text)) { + android.util.Log.d("JBMRenderer", "Found mentions in text") + var lastIndex = 0 + mentionRegex.findAll(text).forEach { match -> + android.util.Log.d("JBMRenderer", "Match: '${match.value}', groups: ${match.groupValues}") + + if (match.range.first > lastIndex) { + append(text.substring(lastIndex, match.range.first)) + } + + val userId = match.groupValues[2] + android.util.Log.d("JBMRenderer", "Processing mention for userId: '$userId'") + + pushStringAnnotation( + tag = JBMAnnotations.UserMention.tag, + annotation = userId + ) + pushStyle( + SpanStyle( + color = state.colors.clickable, + background = state.colors.clickableBackground + ) + ) + append(" ") + appendInlineContent(JBMAnnotations.UserAvatar.tag, userId) + append(" ") + append(MentionResolver.resolveUser(userId, state.currentServer)) + append(" ") + pop() + pop() + + lastIndex = match.range.last + 1 + } + + if (lastIndex < text.length) { + append(text.substring(lastIndex)) + } + } else { + append(text) + } } RSMElementTypes.USER_MENTION -> { val contents = node.getTextInNode(sourceText).toString() + android.util.Log.d("JBMRenderer", "Processing USER_MENTION: '$contents'") val userId = contents.removeSurrounding("<@", ">") + android.util.Log.d("JBMRenderer", "Extracted userId: '$userId', isUlid: ${userId.isUlid()}") if (userId == contents || !userId.isUlid()) { - // Invalid user mention. Append as if it were regular text. + android.util.Log.d("JBMRenderer", "Invalid user mention, treating as text") for (child in node.children) { append(annotateText(state, child, revealedSpoilers)) } } else { + android.util.Log.d("JBMRenderer", "Valid user mention, resolving to: ${MentionResolver.resolveUser(userId, state.currentServer)}") pushStringAnnotation( tag = JBMAnnotations.UserMention.tag, annotation = userId @@ -329,6 +390,22 @@ private fun annotateText( } } + RSMElementTypes.CUSTOM_EMOTE -> { + val contents = node.getTextInNode(sourceText).toString() + val emoteId = contents.removeSurrounding(":", ":") + if (emoteId == contents || !emoteId.isUlid()) { + for (child in node.children) { + append(annotateText(state, child, revealedSpoilers)) + } + } else { + pushStringAnnotation( + tag = JBMAnnotations.CustomEmote.tag, + annotation = emoteId + ) + appendInlineContent(JBMAnnotations.CustomEmote.tag, emoteId) + pop() + } + } RSMElementTypes.SPOILER -> { android.util.Log.d("JBMRenderer", "Found SPOILER node with ${node.children.size} children") @@ -543,6 +620,7 @@ private fun annotateText( append(node.getTextInNode(sourceText)) } + else -> { withStyle(SpanStyle(color = Color.Cyan)) { append("[${node.type.name}]{\n") diff --git a/app/src/main/java/chat/revolt/markdown/jbm/RSMEnhancedFlavourDescriptor.kt b/app/src/main/java/chat/revolt/markdown/jbm/RSMEnhancedFlavourDescriptor.kt index e336d756..b6858338 100644 --- a/app/src/main/java/chat/revolt/markdown/jbm/RSMEnhancedFlavourDescriptor.kt +++ b/app/src/main/java/chat/revolt/markdown/jbm/RSMEnhancedFlavourDescriptor.kt @@ -1,5 +1,6 @@ package chat.revolt.markdown.jbm +import chat.revolt.markdown.jbm.sequentialparsers.CustomEmoteParser import chat.revolt.markdown.jbm.sequentialparsers.SpoilerParser import org.intellij.markdown.parser.sequentialparsers.SequentialParser import org.intellij.markdown.parser.sequentialparsers.SequentialParserManager @@ -14,7 +15,7 @@ class RSMEnhancedFlavourDescriptor : RSMFlavourDescriptor() { override fun getParserSequence(): List { val originalParsers = super@RSMEnhancedFlavourDescriptor.sequentialParserManager.getParserSequence() - return originalParsers + SpoilerParser() + return originalParsers + CustomEmoteParser() + SpoilerParser() } } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt index 0489ba8d..9f6d9a37 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -166,6 +166,8 @@ class ChatRouterViewModel @Inject constructor( val current = kvStorage.get("currentDestination") setSaveDestination(ChatRouterDestination.fromString(current ?: "")) + // Disabled for forked version might use later ;) + /* latestChangelogRead = changelogs.hasSeenCurrent() latestChangelog = changelogs.getLatestChangelogCode() latestChangelogBody = @@ -173,7 +175,12 @@ class ChatRouterViewModel @Inject constructor( if (!latestChangelogRead) { changelogs.markAsSeen() } + */ + // Always mark changelog as read to prevent popup + latestChangelogRead = true + // Disabled for forked version + /* val seenEarlyAccess = kvStorage.getBoolean("spark/earlyAccess/dismissed") val seenSwipeToReply = kvStorage.getBoolean("spark/swipeToReply/dismissed") if (seenEarlyAccess == null) { @@ -186,6 +193,7 @@ class ChatRouterViewModel @Inject constructor( if (seenEarlyAccess == true && seenSwipeToReply != true) { showSwipeToReplySpark = true } + */ val hasNotificationPermission = NotificationManagerCompat.from(context).areNotificationsEnabled()