Fix UI bugs

Signed-off-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
2025-09-09 20:46:46 +03:00
Unverified
parent 6d85dc767f
commit 85a050c0a0
7 changed files with 151 additions and 57 deletions
+3
View File
@@ -17,3 +17,6 @@ build
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
keys
releases
+24 -2
View File
@@ -1,4 +1,7 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.io.FileInputStream
import java.util.Properties
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
@@ -27,10 +30,29 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
signingConfigs {
create("release") {
val keystoreProperties = Properties().apply {
load(FileInputStream(rootProject.file("keys/keystore.properties")))
}
storeFile = rootProject.file("keys/release.jks")
keyAlias = "release"
storePassword = keystoreProperties["storePassword"].toString()
keyPassword = keystoreProperties["keyPassword"].toString()
enableV3Signing = true
}
}
buildTypes { buildTypes {
release { release {
isMinifyEnabled = false isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") isShrinkResources = true
signingConfig = signingConfigs.getByName("release")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
} }
} }
+3 -6
View File
@@ -9,19 +9,16 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:theme="@style/Theme.FromChat">
android:theme="@style/Theme.FromChat"
android:usesCleartextTraffic="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:theme="@style/Theme.FromChat"> android:theme="@style/Theme.FromChat"
android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
</manifest> </manifest>
@@ -1,5 +1,6 @@
package ru.fromchat.api package ru.fromchat.api
import android.util.Log
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
@@ -54,7 +55,7 @@ object ApiClient {
http http
.post("https://fromchat.ru/api/login") { .post("https://fromchat.ru/api/login") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(request) setBody(request.also { Log.d("ApiClient", "Login request: $it") })
} }
.failOnError() .failOnError()
.body<LoginResponse>() .body<LoginResponse>()
@@ -1,11 +1,14 @@
package ru.fromchat.ui.auth package ru.fromchat.ui.auth
import android.util.Log
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Login import androidx.compose.material.icons.automirrored.filled.Login
import androidx.compose.material3.Button import androidx.compose.material3.Button
@@ -34,7 +37,7 @@ fun LoginScreen(
onLoginSuccess: () -> Unit, onLoginSuccess: () -> Unit,
onNavigateToRegister: () -> Unit onNavigateToRegister: () -> Unit
) { ) {
Scaffold { innerPadding -> Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
var username by remember { mutableStateOf("") } var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf("") }
var alert by remember { mutableStateOf<String?>(null) } var alert by remember { mutableStateOf<String?>(null) }
@@ -44,13 +47,14 @@ fun LoginScreen(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(innerPadding) .padding(innerPadding),
.padding(16.dp),
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Column( Column(
modifier = Modifier.padding(16.dp), modifier = Modifier
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
@@ -89,13 +93,12 @@ fun LoginScreen(
scope.launch { scope.launch {
apiRequest( apiRequest(
onError = { message, e -> onError = { message, _ ->
Log.e("LoginScreen", "An error ocurred while logging in:", e)
alert = message alert = message
}, },
onSuccess = { onLoginSuccess() } onSuccess = { onLoginSuccess() }
) { ) {
ApiClient.login(LoginRequest(username, password)) ApiClient.login(LoginRequest(username.trim(), password.trim()))
} }
} }
} }
@@ -3,11 +3,19 @@ package ru.fromchat.ui.auth
import android.util.Log import android.util.Log
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.PersonAdd import androidx.compose.material.icons.filled.PersonAdd
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@@ -26,30 +34,33 @@ import kotlinx.coroutines.launch
import ru.fromchat.api.ApiClient import ru.fromchat.api.ApiClient
import ru.fromchat.api.RegisterRequest import ru.fromchat.api.RegisterRequest
import ru.fromchat.api.apiRequest import ru.fromchat.api.apiRequest
import ru.fromchat.ui.LocalNavController
import ru.fromchat.ui.RowHeader import ru.fromchat.ui.RowHeader
@Composable @Composable
fun RegisterScreen( fun RegisterScreen(
onRegistered: () -> Unit onRegistered: () -> Unit
) { ) {
Scaffold { innerPadding -> Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
var username by remember { mutableStateOf("") } var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf("") }
var confirmPassword by remember { mutableStateOf("") } var confirmPassword by remember { mutableStateOf("") }
var alert by remember { mutableStateOf<String?>(null) } var alert by remember { mutableStateOf<String?>(null) }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val navController = LocalNavController.current
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(innerPadding) .padding(innerPadding),
.padding(16.dp),
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Column( Column(
modifier = Modifier.padding(16.dp), modifier = Modifier
.padding(16.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
@@ -86,47 +97,58 @@ fun RegisterScreen(
visualTransformation = PasswordVisualTransformation() visualTransformation = PasswordVisualTransformation()
) )
Button( FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
onClick = { IconButton(
// Checks onClick = { navController.navigateUp() }
if (username.isBlank() || password.isBlank() || confirmPassword.isBlank()) { ) {
alert = "Пожалуйста, заполните все поля" Icon(
return@Button imageVector = Icons.AutoMirrored.Filled.ArrowBack,
} contentDescription = "Back"
if (password != confirmPassword) { )
alert = "Пароли не совпадают" }
return@Button
}
if (username.length !in 3..20) {
alert = "Имя пользователя должно быть от 3 до 20 символов"
return@Button
}
if (password.length !in 5..50) {
alert = "Пароль должен быть от 5 до 50 символов"
return@Button
}
// Send the register request Button(
scope.launch { onClick = {
apiRequest( // Checks
onError = { message, e -> if (username.isBlank() || password.isBlank() || confirmPassword.isBlank()) {
Log.d("RegisterScreen", "Error while registering:", e) alert = "Пожалуйста, заполните все поля"
alert = message return@Button
}, }
onSuccess = { onRegistered() } if (password != confirmPassword) {
) { alert = "Пароли не совпадают"
ApiClient.register( return@Button
RegisterRequest( }
username, if (username.length !in 3..20) {
password, alert = "Имя пользователя должно быть от 3 до 20 символов"
confirmPassword return@Button
}
if (password.length !in 5..50) {
alert = "Пароль должен быть от 5 до 50 символов"
return@Button
}
// Send the register request
scope.launch {
apiRequest(
onError = { message, e ->
Log.d("RegisterScreen", "Error while registering:", e)
alert = message
},
onSuccess = { onRegistered() }
) {
ApiClient.register(
RegisterRequest(
username,
password,
confirmPassword
)
) )
) }
} }
} }
) {
Text("Зарегистрироваться")
} }
) {
Text("Зарегистрироваться")
} }
} }
} }
Executable
+46
View File
@@ -0,0 +1,46 @@
#!/bin/bash
# 1. Setup
set -e
cd "$(dirname "$0")"
# 2. Variables
TAG=$(git describe --tags --abbrev=0)
# shellcheck disable=SC2207
TAGS=($(git --no-pager tag --sort -v:refname | xargs))
PREVTAG=${TAGS[1]}
ASSET_PATH=./app/build/outputs/apk/release/app-release.apk
DISPLAY_NAME=FromChat.$TAG.apk
# 3. Build release variant
./gradlew assembleRelease
# 4. Move locally
mkdir -p releases
mv "$ASSET_PATH" "releases/$DISPLAY_NAME"
ASSET_PATH=./releases/$DISPLAY_NAME
# 6. Release to GitHub
prerelease_opt=$([[ "$TAG" != v*-pre* ]] || echo "--prerelease")
new_release() {
gh release create \
"$TAG" \
--generate-notes \
--notes-start-tag "$PREVTAG" \
"$prerelease_opt" \
"$ASSET_PATH"
}
edit_existing() {
gh release edit \
"$TAG" \
"$prerelease_opt"
gh release upload \
"$TAG" \
--clobber \
"$ASSET_PATH"
}
git push --tags
new_release || edit_existing