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