mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
Implement proper iOS structure
This commit is contained in:
Generated
+4
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="XcodeMetaData" PROJECT_DIR="$PROJECT_DIR$/iosApp" PROJECT_FILE="$PROJECT_DIR$/iosApp/iosApp.xcodeproj" />
|
||||||
|
</project>
|
||||||
@@ -129,7 +129,7 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compose.resources {
|
compose.resources {
|
||||||
publicResClass = false
|
publicResClass = true
|
||||||
packageOfResClass = "ru.fromchat"
|
packageOfResClass = "ru.fromchat"
|
||||||
generateResClass = auto
|
generateResClass = auto
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,3 +15,4 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ object AndroidKtorLogger : Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ actual object Logger {
|
|||||||
Log.e(tag, message, throwable)
|
Log.e(tag, message, throwable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">FromChat</string>
|
<string name="app_name" translatable="false">FromChat</string>
|
||||||
<string name="register">Регистрация</string>
|
|
||||||
<string name="register_d">Создайте новый аккаунт</string>
|
|
||||||
<string name="register_button">Зарегистрироваться</string>
|
|
||||||
<string name="username">Имя пользователя</string>
|
|
||||||
<string name="password">Пароль</string>
|
|
||||||
<string name="confirm_password">Подтвердите пароль</string>
|
|
||||||
<string name="login">Войти</string>
|
|
||||||
<string name="login_d">Войдите в аккаунт</string>
|
|
||||||
<string name="welcome">Добро пожаловать!</string>
|
|
||||||
<string name="fill_all_fields">Пожалуйста, заполните все поля</string>
|
|
||||||
<string name="username_length_error">Имя пользователя должно быть от 3 до 20 символов</string>
|
|
||||||
<string name="password_length_error">Пароль должен быть от 5 до 50 символов</string>
|
|
||||||
<string name="passwords_dont_match">Пароли не совпадают</string>
|
|
||||||
<string name="chats">Чаты</string>
|
|
||||||
<string name="contacts">Контакты</string>
|
|
||||||
<string name="dms">ЛС</string>
|
|
||||||
<string name="coming_soon">Скоро будет...</string>
|
|
||||||
<string name="public_chat">Общий чат</string>
|
|
||||||
<string name="chat_last_mesaage">Вы: Последние сообщение</string>
|
|
||||||
<string name="message_placeholder">Введите сообщение</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- App -->
|
||||||
|
<string name="app_name">FromChat</string>
|
||||||
|
|
||||||
|
<!-- Navigation -->
|
||||||
|
<string name="back">Back</string>
|
||||||
|
<string name="settings">Settings</string>
|
||||||
|
<string name="home">Home</string>
|
||||||
|
<string name="about">About app</string>
|
||||||
|
|
||||||
|
<!-- Authentication -->
|
||||||
|
<string name="welcome">Welcome!</string>
|
||||||
|
<string name="login">Sign In</string>
|
||||||
|
<string name="login_d">Sign in to your account</string>
|
||||||
|
<string name="register">Registration</string>
|
||||||
|
<string name="register_d">Create a new account</string>
|
||||||
|
<string name="register_button">Register</string>
|
||||||
|
<string name="username">Username</string>
|
||||||
|
<string name="password">Password</string>
|
||||||
|
<string name="confirm_password">Confirm Password</string>
|
||||||
|
<string name="display_name">Display Name</string>
|
||||||
|
<string name="display_name_error">Display name must be between 1 and 64 characters</string>
|
||||||
|
|
||||||
|
<!-- Validation Errors -->
|
||||||
|
<string name="fill_all_fields">Please fill in all fields</string>
|
||||||
|
<string name="username_length_error">Username must be between 3 and 20 characters</string>
|
||||||
|
<string name="password_length_error">Password must be between 5 and 50 characters</string>
|
||||||
|
<string name="passwords_dont_match">Passwords do not match</string>
|
||||||
|
|
||||||
|
<!-- Main Screen -->
|
||||||
|
<string name="chats">Chats</string>
|
||||||
|
<string name="contacts">Contacts</string>
|
||||||
|
<string name="dms">Direct Messages</string>
|
||||||
|
<string name="coming_soon">Coming soon...</string>
|
||||||
|
<string name="public_chat">General Chat</string>
|
||||||
|
<string name="chat_last_mesaage">You: Last message</string>
|
||||||
|
<string name="message_placeholder">Type a message...</string>
|
||||||
|
|
||||||
|
<!-- Error Messages -->
|
||||||
|
<string name="error_unexpected">Unexpected error</string>
|
||||||
|
<string name="error_invalid_credentials">Invalid username or password</string>
|
||||||
|
<string name="error_connection">Connection error</string>
|
||||||
|
<string name="error_unknown">An unknown error occurred</string>
|
||||||
|
</resources>
|
||||||
@@ -5,6 +5,7 @@ import io.ktor.client.call.body
|
|||||||
import io.ktor.client.plugins.ClientRequestException
|
import io.ktor.client.plugins.ClientRequestException
|
||||||
|
|
||||||
suspend inline fun <Response> apiRequest(
|
suspend inline fun <Response> apiRequest(
|
||||||
|
unexpectedError: String,
|
||||||
onError: (String, Exception) -> Unit = { _, _ -> },
|
onError: (String, Exception) -> Unit = { _, _ -> },
|
||||||
onSuccess: (Response) -> Unit = {},
|
onSuccess: (Response) -> Unit = {},
|
||||||
request: suspend () -> Response
|
request: suspend () -> Response
|
||||||
@@ -17,7 +18,7 @@ suspend inline fun <Response> apiRequest(
|
|||||||
val message = if (e.response.status.value in arrayOf(401, 403)) {
|
val message = if (e.response.status.value in arrayOf(401, 403)) {
|
||||||
e.response.body<ErrorResponse>().detail
|
e.response.body<ErrorResponse>().detail
|
||||||
} else {
|
} else {
|
||||||
"Unexpected error"
|
unexpectedError
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.e("API", "API request failed: $message", e)
|
Logger.e("API", "API request failed: $message", e)
|
||||||
@@ -26,7 +27,7 @@ suspend inline fun <Response> apiRequest(
|
|||||||
return Result.failure(e)
|
return Result.failure(e)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.e("API", "API request failed: ${e.message}", e)
|
Logger.e("API", "API request failed: ${e.message}", e)
|
||||||
onError("Unexpected error", e)
|
onError(unexpectedError, e)
|
||||||
return Result.failure(e)
|
return Result.failure(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package ru.fromchat.api
|
package ru.fromchat.api
|
||||||
|
|
||||||
import ru.fromchat.core.Logger
|
|
||||||
import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
|
import io.ktor.client.plugins.websocket.DefaultClientWebSocketSession
|
||||||
import io.ktor.client.plugins.websocket.webSocket
|
import io.ktor.client.plugins.websocket.webSocket
|
||||||
import io.ktor.client.request.url
|
import io.ktor.client.request.url
|
||||||
@@ -10,6 +9,7 @@ import io.ktor.websocket.readText
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.IO
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -20,6 +20,8 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withTimeout
|
import kotlinx.coroutines.withTimeout
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import ru.fromchat.WS_API_HOST
|
import ru.fromchat.WS_API_HOST
|
||||||
|
import ru.fromchat.core.Logger
|
||||||
|
import kotlin.concurrent.Volatile
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
object WebSocketManager {
|
object WebSocketManager {
|
||||||
@@ -41,7 +43,8 @@ object WebSocketManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// State
|
// State
|
||||||
@Volatile private var connecting: Boolean = false
|
@Volatile
|
||||||
|
private var connecting: Boolean = false
|
||||||
@Volatile private var session: DefaultClientWebSocketSession? = null
|
@Volatile private var session: DefaultClientWebSocketSession? = null
|
||||||
|
|
||||||
fun connect() {
|
fun connect() {
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ expect object Logger {
|
|||||||
fun w(tag: String, message: String, throwable: Throwable? = null)
|
fun w(tag: String, message: String, throwable: Throwable? = null)
|
||||||
fun e(tag: String, message: String, throwable: Throwable? = null)
|
fun e(tag: String, message: String, throwable: Throwable? = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,10 @@ fun App() {
|
|||||||
) {
|
) {
|
||||||
composable("login") {
|
composable("login") {
|
||||||
LoginScreen(
|
LoginScreen(
|
||||||
onLoginSuccess = { navController.navigate("chat") },
|
onLoginSuccess = {
|
||||||
|
navController.navigate("chat")
|
||||||
|
navController.popBackStack()
|
||||||
|
},
|
||||||
onNavigateToRegister = { navController.navigate("register") }
|
onNavigateToRegister = { navController.navigate("register") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,22 +24,32 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pr0gramm3r101.utils.crypto.deriveAuthSecret
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.fromchat.R
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import ru.fromchat.Res
|
||||||
import ru.fromchat.api.ApiClient
|
import ru.fromchat.api.ApiClient
|
||||||
import ru.fromchat.api.LoginRequest
|
import ru.fromchat.api.LoginRequest
|
||||||
import ru.fromchat.api.apiRequest
|
import ru.fromchat.api.apiRequest
|
||||||
|
import ru.fromchat.error_unexpected
|
||||||
|
import ru.fromchat.fill_all_fields
|
||||||
|
import ru.fromchat.login
|
||||||
|
import ru.fromchat.login_d
|
||||||
|
import ru.fromchat.password
|
||||||
|
import ru.fromchat.register_button
|
||||||
import ru.fromchat.ui.RowHeader
|
import ru.fromchat.ui.RowHeader
|
||||||
import com.pr0gramm3r101.utils.crypto.deriveAuthSecret
|
import ru.fromchat.username
|
||||||
|
import ru.fromchat.welcome
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreen(
|
fun LoginScreen(
|
||||||
onLoginSuccess: () -> Unit,
|
onLoginSuccess: () -> Unit,
|
||||||
onNavigateToRegister: () -> Unit
|
onNavigateToRegister: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val errorUnexpected = stringResource(Res.string.error_unexpected)
|
||||||
|
|
||||||
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { 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("") }
|
||||||
@@ -63,8 +73,8 @@ fun LoginScreen(
|
|||||||
) {
|
) {
|
||||||
RowHeader(
|
RowHeader(
|
||||||
icon = Icons.AutoMirrored.Filled.Login,
|
icon = Icons.AutoMirrored.Filled.Login,
|
||||||
title = stringResource(R.string.welcome),
|
title = stringResource(Res.string.welcome),
|
||||||
subtitle = stringResource(R.string.login_d)
|
subtitle = stringResource(Res.string.login_d)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (alert != null) {
|
if (alert != null) {
|
||||||
@@ -74,25 +84,25 @@ fun LoginScreen(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = username,
|
value = username,
|
||||||
onValueChange = { username = it },
|
onValueChange = { username = it },
|
||||||
label = { Text(stringResource(R.string.username)) },
|
label = { Text(stringResource(Res.string.username)) },
|
||||||
singleLine = true
|
singleLine = true
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = password,
|
value = password,
|
||||||
onValueChange = { password = it },
|
onValueChange = { password = it },
|
||||||
label = { Text(stringResource(R.string.password)) },
|
label = { Text(stringResource(Res.string.password)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = PasswordVisualTransformation()
|
visualTransformation = PasswordVisualTransformation()
|
||||||
)
|
)
|
||||||
|
|
||||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
val alert_error_filling = stringResource(R.string.fill_all_fields)
|
val alertErrorFilling = stringResource(Res.string.fill_all_fields)
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (username.isBlank() || password.isBlank()) {
|
if (username.isBlank() || password.isBlank()) {
|
||||||
alert = alert_error_filling
|
alert = alertErrorFilling
|
||||||
return@Button
|
return@Button
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +111,7 @@ fun LoginScreen(
|
|||||||
val derived = deriveAuthSecret(username.trim(), password.trim())
|
val derived = deriveAuthSecret(username.trim(), password.trim())
|
||||||
|
|
||||||
apiRequest(
|
apiRequest(
|
||||||
|
unexpectedError = errorUnexpected,
|
||||||
onError = { message, _ ->
|
onError = { message, _ ->
|
||||||
alert = message
|
alert = message
|
||||||
},
|
},
|
||||||
@@ -111,11 +122,11 @@ fun LoginScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.login))
|
Text(stringResource(Res.string.login))
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(onClick = onNavigateToRegister) {
|
Button(onClick = onNavigateToRegister) {
|
||||||
Text(stringResource(R.string.register_button))
|
Text(stringResource(Res.string.register_button))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,22 +27,38 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pr0gramm3r101.utils.crypto.deriveAuthSecret
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import ru.fromchat.R
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import ru.fromchat.Res
|
||||||
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.back
|
||||||
|
import ru.fromchat.confirm_password
|
||||||
|
import ru.fromchat.display_name
|
||||||
|
import ru.fromchat.display_name_error
|
||||||
|
import ru.fromchat.error_unexpected
|
||||||
|
import ru.fromchat.fill_all_fields
|
||||||
|
import ru.fromchat.password
|
||||||
|
import ru.fromchat.password_length_error
|
||||||
|
import ru.fromchat.passwords_dont_match
|
||||||
|
import ru.fromchat.register
|
||||||
|
import ru.fromchat.register_button
|
||||||
|
import ru.fromchat.register_d
|
||||||
import ru.fromchat.ui.LocalNavController
|
import ru.fromchat.ui.LocalNavController
|
||||||
import ru.fromchat.ui.RowHeader
|
import ru.fromchat.ui.RowHeader
|
||||||
import com.pr0gramm3r101.utils.crypto.deriveAuthSecret
|
import ru.fromchat.username
|
||||||
|
import ru.fromchat.username_length_error
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RegisterScreen(
|
fun RegisterScreen(
|
||||||
onRegistered: () -> Unit
|
onRegistered: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val errorUnexpected = stringResource(Res.string.error_unexpected)
|
||||||
|
|
||||||
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
|
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
|
||||||
var username by remember { mutableStateOf("") }
|
var username by remember { mutableStateOf("") }
|
||||||
var displayName by remember { mutableStateOf("") }
|
var displayName by remember { mutableStateOf("") }
|
||||||
@@ -69,8 +85,8 @@ fun RegisterScreen(
|
|||||||
) {
|
) {
|
||||||
RowHeader(
|
RowHeader(
|
||||||
icon = Icons.Filled.PersonAdd,
|
icon = Icons.Filled.PersonAdd,
|
||||||
title = stringResource(R.string.register),
|
title = stringResource(Res.string.register),
|
||||||
subtitle = stringResource(R.string.register_d)
|
subtitle = stringResource(Res.string.register_d)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (alert != null) {
|
if (alert != null) {
|
||||||
@@ -80,21 +96,21 @@ fun RegisterScreen(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = username,
|
value = username,
|
||||||
onValueChange = { username = it },
|
onValueChange = { username = it },
|
||||||
label = { Text(stringResource(R.string.username)) },
|
label = { Text(stringResource(Res.string.username)) },
|
||||||
singleLine = true
|
singleLine = true
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = displayName,
|
value = displayName,
|
||||||
onValueChange = { displayName = it },
|
onValueChange = { displayName = it },
|
||||||
label = { Text("Display Name") },
|
label = { Text(stringResource(Res.string.display_name)) },
|
||||||
singleLine = true
|
singleLine = true
|
||||||
)
|
)
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = password,
|
value = password,
|
||||||
onValueChange = { password = it },
|
onValueChange = { password = it },
|
||||||
label = { Text(stringResource(R.string.password)) },
|
label = { Text(stringResource(Res.string.password)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = PasswordVisualTransformation()
|
visualTransformation = PasswordVisualTransformation()
|
||||||
)
|
)
|
||||||
@@ -102,23 +118,24 @@ fun RegisterScreen(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = confirmPassword,
|
value = confirmPassword,
|
||||||
onValueChange = { confirmPassword = it },
|
onValueChange = { confirmPassword = it },
|
||||||
label = { Text(stringResource(R.string.confirm_password)) },
|
label = { Text(stringResource(Res.string.confirm_password)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
visualTransformation = PasswordVisualTransformation()
|
visualTransformation = PasswordVisualTransformation()
|
||||||
)
|
)
|
||||||
|
|
||||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
FlowRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
val alert_error_filling = stringResource(R.string.fill_all_fields)
|
val alertErrorFilling = stringResource(Res.string.fill_all_fields)
|
||||||
val alert_error_password_little = stringResource(R.string.password_length_error)
|
val alertErrorPasswordLittle = stringResource(Res.string.password_length_error)
|
||||||
val alert_error_name_little = stringResource(R.string.username_length_error)
|
val alertErrorNameLittle = stringResource(Res.string.username_length_error)
|
||||||
val alert_error_password_confrim = stringResource(R.string.passwords_dont_match)
|
val alertErrorPasswordConfrim = stringResource(Res.string.passwords_dont_match)
|
||||||
|
val alertErrorDisplayName = stringResource(Res.string.display_name_error)
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { navController.navigateUp() }
|
onClick = { navController.navigateUp() }
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = "Back"
|
contentDescription = stringResource(Res.string.back)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,23 +143,23 @@ fun RegisterScreen(
|
|||||||
onClick = {
|
onClick = {
|
||||||
// Checks
|
// Checks
|
||||||
if (username.isBlank() || displayName.isBlank() || password.isBlank() || confirmPassword.isBlank()) {
|
if (username.isBlank() || displayName.isBlank() || password.isBlank() || confirmPassword.isBlank()) {
|
||||||
alert = alert_error_filling
|
alert = alertErrorFilling
|
||||||
return@Button
|
return@Button
|
||||||
}
|
}
|
||||||
if (password != confirmPassword) {
|
if (password != confirmPassword) {
|
||||||
alert = alert_error_password_confrim
|
alert = alertErrorPasswordConfrim
|
||||||
return@Button
|
return@Button
|
||||||
}
|
}
|
||||||
if (username.length !in 3..20) {
|
if (username.length !in 3..20) {
|
||||||
alert = alert_error_name_little
|
alert = alertErrorNameLittle
|
||||||
return@Button
|
return@Button
|
||||||
}
|
}
|
||||||
if (displayName.isBlank() || displayName.length > 64) {
|
if (displayName.isBlank() || displayName.length > 64) {
|
||||||
alert = "Display name must be between 1 and 64 characters"
|
alert = alertErrorDisplayName
|
||||||
return@Button
|
return@Button
|
||||||
}
|
}
|
||||||
if (password.length !in 5..50) {
|
if (password.length !in 5..50) {
|
||||||
alert = alert_error_password_little
|
alert = alertErrorPasswordLittle
|
||||||
return@Button
|
return@Button
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +168,7 @@ fun RegisterScreen(
|
|||||||
val derived = deriveAuthSecret(username.trim(), password)
|
val derived = deriveAuthSecret(username.trim(), password)
|
||||||
|
|
||||||
apiRequest(
|
apiRequest(
|
||||||
|
unexpectedError = errorUnexpected,
|
||||||
onError = { message, _ ->
|
onError = { message, _ ->
|
||||||
alert = message
|
alert = message
|
||||||
},
|
},
|
||||||
@@ -168,7 +186,7 @@ fun RegisterScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.register_button))
|
Text(stringResource(Res.string.register_button))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,12 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import ru.fromchat.R
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
import ru.fromchat.Res
|
||||||
|
import ru.fromchat.chat_last_mesaage
|
||||||
|
import ru.fromchat.chats
|
||||||
|
import ru.fromchat.public_chat
|
||||||
import ru.fromchat.ui.LocalNavController
|
import ru.fromchat.ui.LocalNavController
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -28,7 +31,7 @@ fun ChatsTab() {
|
|||||||
MediumTopAppBar(
|
MediumTopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.chats),
|
text = stringResource(Res.string.chats),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
@@ -40,8 +43,8 @@ fun ChatsTab() {
|
|||||||
LazyColumn(contentPadding = innerPadding) {
|
LazyColumn(contentPadding = innerPadding) {
|
||||||
item {
|
item {
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(stringResource(R.string.public_chat)) },
|
headlineContent = { Text(stringResource(Res.string.public_chat)) },
|
||||||
supportingContent = { Text(stringResource(R.string.chat_last_mesaage)) },
|
supportingContent = { Text(stringResource(Res.string.chat_last_mesaage)) },
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
navController.navigate("chats/publicChat")
|
navController.navigate("chats/publicChat")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import ru.fromchat.R
|
import ru.fromchat.Res
|
||||||
|
import ru.fromchat.chats
|
||||||
|
import ru.fromchat.coming_soon
|
||||||
|
import ru.fromchat.contacts
|
||||||
|
import ru.fromchat.dms
|
||||||
import ru.fromchat.utils.exclude
|
import ru.fromchat.utils.exclude
|
||||||
|
|
||||||
|
@Suppress("AssignedValueIsNeverRead")
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen() {
|
fun MainScreen() {
|
||||||
var selectedTab by remember { mutableStateOf("chats") }
|
var selectedTab by remember { mutableStateOf("chats") }
|
||||||
@@ -36,19 +41,19 @@ fun MainScreen() {
|
|||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = selectedTab == "chats",
|
selected = selectedTab == "chats",
|
||||||
onClick = { selectedTab = "chats" },
|
onClick = { selectedTab = "chats" },
|
||||||
label = { Text(stringResource(R.string.chats)) },
|
label = { Text(stringResource(Res.string.chats)) },
|
||||||
icon = { Icon(Icons.AutoMirrored.Filled.Chat, contentDescription = null) }
|
icon = { Icon(Icons.AutoMirrored.Filled.Chat, contentDescription = null) }
|
||||||
)
|
)
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = selectedTab == "contacts",
|
selected = selectedTab == "contacts",
|
||||||
onClick = { selectedTab = "contacts" },
|
onClick = { selectedTab = "contacts" },
|
||||||
label = { Text(stringResource(R.string.contacts)) },
|
label = { Text(stringResource(Res.string.contacts)) },
|
||||||
icon = { Icon(Icons.Filled.Contacts, contentDescription = null) }
|
icon = { Icon(Icons.Filled.Contacts, contentDescription = null) }
|
||||||
)
|
)
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = selectedTab == "dms",
|
selected = selectedTab == "dms",
|
||||||
onClick = { selectedTab = "dms" },
|
onClick = { selectedTab = "dms" },
|
||||||
label = { Text(stringResource(R.string.dms)) },
|
label = { Text(stringResource(Res.string.dms)) },
|
||||||
icon = { Icon(Icons.Filled.Mail, contentDescription = null) }
|
icon = { Icon(Icons.Filled.Mail, contentDescription = null) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -64,10 +69,10 @@ fun MainScreen() {
|
|||||||
when (selectedTab) {
|
when (selectedTab) {
|
||||||
"chats" -> ChatsTab()
|
"chats" -> ChatsTab()
|
||||||
"contacts" -> {
|
"contacts" -> {
|
||||||
Text(stringResource(R.string.coming_soon))
|
Text(stringResource(Res.string.coming_soon))
|
||||||
}
|
}
|
||||||
"dms" -> {
|
"dms" -> {
|
||||||
Text(stringResource(R.string.coming_soon))
|
Text(stringResource(Res.string.coming_soon))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ import androidx.compose.foundation.layout.WindowInsetsSides
|
|||||||
import androidx.compose.foundation.layout.exclude
|
import androidx.compose.foundation.layout.exclude
|
||||||
import androidx.compose.foundation.layout.only
|
import androidx.compose.foundation.layout.only
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun WindowInsets.exclude(sides: WindowInsetsSides) = exclude(this.only(sides))
|
inline fun WindowInsets.exclude(sides: WindowInsetsSides) = exclude(this.only(sides))
|
||||||
@@ -4,3 +4,4 @@ import ru.fromchat.ui.App
|
|||||||
import androidx.compose.ui.window.ComposeUIViewController
|
import androidx.compose.ui.window.ComposeUIViewController
|
||||||
|
|
||||||
fun MainViewController() = ComposeUIViewController { App() }
|
fun MainViewController() = ComposeUIViewController { App() }
|
||||||
|
|
||||||
|
|||||||
@@ -19,3 +19,4 @@ actual object Logger {
|
|||||||
NSLog("ERROR: [%s] %s %s", tag, message, throwable?.message ?: "")
|
NSLog("ERROR: [%s] %s %s", tag, message, throwable?.message ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
TEAM_ID=82MWK23YNM
|
||||||
|
BUNDLE_ID=ru.fromchat.app
|
||||||
|
APP_NAME=FromChat
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 56;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
|
||||||
|
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
|
||||||
|
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
|
||||||
|
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
|
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
|
||||||
|
7555FF7B242A565900829871 /* FromChat.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FromChat.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
|
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
B92378962B6B1156000C7307 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
058557D7273AAEEB004C7B11 /* Preview Content */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
|
||||||
|
);
|
||||||
|
path = "Preview Content";
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
42799AB246E5F90AF97AA0EF /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
7555FF72242A565900829871 = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
AB1DB47929225F7C00F7AF9C /* Configuration */,
|
||||||
|
7555FF7D242A565900829871 /* iosApp */,
|
||||||
|
7555FF7C242A565900829871 /* Products */,
|
||||||
|
42799AB246E5F90AF97AA0EF /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
7555FF7C242A565900829871 /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
7555FF7B242A565900829871 /* FromChat.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
7555FF7D242A565900829871 /* iosApp */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
058557BA273AAA24004C7B11 /* Assets.xcassets */,
|
||||||
|
7555FF82242A565900829871 /* ContentView.swift */,
|
||||||
|
7555FF8C242A565B00829871 /* Info.plist */,
|
||||||
|
2152FB032600AC8F00CF470E /* iOSApp.swift */,
|
||||||
|
058557D7273AAEEB004C7B11 /* Preview Content */,
|
||||||
|
);
|
||||||
|
path = iosApp;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
AB1DB47929225F7C00F7AF9C /* Configuration */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
AB3632DC29227652001CCB65 /* Config.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Configuration;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
7555FF7A242A565900829871 /* iosApp */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
|
||||||
|
buildPhases = (
|
||||||
|
F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */,
|
||||||
|
7555FF77242A565900829871 /* Sources */,
|
||||||
|
B92378962B6B1156000C7307 /* Frameworks */,
|
||||||
|
7555FF79242A565900829871 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = iosApp;
|
||||||
|
packageProductDependencies = (
|
||||||
|
);
|
||||||
|
productName = iosApp;
|
||||||
|
productReference = 7555FF7B242A565900829871 /* FromChat.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
7555FF73242A565900829871 /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastSwiftUpdateCheck = 1130;
|
||||||
|
LastUpgradeCheck = 2610;
|
||||||
|
ORGANIZATIONNAME = orgName;
|
||||||
|
TargetAttributes = {
|
||||||
|
7555FF7A242A565900829871 = {
|
||||||
|
CreatedOnToolsVersion = 11.3.1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
|
||||||
|
compatibilityVersion = "Xcode 14.0";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 7555FF72242A565900829871;
|
||||||
|
packageReferences = (
|
||||||
|
);
|
||||||
|
productRefGroup = 7555FF7C242A565900829871 /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
7555FF7A242A565900829871 /* iosApp */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
7555FF79242A565900829871 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
|
||||||
|
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Compile Kotlin Framework";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
7555FF77242A565900829871 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
|
||||||
|
7555FF83242A565900829871 /* ContentView.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
7555FFA3242A565B00829871 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.3;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
7555FFA4242A565B00829871 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.3;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
7555FFA6242A565B00829871 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
||||||
|
DEVELOPMENT_TEAM = 82MWK23YNM;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
|
||||||
|
);
|
||||||
|
INFOPLIST_FILE = iosApp/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = FromChat;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.3;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
|
||||||
|
PRODUCT_NAME = "${APP_NAME}";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
7555FFA7242A565B00829871 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
||||||
|
DEVELOPMENT_TEAM = 82MWK23YNM;
|
||||||
|
ENABLE_PREVIEWS = YES;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)",
|
||||||
|
);
|
||||||
|
INFOPLIST_FILE = iosApp/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = FromChat;
|
||||||
|
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.social-networking";
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.3;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}";
|
||||||
|
PRODUCT_NAME = "${APP_NAME}";
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||||
|
SUPPORTS_MACCATALYST = NO;
|
||||||
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
7555FFA3242A565B00829871 /* Debug */,
|
||||||
|
7555FFA4242A565B00829871 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
7555FFA6242A565B00829871 /* Debug */,
|
||||||
|
7555FFA7242A565B00829871 /* Release */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 7555FF73242A565900829871 /* Project object */;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
BIN
Binary file not shown.
+5
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict />
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
version = "1.7">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "7555FF7A242A565900829871"
|
||||||
|
BuildableName = "FromChat.app"
|
||||||
|
BlueprintName = "iosApp"
|
||||||
|
ReferencedContainer = "container:iosApp.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
shouldAutocreateTestPlan = "YES">
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "7555FF7A242A565900829871"
|
||||||
|
BuildableName = "FromChat.app"
|
||||||
|
BlueprintName = "iosApp"
|
||||||
|
ReferencedContainer = "container:iosApp.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<LocationScenarioReference
|
||||||
|
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
|
||||||
|
referenceType = "1">
|
||||||
|
</LocationScenarioReference>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>SchemeUserState</key>
|
||||||
|
<dict>
|
||||||
|
<key>iOS.xcscheme</key>
|
||||||
|
<dict>
|
||||||
|
<key>orderHint</key>
|
||||||
|
<integer>0</integer>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<key>SuppressBuildableAutocreation</key>
|
||||||
|
<dict>
|
||||||
|
<key>7555FF7A242A565900829871</key>
|
||||||
|
<dict>
|
||||||
|
<key>primary</key>
|
||||||
|
<true />
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "logo_square.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
import ComposeApp
|
||||||
|
|
||||||
|
struct ComposeView: UIViewControllerRepresentable {
|
||||||
|
func makeUIViewController(context: Context) -> UIViewController {
|
||||||
|
MainViewControllerKt.MainViewController()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentView: View {
|
||||||
|
var body: some View {
|
||||||
|
ComposeView()
|
||||||
|
.ignoresSafeArea(.all) // Compose has own keyboard handler
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>$(PRODUCT_NAME)</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
|
<dict>
|
||||||
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
<key>UILaunchScreen</key>
|
||||||
|
<dict/>
|
||||||
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
|
<array>
|
||||||
|
<string>armv7</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct iOSApp: App {
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,23 +1,33 @@
|
|||||||
package com.pr0gramm3r101.utils.crypto
|
package com.pr0gramm3r101.utils.crypto
|
||||||
|
|
||||||
|
import kotlinx.cinterop.BetaInteropApi
|
||||||
import kotlinx.cinterop.ByteVar
|
import kotlinx.cinterop.ByteVar
|
||||||
|
import kotlinx.cinterop.ExperimentalForeignApi
|
||||||
|
import kotlinx.cinterop.addressOf
|
||||||
|
import kotlinx.cinterop.get
|
||||||
|
import kotlinx.cinterop.reinterpret
|
||||||
import kotlinx.cinterop.usePinned
|
import kotlinx.cinterop.usePinned
|
||||||
import platform.Foundation.NSData
|
import platform.Foundation.NSData
|
||||||
|
import platform.Foundation.base64EncodedStringWithOptions
|
||||||
|
import platform.Foundation.create
|
||||||
|
|
||||||
actual object Base64 {
|
actual object Base64 {
|
||||||
|
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
|
||||||
actual fun encode(bytes: ByteArray): String {
|
actual fun encode(bytes: ByteArray): String {
|
||||||
return bytes.usePinned { pinned ->
|
return bytes.usePinned { pinned ->
|
||||||
val nsData = NSData.create(bytes = pinned.addressOf(0), length = bytes.size.toULong())
|
val nsData = NSData.create(bytes = pinned.addressOf(0), length = bytes.size.toULong())
|
||||||
nsData.base64EncodedStringWithOptions(0u)
|
nsData.base64EncodedStringWithOptions(0u)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(BetaInteropApi::class, ExperimentalForeignApi::class)
|
||||||
actual fun decode(base64: String): ByteArray {
|
actual fun decode(base64: String): ByteArray {
|
||||||
val nsData = NSData.create(base64EncodedString = base64, options = 0u) ?: return ByteArray(0)
|
val nsData = NSData.create(base64EncodedString = base64, options = 0u) ?: return ByteArray(0)
|
||||||
val length = nsData.length.toInt()
|
val length = nsData.length.toInt()
|
||||||
val bytes = nsData.bytes ?: return ByteArray(0)
|
val bytesPtr = nsData.bytes ?: return ByteArray(0)
|
||||||
|
val bytePtr = bytesPtr.reinterpret<ByteVar>()
|
||||||
return ByteArray(length) { index ->
|
return ByteArray(length) { index ->
|
||||||
bytes.reinterpret<ByteVar>()[index]
|
bytePtr[index]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.pr0gramm3r101.utils.crypto
|
package com.pr0gramm3r101.utils.crypto
|
||||||
|
|
||||||
|
import kotlinx.cinterop.ExperimentalForeignApi
|
||||||
|
import kotlinx.cinterop.addressOf
|
||||||
|
import kotlinx.cinterop.usePinned
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import platform.CommonCrypto.CCHmac
|
import platform.CoreCrypto.CCHmac
|
||||||
import platform.CommonCrypto.kCCHmacAlgSHA256
|
import platform.CoreCrypto.kCCHmacAlgSHA256
|
||||||
|
|
||||||
actual object Hmac {
|
actual object Hmac {
|
||||||
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
actual suspend fun hmacSha256(key: ByteArray, data: ByteArray): ByteArray = withContext(Dispatchers.Default) {
|
actual suspend fun hmacSha256(key: ByteArray, data: ByteArray): ByteArray = withContext(Dispatchers.Default) {
|
||||||
val result = ByteArray(32)
|
val result = ByteArray(32)
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ package com.pr0gramm3r101.utils.settings
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import platform.Foundation.NSUserDefaults
|
import platform.Foundation.NSUserDefaults
|
||||||
|
import platform.Foundation.NSBundle
|
||||||
|
|
||||||
class IosAsyncSettings : AsyncSettings {
|
class IosAsyncSettings : AsyncSettings {
|
||||||
private val defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults
|
private val defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults
|
||||||
|
|
||||||
override suspend fun putString(key: String, value: String) = withContext(Dispatchers.Default) {
|
override suspend fun putString(key: String, value: String): Unit = withContext(Dispatchers.Default) {
|
||||||
defaults.setObject(value, key)
|
defaults.setObject(value, key)
|
||||||
defaults.synchronize()
|
defaults.synchronize()
|
||||||
}
|
}
|
||||||
@@ -16,7 +17,7 @@ class IosAsyncSettings : AsyncSettings {
|
|||||||
defaults.stringForKey(key) ?: default
|
defaults.stringForKey(key) ?: default
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putInt(key: String, value: Int) = withContext(Dispatchers.Default) {
|
override suspend fun putInt(key: String, value: Int): Unit = withContext(Dispatchers.Default) {
|
||||||
defaults.setInteger(value.toLong(), key)
|
defaults.setInteger(value.toLong(), key)
|
||||||
defaults.synchronize()
|
defaults.synchronize()
|
||||||
}
|
}
|
||||||
@@ -26,7 +27,7 @@ class IosAsyncSettings : AsyncSettings {
|
|||||||
if (value != null) value.toInt() else default
|
if (value != null) value.toInt() else default
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putLong(key: String, value: Long) = withContext(Dispatchers.Default) {
|
override suspend fun putLong(key: String, value: Long): Unit = withContext(Dispatchers.Default) {
|
||||||
defaults.setObject(value, key)
|
defaults.setObject(value, key)
|
||||||
defaults.synchronize()
|
defaults.synchronize()
|
||||||
}
|
}
|
||||||
@@ -35,7 +36,7 @@ class IosAsyncSettings : AsyncSettings {
|
|||||||
(defaults.objectForKey(key) as? platform.Foundation.NSNumber)?.longValue ?: default
|
(defaults.objectForKey(key) as? platform.Foundation.NSNumber)?.longValue ?: default
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putFloat(key: String, value: Float) = withContext(Dispatchers.Default) {
|
override suspend fun putFloat(key: String, value: Float): Unit = withContext(Dispatchers.Default) {
|
||||||
defaults.setFloat(value, key)
|
defaults.setFloat(value, key)
|
||||||
defaults.synchronize()
|
defaults.synchronize()
|
||||||
}
|
}
|
||||||
@@ -45,7 +46,7 @@ class IosAsyncSettings : AsyncSettings {
|
|||||||
if (value != null) value.toFloat() else default
|
if (value != null) value.toFloat() else default
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putBoolean(key: String, value: Boolean) = withContext(Dispatchers.Default) {
|
override suspend fun putBoolean(key: String, value: Boolean): Unit = withContext(Dispatchers.Default) {
|
||||||
defaults.setBool(value, key)
|
defaults.setBool(value, key)
|
||||||
defaults.synchronize()
|
defaults.synchronize()
|
||||||
}
|
}
|
||||||
@@ -58,7 +59,7 @@ class IosAsyncSettings : AsyncSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putStringSet(key: String, value: Set<String>) = withContext(Dispatchers.Default) {
|
override suspend fun putStringSet(key: String, value: Set<String>): Unit = withContext(Dispatchers.Default) {
|
||||||
defaults.setObject(value.toList(), key)
|
defaults.setObject(value.toList(), key)
|
||||||
defaults.synchronize()
|
defaults.synchronize()
|
||||||
}
|
}
|
||||||
@@ -68,7 +69,7 @@ class IosAsyncSettings : AsyncSettings {
|
|||||||
list.filterIsInstance<String>().toSet()
|
list.filterIsInstance<String>().toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putStringList(key: String, value: List<String>) = withContext(Dispatchers.Default) {
|
override suspend fun putStringList(key: String, value: List<String>): Unit = withContext(Dispatchers.Default) {
|
||||||
defaults.setObject(value, key)
|
defaults.setObject(value, key)
|
||||||
defaults.synchronize()
|
defaults.synchronize()
|
||||||
}
|
}
|
||||||
@@ -78,7 +79,7 @@ class IosAsyncSettings : AsyncSettings {
|
|||||||
list.filterIsInstance<String>()
|
list.filterIsInstance<String>()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun remove(key: String) = withContext(Dispatchers.Default) {
|
override suspend fun remove(key: String): Unit = withContext(Dispatchers.Default) {
|
||||||
defaults.removeObjectForKey(key)
|
defaults.removeObjectForKey(key)
|
||||||
defaults.synchronize()
|
defaults.synchronize()
|
||||||
}
|
}
|
||||||
@@ -87,8 +88,9 @@ class IosAsyncSettings : AsyncSettings {
|
|||||||
defaults.objectForKey(key) != null
|
defaults.objectForKey(key) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clear() = withContext(Dispatchers.Default) {
|
override suspend fun clear(): Unit = withContext(Dispatchers.Default) {
|
||||||
val domain = defaults.persistentDomainForName(defaults.bundleIdentifier() ?: "")
|
val bundleId = NSBundle.mainBundle.bundleIdentifier ?: ""
|
||||||
|
val domain = defaults.persistentDomainForName(bundleId)
|
||||||
domain?.keys?.forEach { key ->
|
domain?.keys?.forEach { key ->
|
||||||
defaults.removeObjectForKey(key as String)
|
defaults.removeObjectForKey(key as String)
|
||||||
}
|
}
|
||||||
@@ -96,4 +98,4 @@ class IosAsyncSettings : AsyncSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actual val asyncSettings: AsyncSettings = IosAsyncSettings()
|
actual val asyncSettings: AsyncSettings = IosAsyncSettings()
|
||||||
Reference in New Issue
Block a user