1 Commits

7 changed files with 151 additions and 57 deletions
+3
View File
@@ -17,3 +17,6 @@ build
.externalNativeBuild
.cxx
local.properties
keys
releases
+24 -2
View File
@@ -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"
)
}
}
+3 -6
View File
@@ -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">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.FromChat">
android:theme="@style/Theme.FromChat"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -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<LoginResponse>()
@@ -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<String?>(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()))
}
}
}
@@ -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<String?>(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("Зарегистрироваться")
}
}
}
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