+
+
+
## Description
+NOTE: This is a forked version of the Android app for the [Revolt](https://revolt.chat) chat platform. I made for some QOL changes that aren't present in the current version at the time of writing.
+
+Feel free to use this for whatever, but note that this is NOT the official Revolt android app. :)
+
+You can download the latest APK [here](https://github.com/alexjyong/android/releases/download/1.3.6ba-forked/app-release.apk).
+
+Or the latest debug APK [here](https://github.com/alexjyong/android/releases/download/1.3.6ba-forked/app-debug.apk).
+
+## Features Added
+
+
+
+ Emojis next to channel names
+
+
+
+ Jump to replied message
+
+
+
+
+
+ Server context menu on long press
+
+
+
+
+
+
+
The codebase includes the app itself, as well as an internal library for interacting with the Revolt
API. The app is written in Kotlin, and wholly
uses [Jetpack Compose](https://developer.android.com/jetpack/compose).
@@ -43,6 +77,51 @@ uses [Jetpack Compose](https://developer.android.com/jetpack/compose).
- [Revolt Server](https://app.revolt.chat/invite/Testers)
- [General Revolt Contribution Guide](https://developers.revolt.chat/contrib.html)
+## Quick Build
+
+If you don't want to download the apks in the releases section and rather build yourself, follow these steps:
+
+Fire up a Github Codespaces instance at this link [here](https://github.com/codespaces/new?hide_repo_select=true&ref=combined-pr&repo=1020437871&skip_quickstart=true&machine=premiumLinux&devcontainer_path=.devcontainer%2Fdevcontainer.json&geo=UsEast)
+
+The URL should have it selected for you automatically, but be sure to use this branch for your instance!
+
+Note that this url will have an 8-core instance selected by default. Feel free to use a smaller instance, but I've ran into build errors with that.
+At the time of writing, Github offers a number of free hours for personal accounts, but note that this bigger instance will use more of your free hours than a smaller one. **For just building the apk and downloading it to whatever device, this should be fine though**. Be sure to delete the instance when you are done. **It won't cost you $$ if you don't have payment set up with Github or have budget limits.** [See the billing page for more details.](https://docs.github.com/en/billing/concepts/product-billing/github-codespaces#pricing-for-paid-usage)
+
+After the instance fires up run
+
+```sh
+./gradlew assembledebug --no-daemon
+```
+To generate a debug version of the application.
+
+If you wanted a signed copy that isn't in debug mode, set up a release-key.keystore file, update revoltbuild.properties to have your passwords and run:
+
+```sh
+./gradlew assembleRelease -x app:uploadSentryProguardMappingsRelease
+```
+
+It will be located in `app/build/outputs/apk/debug/` under the name `app-debug.apk`
+
+Download it to your system by right clicking on the file like so:
+
+
+
+Send it to your phone, and install and run it!
+
+Alternatively, you can send it to your phone right from Codespaces using magic wormhole (installed on this codespace instance by default)
+
+`wormhole send app/build/outputs/apk/debug/app-debug.apk`
+
+It will give you a code that you can punch into your phone.
+
+Either use the [Wormhole William app from the Google Playstore](https://play.google.com/store/apps/details?id=io.sanford.wormhole_william&hl=en_US)
+
+Or get the apk [directly from here](https://github.com/psanford/wormhole-william-mobile/releases/tag/v1.0.13)
+
+Or install [Termux](https://termux.dev/en/), then install `wormhole-rs` on Termux with `pkg install magic-wormhole-rs` and fetch the apk with
+`wormhole-rs receive YOUR_CODE_HERE`
+
## Quick Start
Open the project in Android Studio. You can then run the app on an emulator or a physical device by
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a807dbcf..c5daa312 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -76,11 +76,11 @@ android {
compileSdk = 36
defaultConfig {
- applicationId = "chat.revolt"
+ applicationId = "chat.revolt.forked"
minSdk = 24
targetSdk = 36
versionCode = Integer.parseInt("001_003_206".replace("_", ""), 10)
- versionName = "1.3.6b"
+ versionName = "1.3.6ba-forked"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -95,6 +95,15 @@ android {
}
}
+ signingConfigs {
+ create("release") {
+ storeFile = file(buildproperty("signing.store_file", "SIGNING_STORE_FILE") ?: "release-key.keystore")
+ storePassword = buildproperty("signing.store_password", "SIGNING_STORE_PASSWORD")
+ keyAlias = buildproperty("signing.key_alias", "SIGNING_KEY_ALIAS")
+ keyPassword = buildproperty("signing.key_password", "SIGNING_KEY_PASSWORD")
+ }
+ }
+
buildTypes {
release {
isMinifyEnabled = true
@@ -113,6 +122,7 @@ android {
"FLAVOUR_ID",
"\"${buildproperty("build.flavour_id", "RVX_BUILD_FLAVOUR_ID")}\""
)
+ signingConfig = signingConfigs.getByName("release")
}
debug {
diff --git a/app/google-services.json.example b/app/google-services.json.example
index 997e884c..d512b007 100644
--- a/app/google-services.json.example
+++ b/app/google-services.json.example
@@ -9,7 +9,7 @@
"client_info": {
"mobilesdk_app_id": "1:123456789012:android:abcdef1234567890",
"android_client_info": {
- "package_name": "chat.revolt"
+ "package_name": "chat.revolt.forked"
}
},
"oauth_client": [
@@ -38,7 +38,7 @@
"client_info": {
"mobilesdk_app_id": "1:123456789012:android:abcdef1234567891",
"android_client_info": {
- "package_name": "chat.revolt.debug"
+ "package_name": "chat.revolt.forked.debug"
}
},
"oauth_client": [
@@ -65,4 +65,4 @@
}
],
"configuration_version": "1"
-}
\ No newline at end of file
+}
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/ChannelHeader.kt b/app/src/main/java/chat/revolt/composables/screens/chat/ChannelHeader.kt
index a841cd63..fad9385e 100644
--- a/app/src/main/java/chat/revolt/composables/screens/chat/ChannelHeader.kt
+++ b/app/src/main/java/chat/revolt/composables/screens/chat/ChannelHeader.kt
@@ -64,10 +64,20 @@ fun ChannelHeader(
}
channel.channelType?.let {
- ChannelIcon(
- channelType = it,
- modifier = Modifier.alpha(0.6f)
- )
+ when (it) {
+ ChannelType.TextChannel, ChannelType.VoiceChannel -> {
+ ChannelIcon(
+ channel = channel,
+ modifier = Modifier.alpha(0.6f)
+ )
+ }
+ else -> {
+ ChannelIcon(
+ channelType = it,
+ modifier = Modifier.alpha(0.6f)
+ )
+ }
+ }
}
Spacer(modifier = Modifier.width(8.dp))
diff --git a/app/src/main/java/chat/revolt/composables/screens/chat/ChannelIcon.kt b/app/src/main/java/chat/revolt/composables/screens/chat/ChannelIcon.kt
index 59e00931..105cdacf 100644
--- a/app/src/main/java/chat/revolt/composables/screens/chat/ChannelIcon.kt
+++ b/app/src/main/java/chat/revolt/composables/screens/chat/ChannelIcon.kt
@@ -1,15 +1,21 @@
package chat.revolt.composables.screens.chat
+import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
+import androidx.compose.ui.unit.dp
import chat.revolt.R
+import chat.revolt.api.REVOLT_FILES
+import chat.revolt.api.schemas.Channel
import chat.revolt.api.schemas.ChannelType
+import chat.revolt.composables.generic.RemoteImage
@Composable
fun ChannelIcon(channelType: ChannelType, modifier: Modifier = Modifier) {
@@ -56,6 +62,29 @@ fun ChannelIcon(channelType: ChannelType, modifier: Modifier = Modifier) {
}
}
+@Composable
+fun ChannelIcon(
+ channel: Channel,
+ modifier: Modifier = Modifier,
+ size: androidx.compose.ui.unit.Dp = 24.dp
+) {
+ val channelType = channel.channelType ?: ChannelType.TextChannel
+
+ if (channel.icon?.id != null) {
+ RemoteImage(
+ url = "$REVOLT_FILES/icons/${channel.icon.id}",
+ description = channel.name ?: stringResource(R.string.unknown),
+ contentScale = ContentScale.Crop,
+ height = size.value.toInt(),
+ width = size.value.toInt(),
+ allowAnimation = false,
+ modifier = modifier.size(size)
+ )
+ } else {
+ ChannelIcon(channelType = channelType, modifier = modifier)
+ }
+}
+
class ChannelTypeProvider : PreviewParameterProvider {
override val values: Sequence
get() = sequenceOf(
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/composables/screens/chat/drawer/ChannelSideDrawer.kt b/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt
index c78fb7c4..202f71c9 100644
--- a/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt
+++ b/app/src/main/java/chat/revolt/composables/screens/chat/drawer/ChannelSideDrawer.kt
@@ -98,7 +98,9 @@ import chat.revolt.composables.generic.presenceFromStatus
import chat.revolt.composables.screens.chat.ChannelIcon
import chat.revolt.screens.chat.ChatRouterDestination
import chat.revolt.screens.chat.LocalIsConnected
+import chat.revolt.screens.chat.dialogs.safety.ReportServerDialog
import chat.revolt.sheets.ChannelContextSheet
+import chat.revolt.sheets.ServerContextSheet
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@@ -183,6 +185,10 @@ fun ChannelSideDrawer(
}.sortedBy { it.id }))
var channelContextSheetTarget by remember { mutableStateOf(null) }
+ var serverContextSheetTarget by remember { mutableStateOf(null) }
+
+ var showReportServer by remember { mutableStateOf(false) }
+ var reportServerTarget by remember { mutableStateOf("") }
if (channelContextSheetTarget != null) {
val channelContextSheetState = rememberModalBottomSheetState()
@@ -203,6 +209,29 @@ fun ChannelSideDrawer(
}
}
+ if (serverContextSheetTarget != null) {
+ val serverContextSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+
+ ModalBottomSheet(
+ sheetState = serverContextSheetState,
+ onDismissRequest = {
+ serverContextSheetTarget = null
+ }
+ ) {
+ ServerContextSheet(
+ serverId = serverContextSheetTarget!!,
+ onReportServer = {
+ reportServerTarget = serverContextSheetTarget!!
+ showReportServer = true
+ },
+ onHideSheet = {
+ serverContextSheetState.hide()
+ serverContextSheetTarget = null
+ }
+ )
+ }
+ }
+
val scope = rememberCoroutineScope()
Row(modifier.fillMaxSize()) {
@@ -355,12 +384,19 @@ fun ChannelSideDrawer(
Modifier
.padding(8.dp)
.clip(CircleShape)
- .clickable {
- serverInList.id?.let { srvId -> navigateToServer(srvId) }
- scope.launch {
- drawerState?.close()
+ .combinedClickable(
+ onClick = {
+ serverInList.id?.let { srvId -> navigateToServer(srvId) }
+ scope.launch {
+ drawerState?.close()
+ }
+ },
+ onLongClick = {
+ serverInList.id?.let { srvId ->
+ serverContextSheetTarget = srvId
+ }
}
- }) {
+ )) {
val icon = serverInList.icon?.id?.let { iconId ->
"$REVOLT_FILES/icons/$iconId"
}
@@ -594,6 +630,15 @@ fun ChannelSideDrawer(
}
}
}
+
+ if (showReportServer) {
+ ReportServerDialog(
+ onDismiss = {
+ showReportServer = false
+ },
+ serverId = reportServerTarget
+ )
+ }
}
@Composable
@@ -924,7 +969,7 @@ fun ChannelItem(
)
}
- else -> ChannelIcon(iconType.type)
+ else -> ChannelIcon(channel = channel, size = 24.dp)
}
}
@@ -1085,4 +1130,4 @@ fun DMOrGroupItem(
}
}
}
-}
\ No newline at end of file
+}
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..75e55389 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
@@ -551,6 +551,14 @@ fun ChannelScreen(
)
}
+ ChannelType.TextChannel, ChannelType.VoiceChannel -> {
+ ChannelIcon(
+ channel = it,
+ size = 24.dp,
+ modifier = Modifier.alpha(0.8f)
+ )
+ }
+
else -> {
ChannelIcon(
channelType = it.channelType ?: ChannelType.TextChannel,
@@ -677,6 +685,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 +766,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(
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
index 6a29b828..ee6a72ec 100644
--- a/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -6,4 +6,11 @@
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_monochrome.xml b/app/src/main/res/drawable/ic_launcher_monochrome.xml
index 6a29b828..1fb94380 100644
--- a/app/src/main/res/drawable/ic_launcher_monochrome.xml
+++ b/app/src/main/res/drawable/ic_launcher_monochrome.xml
@@ -5,5 +5,12 @@
android:viewportHeight="108">
+ android:fillColor="#000000"/>
+
+
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
index fe170f68..0fa2720a 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
index 7b2c74f4..0fa2720a 100644
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
index ee98a72a..e6079fcb 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
index 62f042b2..e6079fcb 100644
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index b9044ea5..4f005df3 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
index 4223efd3..4f005df3 100644
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 4bc233c3..4d2b4569 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
index a7d0a9cc..4d2b4569 100644
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index b67bea85..ffa2d260 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
index c4c69dc7..ffa2d260 100644
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4aff6a52..78d7e200 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,5 +1,5 @@
- Revolt
+ Revolt Forked← BackNext →
@@ -476,6 +476,16 @@
An error occurred while creating your server. Please try again later.Create
+ Add Server
+ Join by invite code or link
+ Invite code or link
+ Enter a link like rvlt.gg/Testers or an invite code like Testers
+ Invite code or link
+ Join
+ Create a new server
+ Create a new server
+ This feature is currently under construction.
+
Discover RevoltReport
diff --git a/generate_icon_pngs.sh b/generate_icon_pngs.sh
new file mode 100755
index 00000000..5c048bc3
--- /dev/null
+++ b/generate_icon_pngs.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+# script to generate PNG icons from vector drawable
+# this converts the vector drawable to PNGs for all required densities
+
+VECTOR_FILE="app/src/main/res/drawable/ic_launcher_foreground.xml"
+OUTPUT_BASE="app/src/main/res"
+
+declare -A SIZES=(
+ ["mdpi"]=48
+ ["hdpi"]=72
+ ["xhdpi"]=96
+ ["xxhdpi"]=144
+ ["xxxhdpi"]=192
+)
+
+echo "Generating PNG icons from vector drawable..."
+
+TEMP_SVG="/tmp/temp_icon.svg"
+
+cat > "$TEMP_SVG" << 'EOF'
+
+EOF
+
+echo "Created temporary SVG file"
+
+for density in "${!SIZES[@]}"; do
+ size=${SIZES[$density]}
+ output_dir="$OUTPUT_BASE/mipmap-$density"
+
+ echo "Generating ${density} (${size}x${size})..."
+
+ rsvg-convert -w $size -h $size "$TEMP_SVG" -o "$output_dir/ic_launcher.png"
+
+ # Generate round icon (same image, Android will clip it)
+ rsvg-convert -w $size -h $size "$TEMP_SVG" -o "$output_dir/ic_launcher_round.png"
+done
+
+rm "$TEMP_SVG"
+
+echo "Done! Generated PNG icons for all densities."
+echo "Icons generated in: $OUTPUT_BASE/mipmap-*/"
diff --git a/revoltbuild.properties.example b/revoltbuild.properties.example
index ea6a101c..648c0c28 100644
--- a/revoltbuild.properties.example
+++ b/revoltbuild.properties.example
@@ -1,4 +1,10 @@
sentry.dsn=
sentry.upload_mappings=true
-build.debug.app_name=
-build.flavour_id=ZZUU
\ No newline at end of file
+build.debug.app_name=Revolt Forked
+build.flavour_id=ZZUU
+
+# Release signing configuration
+signing.store_file=release-key.keystore
+signing.store_password=
+signing.key_alias=revolt-release
+signing.key_password=