diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fdeee54a..46c69763 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -100,6 +100,7 @@ android { "FLAVOUR_ID", "\"${buildproperty("build.flavour_id", "RVX_BUILD_FLAVOUR_ID")}\"" ) + signingConfig = signingConfigs.getByName("debug") } debug { @@ -110,7 +111,7 @@ android { resValue( "string", "app_name", - buildproperty("build.debug.app_name", "RVX_DEBUG_APP_NAME")!! + buildproperty("build.debug.app_name", "RVX_DEBUG_APP_NAME") ?: "ZekoChat Debug" ) // buildConfigField( diff --git a/app/src/main/java/chat/zekochat/internals/Changelogs.kt b/app/src/main/java/chat/zekochat/internals/Changelogs.kt index 57810cb6..0f44058d 100644 --- a/app/src/main/java/chat/zekochat/internals/Changelogs.kt +++ b/app/src/main/java/chat/zekochat/internals/Changelogs.kt @@ -59,7 +59,9 @@ class Changelogs(val context: Context, val kvStorage: KVStorage? = null) { cachedIndex = PeptideJson.decodeFromString(ChangelogIndex.serializer(), response.bodyAsText()) return cachedIndex as ChangelogIndex - } catch (e: Error) { + } catch (_: Error) { + return ChangelogIndex() + }catch (_: Exception) { return ChangelogIndex() } } @@ -95,7 +97,8 @@ class Changelogs(val context: Context, val kvStorage: KVStorage? = null) { } suspend fun getLatestChangelogCode(): String { - return getLatestChangelog().version.code.toString() +// return getLatestChangelog().version.code.toString() + return "" } suspend fun hasSeenCurrent(): Boolean { @@ -105,7 +108,7 @@ class Changelogs(val context: Context, val kvStorage: KVStorage? = null) { ) } - val latest = getLatestChangelog().version.code +// val latest = getLatestChangelog().version.code val lastRead = kvStorage.get("latestChangelogRead") if (lastRead == null) { @@ -113,7 +116,8 @@ class Changelogs(val context: Context, val kvStorage: KVStorage? = null) { } // If the last read changelog is >= the latest, it has been read - return lastRead.toLong() >= latest +// return lastRead.toLong() >= latest + return true } suspend fun markAsSeen() { @@ -124,7 +128,7 @@ class Changelogs(val context: Context, val kvStorage: KVStorage? = null) { } val index = fetchChangelogIndex() - val latest = index.changelogs.maxByOrNull { it.version.code }!!.version.code.toString() - kvStorage.set("latestChangelogRead", latest) +// val latest = index.changelogs.maxByOrNull { it.version.code }!!.version.code.toString() +// kvStorage.set("latestChangelogRead", latest) } } diff --git a/app/src/main/java/chat/zekochat/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/zekochat/screens/chat/ChatRouterScreen.kt index f110102a..85611ead 100644 --- a/app/src/main/java/chat/zekochat/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/zekochat/screens/chat/ChatRouterScreen.kt @@ -187,8 +187,8 @@ class ChatRouterViewModel @Inject constructor( latestChangelogRead = changelogs.hasSeenCurrent() latestChangelog = changelogs.getLatestChangelogCode() - latestChangelogBody = - changelogs.fetchChangelogByVersionCode(latestChangelog.toLong()).rendered +// latestChangelogBody = +// changelogs.fetchChangelogByVersionCode(latestChangelog.toLong()).rendered if (!latestChangelogRead) { changelogs.markAsSeen() } diff --git a/app/src/main/java/chat/zekochat/screens/settings/ChangelogsScreen.kt b/app/src/main/java/chat/zekochat/screens/settings/ChangelogsScreen.kt index 96d05238..549012fc 100644 --- a/app/src/main/java/chat/zekochat/screens/settings/ChangelogsScreen.kt +++ b/app/src/main/java/chat/zekochat/screens/settings/ChangelogsScreen.kt @@ -60,12 +60,12 @@ class ChangelogsSettingsScreenViewModel @Inject constructor( var renderedChangelog by mutableStateOf("") suspend fun requestChangelog(version: String) { - viewModelScope.launch { - renderedChangelog = Changelogs( - context, - kvStorage - ).fetchChangelogByVersionCode(version.toLong()).rendered - } +// viewModelScope.launch { +// renderedChangelog = Changelogs( +// context, +// kvStorage +// ).fetchChangelogByVersionCode(version.toLong()).rendered +// } } suspend fun populate() { diff --git a/app/src/main/java/chat/zekochat/sheets/AddMemberToGroupSheet.kt b/app/src/main/java/chat/zekochat/sheets/AddMemberToGroupSheet.kt new file mode 100644 index 00000000..c22db64e --- /dev/null +++ b/app/src/main/java/chat/zekochat/sheets/AddMemberToGroupSheet.kt @@ -0,0 +1,214 @@ +package chat.zekochat.sheets + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.compose.viewModel +import chat.zekochat.api.PeptideAPI +import chat.zekochat.api.PeptideHttp +import chat.zekochat.api.PeptideJson +import chat.zekochat.api.api +import chat.zekochat.api.internals.FriendRequests +import chat.zekochat.api.routes.channel.addMember +import chat.zekochat.api.routes.channel.removeMember +import chat.zekochat.api.schemas.User +import chat.zekochat.composables.chat.MemberListItem +import chat.zekochat.screens.create.MAX_ADDABLE_PEOPLE_IN_GROUP +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.serialization.builtins.ListSerializer + +class AddMemberToGroupSheetViewModel : ViewModel() { + var groupMembers = mutableStateListOf() + var friendSearchQuery by mutableStateOf("") + var loadingUser by mutableStateOf("") + var friendsFilteredBySearch = mutableStateListOf() + var error by mutableStateOf(null) + + suspend fun fetchGroupParticipants(channelId: String) { + val response = PeptideHttp.get("/channels/$channelId/members".api()) + .bodyAsText() + + val users = PeptideJson.decodeFromString( + ListSerializer(User.serializer()), + response + ).mapNotNull { it.id } + + groupMembers.clear() + groupMembers.addAll(users) + + } + + fun filterFriends() { + friendsFilteredBySearch.clear() + friendsFilteredBySearch.addAll(FriendRequests.getFriends().filter { + if (friendSearchQuery.isBlank()) { + return@filter true + } + + if (it.displayName == null || it.username == null) { + return@filter false + } + + it.displayName.contains(friendSearchQuery, ignoreCase = true) || + it.username.contains(friendSearchQuery, ignoreCase = true) + }.map { it.id!! }) + } + + fun addMemberToGroup(channelId: String, popBackStack: () -> Unit) { + if (groupMembers.size > MAX_ADDABLE_PEOPLE_IN_GROUP) { + error = "Too many members, maximum is $MAX_ADDABLE_PEOPLE_IN_GROUP" + return + } + + try { + error = null + viewModelScope.launch { + groupMembers.map { + CoroutineScope(Dispatchers.IO).launch { + addMember(channelId, it) + } + } + popBackStack() + } + } catch (e: Exception) { + error = e.message + } + } + + fun addMemberToGroup(channelId: String, userId: String) { + + if(loadingUser.isNotEmpty()) return + + viewModelScope.launch { + try { + loadingUser = userId + error = null + + addMember(channelId, userId) + groupMembers.add(userId) + + loadingUser = "" + } catch (e: Exception) { + loadingUser = "" + error = e.message + } catch (e: Error) { + loadingUser = "" + groupMembers.add(userId) + } + } + } + + fun removeMemberFromGroup(channelId: String, userId: String) { + + if(loadingUser.isNotEmpty()) return + + viewModelScope.launch { + try { + loadingUser = userId + error = null + + removeMember(channelId, userId) + groupMembers.remove(userId) + + loadingUser = "" + } catch (e: Exception) { + loadingUser = "" + error = e.message + } catch (e: Error) { + loadingUser = "" + groupMembers.remove(userId) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AddMemberToGroupSheet( + channelId: String, + viewModel: AddMemberToGroupSheetViewModel = viewModel(), + onHideSheet: suspend () -> Unit +) +{ + + LaunchedEffect(Unit) { + viewModel.filterFriends() + viewModel.fetchGroupParticipants(channelId); + } + + Column( + Modifier + .imePadding() + ) + { + AnimatedVisibility(visible = viewModel.error?.isNotBlank() ?: false) { + Text( + text = viewModel.error ?: "", + color = MaterialTheme.colorScheme.error, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) + } + + LazyColumn(contentPadding = PaddingValues(bottom = 78.0.dp)) { + items(viewModel.friendsFilteredBySearch.size) { index -> + val friend = PeptideAPI.userCache[viewModel.friendsFilteredBySearch[index]] + ?: return@items + val isMember = viewModel.groupMembers.contains(friend.id) + val isLoading = viewModel.loadingUser == friend.id + + MemberListItem( + member = null, + user = friend, + serverId = null, + userId = friend.id!!, + modifier = Modifier.clickable { + if (isMember) { + viewModel.removeMemberFromGroup(channelId, friend.id) + } else { + viewModel.addMemberToGroup(channelId, friend.id) + } + }, + trailingContent = { + if(isLoading){ + CircularProgressIndicator(Modifier.size(24.dp)) + }else{ + + Checkbox( + checked = isMember, + onCheckedChange = null, + enabled = (isMember.not() && viewModel.groupMembers.size >= MAX_ADDABLE_PEOPLE_IN_GROUP).not() + ) + } + } + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/chat/zekochat/sheets/ChannelInfoSheet.kt b/app/src/main/java/chat/zekochat/sheets/ChannelInfoSheet.kt index 8d63e660..7e826837 100644 --- a/app/src/main/java/chat/zekochat/sheets/ChannelInfoSheet.kt +++ b/app/src/main/java/chat/zekochat/sheets/ChannelInfoSheet.kt @@ -46,6 +46,7 @@ import kotlinx.coroutines.launch fun ChannelInfoSheet(channelId: String, onHideSheet: suspend () -> Unit) { val channel = PeptideAPI.channelCache[channelId] var memberListSheetShown by remember { mutableStateOf(false) } + var addMemberSheetShown by remember { mutableStateOf(false) } var inviteDialogShown by remember { mutableStateOf(false) } val scope = rememberCoroutineScope() val permissions by rememberChannelPermissions(channelId) @@ -66,6 +67,24 @@ fun ChannelInfoSheet(channelId: String, onHideSheet: suspend () -> Unit) { } } + if (addMemberSheetShown) { + val addMemberSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + + ModalBottomSheet( + sheetState = addMemberSheetState, + onDismissRequest = { + addMemberSheetShown = false + } + ) { + AddMemberToGroupSheet( + channelId = channelId, + onHideSheet = { + addMemberSheetShown = false + } + ) + } + } + if (inviteDialogShown) { Dialog( onDismissRequest = { @@ -176,7 +195,9 @@ fun ChannelInfoSheet(channelId: String, onHideSheet: suspend () -> Unit) { contentDescription = null ) }, - onClick = {} + onClick = { + addMemberSheetShown = true + } ) } @@ -184,20 +205,22 @@ fun ChannelInfoSheet(channelId: String, onHideSheet: suspend () -> Unit) { } } - SheetButton( - headlineContent = { - Text( - text = stringResource(id = R.string.channel_info_sheet_options_notifications_manage), - ) - }, - leadingContent = { - Icon( - painter = painterResource(R.drawable.icn_notification_settings_24dp), - contentDescription = null - ) - }, - onClick = {} - ) + if(channel.channelType != ChannelType.Group){ + SheetButton( + headlineContent = { + Text( + text = stringResource(id = R.string.channel_info_sheet_options_notifications_manage), + ) + }, + leadingContent = { + Icon( + painter = painterResource(R.drawable.icn_notification_settings_24dp), + contentDescription = null + ) + }, + onClick = {} + ) + } if ( (permissions has PermissionBit.ManageChannel || permissions has PermissionBit.ManageRole) diff --git a/app/src/main/res/drawable/splash_screen_icon_square.xml b/app/src/main/res/drawable/splash_screen_icon_square.xml new file mode 100644 index 00000000..7b720f7e --- /dev/null +++ b/app/src/main/res/drawable/splash_screen_icon_square.xml @@ -0,0 +1,7 @@ + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index a4ecf258..64ee7504 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-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index a33d49ec..666f311f 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-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 7dcb22cf..4fae0109 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-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 2d842b14..481837f3 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-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 6d55bf2f..b539eaad 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/values-v31/themes.xml b/app/src/main/res/values-v31/themes.xml index 97632d5b..5180ed83 100644 --- a/app/src/main/res/values-v31/themes.xml +++ b/app/src/main/res/values-v31/themes.xml @@ -5,6 +5,6 @@ @android:color/transparent @android:color/transparent @color/background - @drawable/splash_screen_icon + @drawable/splash_screen_icon_square \ No newline at end of file diff --git a/app/src/main/res/values-v33/themes.xml b/app/src/main/res/values-v33/themes.xml index cfebe11d..a5e0f1d9 100644 --- a/app/src/main/res/values-v33/themes.xml +++ b/app/src/main/res/values-v33/themes.xml @@ -2,9 +2,8 @@ \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index a0d6c1bf..e5823abb 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -11,7 +11,7 @@