From af9af1f6a2e153a67047264093f1bf549435b4d3 Mon Sep 17 00:00:00 2001 From: Alex Yong Date: Wed, 6 Aug 2025 21:32:50 +0000 Subject: [PATCH 1/2] feat: add jump to reply message functionality --- .../chat/revolt/composables/chat/Message.kt | 34 +++++++++++-------- .../screens/chat/atoms/RegularMessage.kt | 4 +++ .../chat/views/channel/ChannelScreen.kt | 12 +++++++ .../views/channel/ChannelScreenViewModel.kt | 23 +++++++++++++ 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/chat/revolt/composables/chat/Message.kt b/app/src/main/java/chat/revolt/composables/chat/Message.kt index 4e68651d..0e5bf881 100644 --- a/app/src/main/java/chat/revolt/composables/chat/Message.kt +++ b/app/src/main/java/chat/revolt/composables/chat/Message.kt @@ -196,6 +196,8 @@ fun Message( onAddReaction: () -> Unit = {}, fromWebhook: Boolean = false, webhookName: String? = null, + jumpToMessage: (String) -> Unit = {}, + highlightedMessageId: String? = null, modifier: Modifier = Modifier ) { val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator() @@ -281,16 +283,21 @@ fun Message( } else { Column( modifier = Modifier.then( - if ((message.mentions?.contains(RevoltAPI.selfId) == true) - || mentionsSelfRole - || message.flags has MessageFlag.MentionsOnline - || message.flags has MessageFlag.MentionsEveryone - ) { - Modifier.background( - MaterialTheme.colorScheme.primary.copy(alpha = 0.1f) - ) - } else { - Modifier + when { + highlightedMessageId == message.id -> { + Modifier.background( + MaterialTheme.colorScheme.secondary.copy(alpha = 0.3f) + ) + } + (message.mentions?.contains(RevoltAPI.selfId) == true) + || mentionsSelfRole + || message.flags has MessageFlag.MentionsOnline + || message.flags has MessageFlag.MentionsEveryone -> { + Modifier.background( + MaterialTheme.colorScheme.primary.copy(alpha = 0.1f) + ) + } + else -> Modifier } ) ) { @@ -306,11 +313,8 @@ fun Message( replyMessage.author ) } == true), - ) { - // TODO Add jump to message - if (replyMessage == null) { - Toast.makeText(context, "lmao prankd", Toast.LENGTH_SHORT).show() - } + ) { messageId -> + jumpToMessage(messageId) } } } diff --git a/app/src/main/java/chat/revolt/composables/screens/chat/atoms/RegularMessage.kt b/app/src/main/java/chat/revolt/composables/screens/chat/atoms/RegularMessage.kt index 3ba2a7e4..234fb3a3 100644 --- a/app/src/main/java/chat/revolt/composables/screens/chat/atoms/RegularMessage.kt +++ b/app/src/main/java/chat/revolt/composables/screens/chat/atoms/RegularMessage.kt @@ -70,6 +70,8 @@ fun RegularMessage( showReactBottomSheet: () -> Unit, putTextAtCursorPosition: (String) -> Unit, replyToMessage: suspend (String) -> Unit, + jumpToMessage: (String) -> Unit = {}, + highlightedMessageId: String? = null, scope: CoroutineScope = rememberCoroutineScope() ) { val haptic = LocalHapticFeedback.current @@ -182,6 +184,8 @@ fun RegularMessage( }, fromWebhook = message.webhook != null, webhookName = message.webhook?.name, + jumpToMessage = jumpToMessage, + highlightedMessageId = highlightedMessageId, modifier = Modifier .offset( x = with(LocalDensity.current) { animOffsetX.toDp() } diff --git a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt index 08e3b1f7..e3c62b4d 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreen.kt @@ -677,6 +677,16 @@ fun ChannelScreen( modifier = Modifier.weight(1f), contentAlignment = Alignment.BottomCenter ) { + val jumpToMessage: (String) -> Unit = { messageId -> + viewModel.setHighlightedMessage(messageId) + val messageIndex = viewModel.findMessageIndex(messageId) + if (messageIndex >= 0) { + scope.launch { + lazyListState.animateScrollToItem(messageIndex) + } + } + } + LazyColumn( state = lazyListState, userScrollEnabled = !disableScroll, @@ -748,6 +758,8 @@ fun ChannelScreen( }, putTextAtCursorPosition = viewModel::putAtCursorPosition, replyToMessage = viewModel::addReplyTo, + jumpToMessage = jumpToMessage, + highlightedMessageId = viewModel.highlightedMessageId, scope = scope ) } diff --git a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt index 1402e278..dbb0e058 100644 --- a/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt +++ b/app/src/main/java/chat/revolt/screens/chat/views/channel/ChannelScreenViewModel.kt @@ -106,6 +106,8 @@ class ChannelScreenViewModel @Inject constructor( var ageGateUnlocked by mutableStateOf(null) var showGeoGate by mutableStateOf(false) + var highlightedMessageId by mutableStateOf(null) + init { viewModelScope.launch { keyboardHeight = kvStorage.getInt("keyboardHeight") ?: 900 // reasonable default for now @@ -311,6 +313,27 @@ class ChannelScreenViewModel @Inject constructor( keyboardHeight = height } + fun setHighlightedMessage(messageId: String) { + highlightedMessageId = messageId + + viewModelScope.launch { + delay(3000) // 3 second highlight + if (highlightedMessageId == messageId) { + highlightedMessageId = null + } + } + } + + fun findMessageIndex(messageId: String): Int { + return items.indexOfFirst { item -> + when (item) { + is ChannelScreenItem.RegularMessage -> item.message.id == messageId + is ChannelScreenItem.SystemMessage -> item.message.id == messageId + else -> false + } + } + } + private suspend fun applyMessageEdit() { try { editMessage( From bf3c72d1740743711ba8d30bae580fe57c053d72 Mon Sep 17 00:00:00 2001 From: Alex Yong Date: Mon, 11 Aug 2025 13:10:30 -0400 Subject: [PATCH 2/2] feature: Adding support for Github codespaces (#25) * Adding support for codespaces Signed-off-by: Alex Yong * feat: allowing this to be ran in automated contexts Signed-off-by: Alex Yong * Update devcontainer.json Signed-off-by: Alex Yong * feat: Adding in dummy google services json file, and updating documentation * fix: typo :) * Adjusting storage bucket per feedback in Revolt Dev server. --------- Signed-off-by: Alex Yong --- .devcontainer/devcontainer.json | 23 +++++++ app/google-services.json.example | 68 ++++++++++++++++++++ docs/src/content/docs/contributing/setup.mdx | 25 +++++-- scripts/download_deps.ts | 24 +++++-- 4 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 app/google-services.json.example diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..7b70f8bf --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,23 @@ +{ + "image": "mcr.microsoft.com/devcontainers/universal:2", + "features": { + "ghcr.io/devcontainers-community/features/deno:1": {}, + "ghcr.io/nordcominc/devcontainer-features/android-sdk:1": { + "extra_packages": "ndk;28.1.13356709" + }, + "ghcr.io/devcontainers/features/java:1": { + "version": "21", + "jdkDistro": "oracle" + } + }, + "postCreateCommand": "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", + "customizations": { + "vscode": { + "extensions": [ + "redhat.java", + "vscjava.vscode-java-pack", + "kotlin.kotlin" + ] + } + } + } diff --git a/app/google-services.json.example b/app/google-services.json.example new file mode 100644 index 00000000..997e884c --- /dev/null +++ b/app/google-services.json.example @@ -0,0 +1,68 @@ +{ + "project_info": { + "project_number": "123456789012", + "project_id": "revolt-android-dev", + "storage_bucket": "revolt-chat.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:123456789012:android:abcdef1234567890", + "android_client_info": { + "package_name": "chat.revolt" + } + }, + "oauth_client": [ + { + "client_id": "123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDummyKeyForDevelopmentOnly1234567890" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:123456789012:android:abcdef1234567891", + "android_client_info": { + "package_name": "chat.revolt.debug" + } + }, + "oauth_client": [ + { + "client_id": "123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDummyKeyForDevelopmentOnly1234567890" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/docs/src/content/docs/contributing/setup.mdx b/docs/src/content/docs/contributing/setup.mdx index e69c28a3..6bfb440e 100644 --- a/docs/src/content/docs/contributing/setup.mdx +++ b/docs/src/content/docs/contributing/setup.mdx @@ -6,10 +6,12 @@ template: doc This page contains the guidelines for setting up your development environment for Revolt on Android. These guidelines are important to ensure that your development environment is set up correctly and you can start contributing to the project. -If you want to compile the app for yourself, you can also follow these guidelines, however you may not need Android Studio and it may be possible to build the app using the command line, out of the scope of this guide. +If you want to compile the app yourself, you can follow these guidelines. You may not need Android Studio, as it’s possible to build the app from the command line. GitHub Codespaces automates most of the process for you if you want to go that route and this guide covers those steps, but won’t go into detail on other command-line methods. :::danger It may be tempting to skip some of these steps, but make sure you follow them to ensure that your development environment is set up correctly. + +Note: If you are doing this in Github Codespaces, steps 1-10 will be done for you. Also note that while small codespace instances can build this app, it's recommend to use 8-core or above for best results. ::: import { Tabs, TabItem, Steps } from "@astrojs/starlight/components" @@ -137,11 +139,26 @@ import { Tabs, TabItem, Steps } from "@astrojs/starlight/components" You can get these values from the Sentry dashboard. -10. Build the project. +10. Copy the `google-services.json.example` file within the `app` directory to `google-services.json`. + + Firebase services are integrated into the project, so we need a `google-services.json` file for the build to succeed. For development purposes, use the provided example file: + + ```sh + cp app/google-services.json.example app/google-services.json + ``` + + :::note + This is a mock configuration file for development purposes only. In a production environment, you would use a real Firebase project configuration. + ::: + +11. Build the project. You can build the project by clicking on the 'Run' button in Android Studio. If asked, build the `:app` module. -11. **You're all set!** You can now start contributing to Revolt on Android. + If building within Github Codespaces, you can build a fresh debug apk by running `./gradlew assembledebug --no-daemon` from the root of the project. + Upon completion, the apk will be within `app/build/outputs/apk/debug/` + +12. **You're all set!** You can now start contributing to Revolt on Android. - + \ No newline at end of file diff --git a/scripts/download_deps.ts b/scripts/download_deps.ts index 6476353b..c9d252ab 100644 --- a/scripts/download_deps.ts +++ b/scripts/download_deps.ts @@ -1,4 +1,14 @@ import { resolve } from "jsr:@std/path" +import { parseArgs } from "jsr:@std/cli/parse-args" + +const args = parseArgs(Deno.args, { + boolean: ["yes", "y", "auto-accept"], + alias: { + "y": "yes" + } +}) + +const autoAccept = args.yes || args["auto-accept"] const outputFolderParent = resolve(Deno.cwd(), "app", "src", "main", "assets") @@ -13,7 +23,7 @@ try { console.error( "Usage: " + "\x1b[35m" + // magenta - "deno run -A scripts/download_deps.ts" + + "deno run -A scripts/download_deps.ts [--yes|-y|--auto-accept]" + "\x1b[0m" + " from the " + "\x1b[1;31;4m" + // bold red underline @@ -299,10 +309,14 @@ for (const dep of deps) { console.log(`- ${dep.file} from ${dep.url}`) } -console.log("Will download the above files.") -if (!confirm("Continue?")) { - console.log("Aborted.") - Deno.exit(0) +if (!autoAccept) { + console.log("Will download the above files.") + if (!confirm("Continue?")) { + console.log("Aborted.") + Deno.exit(0) + } +} else { + console.log("Will download the above files. (auto-accepted)") } const fontsFolder = resolve(outputFolder, "fonts")