diff --git a/.gitignore b/.gitignore index 1a38334..9502fb1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ build .externalNativeBuild .cxx local.properties + +keys +releases \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 71bb36e..6353b28 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,7 @@ + import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import java.io.FileInputStream +import java.util.Properties plugins { alias(libs.plugins.android.application) @@ -27,10 +30,29 @@ android { 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 { release { - isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + isMinifyEnabled = true + isShrinkResources = true + signingConfig = signingConfigs.getByName("release") + + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5adafd2..a38d866 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,19 +9,16 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" - android:supportsRtl="true" - android:theme="@style/Theme.FromChat" - android:usesCleartextTraffic="true"> + android:theme="@style/Theme.FromChat"> + android:theme="@style/Theme.FromChat" + android:windowSoftInputMode="adjustResize"> - - \ No newline at end of file diff --git a/app/src/main/java/ru/fromchat/api/ApiClient.kt b/app/src/main/java/ru/fromchat/api/ApiClient.kt index ebd215a..41c0afa 100644 --- a/app/src/main/java/ru/fromchat/api/ApiClient.kt +++ b/app/src/main/java/ru/fromchat/api/ApiClient.kt @@ -1,5 +1,6 @@ package ru.fromchat.api +import android.util.Log import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.plugins.contentnegotiation.ContentNegotiation @@ -54,7 +55,7 @@ object ApiClient { http .post("https://fromchat.ru/api/login") { contentType(ContentType.Application.Json) - setBody(request) + setBody(request.also { Log.d("ApiClient", "Login request: $it") }) } .failOnError() .body() diff --git a/app/src/main/java/ru/fromchat/ui/auth/LoginScreen.kt b/app/src/main/java/ru/fromchat/ui/auth/LoginScreen.kt index e84dc64..ce84789 100644 --- a/app/src/main/java/ru/fromchat/ui/auth/LoginScreen.kt +++ b/app/src/main/java/ru/fromchat/ui/auth/LoginScreen.kt @@ -1,11 +1,14 @@ package ru.fromchat.ui.auth -import android.util.Log import androidx.compose.foundation.layout.Arrangement 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.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.automirrored.filled.Login import androidx.compose.material3.Button @@ -34,7 +37,7 @@ fun LoginScreen( onLoginSuccess: () -> Unit, onNavigateToRegister: () -> Unit ) { - Scaffold { innerPadding -> + Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding -> var username by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } var alert by remember { mutableStateOf(null) } @@ -44,13 +47,14 @@ fun LoginScreen( Column( modifier = Modifier .fillMaxSize() - .padding(innerPadding) - .padding(16.dp), + .padding(innerPadding), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Column( - modifier = Modifier.padding(16.dp), + modifier = Modifier + .padding(16.dp) + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp) ) { @@ -89,13 +93,12 @@ fun LoginScreen( scope.launch { apiRequest( - onError = { message, e -> - Log.e("LoginScreen", "An error ocurred while logging in:", e) + onError = { message, _ -> alert = message }, onSuccess = { onLoginSuccess() } ) { - ApiClient.login(LoginRequest(username, password)) + ApiClient.login(LoginRequest(username.trim(), password.trim())) } } } diff --git a/app/src/main/java/ru/fromchat/ui/auth/RegisterScreen.kt b/app/src/main/java/ru/fromchat/ui/auth/RegisterScreen.kt index 04b7bbe..e4b554e 100644 --- a/app/src/main/java/ru/fromchat/ui/auth/RegisterScreen.kt +++ b/app/src/main/java/ru/fromchat/ui/auth/RegisterScreen.kt @@ -3,11 +3,19 @@ package ru.fromchat.ui.auth import android.util.Log import androidx.compose.foundation.layout.Arrangement 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.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.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.PersonAdd import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold @@ -26,30 +34,33 @@ import kotlinx.coroutines.launch import ru.fromchat.api.ApiClient import ru.fromchat.api.RegisterRequest import ru.fromchat.api.apiRequest +import ru.fromchat.ui.LocalNavController import ru.fromchat.ui.RowHeader @Composable fun RegisterScreen( onRegistered: () -> Unit ) { - Scaffold { innerPadding -> + Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding -> var username by remember { mutableStateOf("") } var password by remember { mutableStateOf("") } var confirmPassword by remember { mutableStateOf("") } var alert by remember { mutableStateOf(null) } val scope = rememberCoroutineScope() + val navController = LocalNavController.current Column( modifier = Modifier .fillMaxSize() - .padding(innerPadding) - .padding(16.dp), + .padding(innerPadding), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Column( - modifier = Modifier.padding(16.dp), + modifier = Modifier + .padding(16.dp) + .verticalScroll(rememberScrollState()), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(8.dp) ) { @@ -86,47 +97,58 @@ fun RegisterScreen( visualTransformation = PasswordVisualTransformation() ) - Button( - onClick = { - // Checks - if (username.isBlank() || password.isBlank() || confirmPassword.isBlank()) { - alert = "Пожалуйста, заполните все поля" - return@Button - } - 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 - } + FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) { + IconButton( + onClick = { navController.navigateUp() } + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back" + ) + } - // 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 + Button( + onClick = { + // Checks + if (username.isBlank() || password.isBlank() || confirmPassword.isBlank()) { + alert = "Пожалуйста, заполните все поля" + return@Button + } + 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 + 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("Зарегистрироваться") } } } diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..fafdc05 --- /dev/null +++ b/release.sh @@ -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 \ No newline at end of file