commit 00055366ed31c957a05b7648c2e3333c722fc66e Author: AndreyIgorevich Date: Fri Jun 13 05:56:26 2025 +0700 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cf53903 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties +/.idea/* \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..f900131 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,81 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kapt) + alias(libs.plugins.hilt.android) +} + +android { + namespace = "com.cardinalnsk.volnahub" + compileSdk = 35 + + buildFeatures { + buildConfig = true + } + + defaultConfig { + applicationId = "com.cardinalnsk.volnahub" + minSdk = 26 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + + buildConfigField("String", "USER_AGENT", "\"VolnaHub Android App\"") + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = "11" + } + buildFeatures { + compose = true + } +} + +dependencies { + + implementation(libs.retrofit) + implementation(libs.gson.converter) + implementation(libs.okhttp.logging) + implementation(libs.androidx.lifecycle.viewmodel.compose) + + implementation(libs.navigation.compose) + implementation(libs.material.icons.extended) + + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.hilt.android) + kapt(libs.hilt.compiler) + + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} + diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/cardinalnsk/volnahub/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/cardinalnsk/volnahub/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..3e06de1 --- /dev/null +++ b/app/src/androidTest/java/com/cardinalnsk/volnahub/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.cardinalnsk.volnahub + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.myapplication", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fae8c5e --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/MainActivity.kt b/app/src/main/java/com/cardinalnsk/volnahub/MainActivity.kt new file mode 100644 index 0000000..f7bd2a8 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/MainActivity.kt @@ -0,0 +1,32 @@ +package com.cardinalnsk.volnahub + +import android.os.Bundle +import androidx.activity.* +import androidx.activity.compose.setContent +import androidx.compose.material3.Scaffold +import androidx.hilt.navigation.compose.hiltViewModel +import com.cardinalnsk.volnahub.navigation.AppNavHost +import com.cardinalnsk.volnahub.ui.fragments.AppTopBar +import com.cardinalnsk.volnahub.ui.theme.MyApplicationTheme +import com.cardinalnsk.volnahub.viewmodel.ThemeViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + val themeViewModel: ThemeViewModel = hiltViewModel() + val isDark = themeViewModel.isDarkTheme.value + + MyApplicationTheme(darkTheme = isDark) { + Scaffold( + topBar = { AppTopBar(themeViewModel = themeViewModel) } + ) { innerPadding -> + AppNavHost(innerPadding) + } + } + } + } +} diff --git a/app/src/main/java/com/cardinalnsk/volnahub/VolnaHubApplication.kt b/app/src/main/java/com/cardinalnsk/volnahub/VolnaHubApplication.kt new file mode 100644 index 0000000..d17294b --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/VolnaHubApplication.kt @@ -0,0 +1,7 @@ +package com.cardinalnsk.volnahub + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class VolnaHubApplication: Application() \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/di/AppModule.kt b/app/src/main/java/com/cardinalnsk/volnahub/di/AppModule.kt new file mode 100644 index 0000000..e1040f2 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/di/AppModule.kt @@ -0,0 +1,44 @@ +package com.cardinalnsk.volnahub.di + +import com.cardinalnsk.volnahub.network.AuthApi +import com.cardinalnsk.volnahub.network.HeaderInterceptor +import com.cardinalnsk.volnahub.repository.AuthRepository +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppModule { + + @Provides + @Singleton + fun provideOkHttpClient(): OkHttpClient = + OkHttpClient.Builder() + .addInterceptor(HeaderInterceptor()) + .build() + + @Provides + @Singleton + fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = + Retrofit.Builder() + .baseUrl("https://api.volnahub.ru") + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClient) + .build() + + @Provides + @Singleton + fun provideAuthApi(retrofit: Retrofit): AuthApi = + retrofit.create(AuthApi::class.java) + + @Provides + @Singleton + fun provideAuthRepository(authApi: AuthApi): AuthRepository = + AuthRepository(authApi) +} diff --git a/app/src/main/java/com/cardinalnsk/volnahub/model/LoginRequest.kt b/app/src/main/java/com/cardinalnsk/volnahub/model/LoginRequest.kt new file mode 100644 index 0000000..b3d7d4e --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/model/LoginRequest.kt @@ -0,0 +1,6 @@ +package com.cardinalnsk.volnahub.model + +data class LoginRequest( + val login: String, + val password: String +) \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/model/LoginResponse.kt b/app/src/main/java/com/cardinalnsk/volnahub/model/LoginResponse.kt new file mode 100644 index 0000000..aac7bdc --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/model/LoginResponse.kt @@ -0,0 +1,10 @@ +package com.cardinalnsk.volnahub.model + +import com.google.gson.annotations.SerializedName + +data class LoginResponse( + val status: String, + @SerializedName("access_token") val accessToken: String, + @SerializedName("refresh_token") val refreshToken: String, + @SerializedName("token_type") val tokenType: String +) \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/model/RegisterRequest.kt b/app/src/main/java/com/cardinalnsk/volnahub/model/RegisterRequest.kt new file mode 100644 index 0000000..2cf9437 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/model/RegisterRequest.kt @@ -0,0 +1,7 @@ +package com.cardinalnsk.volnahub.model + +data class RegisterRequest( + val login: String, + val password: String, + val invite: String +) \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/model/RegisterResponse.kt b/app/src/main/java/com/cardinalnsk/volnahub/model/RegisterResponse.kt new file mode 100644 index 0000000..11b0d09 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/model/RegisterResponse.kt @@ -0,0 +1,6 @@ +package com.cardinalnsk.volnahub.model + +data class RegisterResponse( + val status: String, + val message: String +) \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/navigation/AppNavHost.kt b/app/src/main/java/com/cardinalnsk/volnahub/navigation/AppNavHost.kt new file mode 100644 index 0000000..4899715 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/navigation/AppNavHost.kt @@ -0,0 +1,152 @@ +package com.cardinalnsk.volnahub.navigation + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.* +import androidx.navigation.compose.* +import com.cardinalnsk.volnahub.ui.fragments.AppTopBar +import com.cardinalnsk.volnahub.ui.screen.AuthScreen +import com.cardinalnsk.volnahub.ui.screen.RegisterScreen +import com.cardinalnsk.volnahub.ui.screen.ContactsScreen +import com.cardinalnsk.volnahub.ui.screen.ChatsScreen +import com.cardinalnsk.volnahub.ui.screen.SettingsScreen +import com.cardinalnsk.volnahub.viewmodel.ThemeViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AppNavHost( + innerPadding: PaddingValues, + themeViewModel: ThemeViewModel = hiltViewModel() +) { + val navController = rememberNavController() + NavHost( + navController = navController, + startDestination = Screen.Login.route, + modifier = Modifier.padding(innerPadding) + ) { + composable(Screen.Login.route) { + AuthScreen( + onLoginSuccess = { + navController.navigate(Screen.Home.route) { + popUpTo(Screen.Login.route) { inclusive = true } + } + }, + onRegisterClick = { navController.navigate(Screen.Register.route) } + ) + } + composable(Screen.Register.route) { + RegisterScreen( + onRegisterSuccess = { + navController.navigate(Screen.Home.route) { + popUpTo(Screen.Register.route) { inclusive = true } + } + }, + onBackToLogin = { + navController.navigate(Screen.Login.route) { + popUpTo(Screen.Register.route) { inclusive = true } + } + } + ) + } + composable(Screen.Home.route) { + HomeNavHost(themeViewModel, navController) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun HomeNavHost(themeViewModel: ThemeViewModel, rootNavController: NavHostController) { + val navController = rememberNavController() + Scaffold( + topBar = { AppTopBar(themeViewModel) }, + bottomBar = { + NavigationBar { + val navBackStackEntry by navController.currentBackStackEntryAsState() + val currentRoute = navBackStackEntry?.destination?.route + Screen.bottomNavScreens.forEach { screen -> + NavigationBarItem( + icon = { Icon(Screen.icon(screen), contentDescription = null) }, + label = { Text(Screen.label(screen)) }, + selected = currentRoute == screen.route, + onClick = { + navController.navigate(screen.route) { + launchSingleTop = true + popUpTo(navController.graph.startDestinationId) { + saveState = true + } + restoreState = true + } + } + ) + } + } + } + ) { innerPadding -> + NavHost( + navController = navController, + startDestination = Screen.Contacts.route, + modifier = Modifier.padding(innerPadding) + ) { + composable(Screen.Contacts.route) { ContactsScreen() } + composable(Screen.Chats.route) { ChatsScreen() } + composable(Screen.Settings.route) { + SettingsScreen( + onLogout = { + rootNavController.navigate(Screen.Login.route) { + popUpTo(Screen.Home.route) { inclusive = true } + } + } + ) + } + } + } +} + + +//package com.cardinalnsk.volnahub.navigation +// +//import androidx.compose.runtime.Composable +//import androidx.navigation.compose.* +//import com.cardinalnsk.volnahub.ui.screen.* +// +// +//@Composable +//fun AppNavHost() { +// val navController = rememberNavController() +// +// NavHost(navController = navController, startDestination = Screen.Login.route) { +// composable(Screen.Login.route) { +// AuthScreen( +// onLoginSuccess = { +// navController.navigate(Screen.Home.route) { +// popUpTo(Screen.Login.route) { inclusive = true } +// } +// }, +// onRegisterClick = { +// navController.navigate(Screen.Register.route) +// } +// ) +// } +// composable(Screen.Register.route) { +// RegisterScreen( +// onRegisterSuccess = { +// navController.navigate(Screen.Home.route) { +// popUpTo(Screen.Register.route) { inclusive = true } +// } +// }, +// onBackToLogin = { +// navController.popBackStack() +// } +// ) +// } +// composable(Screen.Home.route) { +// HomeScreen() // Pass it here +// } +// } +//} +// diff --git a/app/src/main/java/com/cardinalnsk/volnahub/navigation/Screen.kt b/app/src/main/java/com/cardinalnsk/volnahub/navigation/Screen.kt new file mode 100644 index 0000000..6649a45 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/navigation/Screen.kt @@ -0,0 +1,37 @@ +package com.cardinalnsk.volnahub.navigation + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Chat +import androidx.compose.material.icons.filled.* +import androidx.compose.ui.graphics.vector.ImageVector + +sealed class Screen(val route: String) { + object Login : Screen("auth") + object Register : Screen("register") + object Home : Screen("home") + + // Internal tabs + object Contacts : Screen("contacts") + object Chats : Screen("chats") + object Settings : Screen("settings") + + companion object { + val bottomNavScreens = listOf( + Contacts, Chats, Settings + ) + + fun icon(screen: Screen): ImageVector = when (screen) { + Contacts -> Icons.Filled.Person + Chats -> Icons.AutoMirrored.Filled.Chat + Settings -> Icons.Filled.Settings + else -> Icons.Filled.Person + } + + fun label(screen: Screen): String = when (screen) { + Contacts -> "Контакты" + Chats -> "Чаты" + Settings -> "Настройки" + else -> "" + } + } +} diff --git a/app/src/main/java/com/cardinalnsk/volnahub/network/AuthApi.kt b/app/src/main/java/com/cardinalnsk/volnahub/network/AuthApi.kt new file mode 100644 index 0000000..3a89197 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/network/AuthApi.kt @@ -0,0 +1,16 @@ +package com.cardinalnsk.volnahub.network + +import com.cardinalnsk.volnahub.model.LoginRequest +import com.cardinalnsk.volnahub.model.LoginResponse +import com.cardinalnsk.volnahub.model.RegisterRequest +import com.cardinalnsk.volnahub.model.RegisterResponse +import retrofit2.Response +import retrofit2.http.* + +interface AuthApi { + @POST("/auth/login") + suspend fun login(@Body request: LoginRequest): Response + + @POST("/auth/register") + suspend fun register(@Body request: RegisterRequest): Response +} \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/network/HeaderInterceptor.kt b/app/src/main/java/com/cardinalnsk/volnahub/network/HeaderInterceptor.kt new file mode 100644 index 0000000..1eec148 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/network/HeaderInterceptor.kt @@ -0,0 +1,14 @@ +package com.cardinalnsk.volnahub.network + +import com.cardinalnsk.volnahub.BuildConfig +import okhttp3.Interceptor +import okhttp3.Response + +class HeaderInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request().newBuilder() + .addHeader("User-Agent", BuildConfig.USER_AGENT) + .build() + return chain.proceed(request) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/repository/AuthRepository.kt b/app/src/main/java/com/cardinalnsk/volnahub/repository/AuthRepository.kt new file mode 100644 index 0000000..8fde5e1 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/repository/AuthRepository.kt @@ -0,0 +1,44 @@ +package com.cardinalnsk.volnahub.repository + +import com.cardinalnsk.volnahub.model.LoginRequest +import com.cardinalnsk.volnahub.model.LoginResponse +import com.cardinalnsk.volnahub.model.RegisterRequest +import com.cardinalnsk.volnahub.model.RegisterResponse +import com.cardinalnsk.volnahub.network.* +import jakarta.inject.Inject + +class AuthRepository @Inject constructor( + private val api: AuthApi +) { + + + suspend fun login(login: String, password: String): Result { + return try { + val response = api.login(LoginRequest(login, password)) + if (response.isSuccessful) { + Result.success(response.body()!!) + } else { + Result.failure(Exception("Error: ${response.code()}")) + } + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun register( + login: String, + password: String, + invite: String + ): Result { + return try { + val response = api.register(RegisterRequest(login, password, invite)) + if (response.isSuccessful) { + Result.success(response.body()!!) + } else { + Result.failure(Exception(response.errorBody()?.string() ?: "Register error")) + } + } catch (e: Exception) { + Result.failure(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/ui/fragments/AppTopBar.kt b/app/src/main/java/com/cardinalnsk/volnahub/ui/fragments/AppTopBar.kt new file mode 100644 index 0000000..c712825 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/ui/fragments/AppTopBar.kt @@ -0,0 +1,35 @@ +package com.cardinalnsk.volnahub.ui.fragments + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.font.FontWeight +import androidx.hilt.navigation.compose.hiltViewModel +import com.cardinalnsk.volnahub.viewmodel.ThemeViewModel + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AppTopBar( + themeViewModel: ThemeViewModel = hiltViewModel() +) { + val isDark = themeViewModel.isDarkTheme.value + CenterAlignedTopAppBar( + title = { + Text( + text = "VolnaHub", + fontWeight = FontWeight.Bold, + style = MaterialTheme.typography.titleLarge + ) + }, + actions = { + IconButton(onClick = { themeViewModel.toggleTheme() }) { + Icon( + imageVector = if (isDark) Icons.Filled.LightMode else Icons.Filled.DarkMode, + contentDescription = if (isDark) "Светлая тема" else "Тёмная тема" + ) + } + } + ) +} diff --git a/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/AuthScreen.kt b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/AuthScreen.kt new file mode 100644 index 0000000..248ba92 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/AuthScreen.kt @@ -0,0 +1,111 @@ +package com.cardinalnsk.volnahub.ui.screen + +import androidx.compose.animation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.* +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.* +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.cardinalnsk.volnahub.R +import com.cardinalnsk.volnahub.viewmodel.AuthViewModel + + +@Composable +fun AuthScreen( + onLoginSuccess: () -> Unit, + onRegisterClick: () -> Unit, +) { + val authViewModel: AuthViewModel = hiltViewModel() + + val login = authViewModel.login + val password = authViewModel.password + val isLoading = authViewModel.isLoading + val error = authViewModel.errorMessage + + var showPasswordField by remember { + mutableStateOf(false) + } + + LaunchedEffect(login, password) { + showPasswordField = (password.isNotBlank() || login.length >= 3) + } + + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(24.dp) + .imePadding() + .animateContentSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + +// Spacer(modifier = Modifier.height(64.dp)) + + Text( + stringResource(R.string.login_form_title), + style = MaterialTheme.typography.headlineSmall + ) + + Spacer(modifier = Modifier.height(16.dp)) + + OutlinedTextField( + value = login, + onValueChange = { authViewModel.login = it }, + label = { Text(stringResource(R.string.login_form_login_placeholder)) }, + singleLine = true, + modifier = Modifier.fillMaxWidth() + ) + + AnimatedVisibility( + visible = showPasswordField, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically() + ) { + Column { + Spacer(modifier = Modifier.height(8.dp)) + + OutlinedTextField( + value = password, + onValueChange = { authViewModel.password = it }, + label = { Text(stringResource(R.string.login_form_password_placeholder)) }, + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + modifier = Modifier.fillMaxWidth() + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { + authViewModel.login { onLoginSuccess() } + }, + enabled = login.length >= 3 && password.length >= 3 && !isLoading, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.login_button)) + } + + if (error.isNotEmpty()) { + Spacer(modifier = Modifier.height(8.dp)) + Text(error, color = MaterialTheme.colorScheme.error) + } + + Spacer(modifier = Modifier.height(16.dp)) + + TextButton(onClick = onRegisterClick) { + Text(stringResource(R.string.login_form_register_link)) + } + } +} diff --git a/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/ChatsScreen.kt b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/ChatsScreen.kt new file mode 100644 index 0000000..6d4f368 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/ChatsScreen.kt @@ -0,0 +1,16 @@ +package com.cardinalnsk.volnahub.ui.screen + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun ChatsScreen() { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("Чаты", style = MaterialTheme.typography.titleLarge) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/ContactScreen.kt b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/ContactScreen.kt new file mode 100644 index 0000000..ea2490f --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/ContactScreen.kt @@ -0,0 +1,16 @@ +package com.cardinalnsk.volnahub.ui.screen + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun ContactsScreen() { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("Контакты", style = MaterialTheme.typography.titleLarge) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/HomeScreen.kt b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/HomeScreen.kt new file mode 100644 index 0000000..d38ffea --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/HomeScreen.kt @@ -0,0 +1,20 @@ +package com.cardinalnsk.volnahub.ui.screen + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.* +import androidx.hilt.navigation.compose.hiltViewModel +import com.cardinalnsk.volnahub.viewmodel.ThemeViewModel + + +@Composable +fun HomeScreen() { + val themeViewModel: ThemeViewModel = hiltViewModel() + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text("Добро пожаловать!", style = MaterialTheme.typography.headlineMedium) + } +} + + + diff --git a/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/RegisterScreen.kt b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/RegisterScreen.kt new file mode 100644 index 0000000..d8cd0e3 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/RegisterScreen.kt @@ -0,0 +1,222 @@ +package com.cardinalnsk.volnahub.ui.screen + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.* +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.* +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.cardinalnsk.volnahub.R +import com.cardinalnsk.volnahub.viewmodel.AuthViewModel + +@Composable +fun RegisterScreen( + onRegisterSuccess: () -> Unit, + onBackToLogin: () -> Unit, +) { + val authViewModel: AuthViewModel = hiltViewModel() + val scrollState = rememberScrollState() + + Box( + modifier = Modifier + .fillMaxSize() + .imePadding() + .padding(24.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.Center) + .verticalScroll(scrollState) + .animateContentSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + stringResource(R.string.registration_form_title), + style = MaterialTheme.typography.headlineSmall + ) + + Spacer(modifier = Modifier.height(16.dp)) + + OutlinedTextField( + value = authViewModel.login, + onValueChange = { authViewModel.login = it }, + label = { Text(stringResource(R.string.registration_form_login_placeholder)) }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(8.dp)) + + OutlinedTextField( + value = authViewModel.password, + onValueChange = { authViewModel.password = it }, + label = { Text(stringResource(R.string.registration_form_password_placeholder)) }, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(8.dp)) + + OutlinedTextField( + value = authViewModel.confirmPassword, + onValueChange = { authViewModel.confirmPassword = it }, + label = { Text(stringResource(R.string.registration_form_confirm_password_placeholder)) }, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(8.dp)) + + OutlinedTextField( + value = authViewModel.invite, + onValueChange = { authViewModel.invite = it }, + label = { Text(stringResource(R.string.registration_form_inviteCode_placeholder)) }, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = { authViewModel.register { onRegisterSuccess() } }, + enabled = authViewModel.login.length >= 3 && + authViewModel.password.length >= 3 && + authViewModel.invite.isNotBlank() && + !authViewModel.isLoading, + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(R.string.registration_form_submit_button)) + } + + if (authViewModel.errorMessage.isNotEmpty()) { + Spacer(modifier = Modifier.height(8.dp)) + Text(authViewModel.errorMessage, color = MaterialTheme.colorScheme.error) + } + + Spacer(modifier = Modifier.height(16.dp)) + + TextButton(onClick = onBackToLogin) { + Text(stringResource(R.string.registration_form_link_to_login)) + } + + Spacer(modifier = Modifier.height(24.dp)) + } + } +} + + + +//@Composable +//fun RegisterScreen( +// onRegisterSuccess: () -> Unit, +// onBackToLogin: () -> Unit, +//) { +// val authViewModel: AuthViewModel = hiltViewModel() +// +// val login = authViewModel.login +// val password = authViewModel.password +// val confirmPassword = authViewModel.confirmPassword +// val invite = authViewModel.invite +// val isLoading = authViewModel.isLoading +// val error = authViewModel.errorMessage +// +// val scrollState = rememberScrollState() +// Column( +// modifier = Modifier +// .fillMaxSize() +// .verticalScroll(scrollState) +// .padding(24.dp) +// .imePadding() +// .animateContentSize(), +// verticalArrangement = Arrangement.Center, +// horizontalAlignment = Alignment.CenterHorizontally +// ) { +// Text( +// stringResource(R.string.registration_form_title), +// style = MaterialTheme.typography.headlineSmall +// ) +// Spacer(modifier = Modifier.height(16.dp)) +// +// OutlinedTextField( +// value = login, +// onValueChange = { authViewModel.login = it }, +// label = { Text(stringResource(R.string.registration_form_login_placeholder)) }, +// singleLine = true, +// modifier = Modifier.fillMaxWidth() +// ) +// +// Spacer(modifier = Modifier.height(8.dp)) +// +// OutlinedTextField( +// value = password, +// onValueChange = { authViewModel.password = it }, +// label = { Text(stringResource(R.string.registration_form_password_placeholder)) }, +// singleLine = true, +// visualTransformation = PasswordVisualTransformation(), +// keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), +// modifier = Modifier.fillMaxWidth() +// ) +// +// Spacer(modifier = Modifier.height(8.dp)) +// +// OutlinedTextField( +// value = confirmPassword, +// onValueChange = { authViewModel.confirmPassword = it }, +// label = { Text(stringResource(R.string.registration_form_confirm_password_placeholder)) }, +// singleLine = true, +// visualTransformation = PasswordVisualTransformation(), +// keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), +// modifier = Modifier.fillMaxWidth() +// ) +// +// Spacer(modifier = Modifier.height(8.dp)) +// +// OutlinedTextField( +// value = invite, +// onValueChange = { authViewModel.invite = it }, +// label = { Text(stringResource(R.string.registration_form_inviteCode_placeholder)) }, +// singleLine = true, +// modifier = Modifier.fillMaxWidth() +// ) +// +// Spacer(modifier = Modifier.height(16.dp)) +// +// Button( +// onClick = { +// authViewModel.register { +// onRegisterSuccess() +// } +// }, +// enabled = login.length >= 3 && password.length >= 3 && invite.isNotBlank() && !isLoading, +// modifier = Modifier.fillMaxWidth() +// ) { +// Text(stringResource(R.string.registration_form_submit_button)) +// } +// +// if (authViewModel.passwordError != null) { +// Text(text = authViewModel.passwordError!!, color = Color.Red) +// } +// +// if (error.isNotEmpty()) { +// Spacer(modifier = Modifier.height(8.dp)) +// Text(error, color = MaterialTheme.colorScheme.error) +// } +// +// Spacer(modifier = Modifier.height(16.dp)) +// +// TextButton(onClick = onBackToLogin) { +// Text(stringResource(R.string.registration_form_link_to_login)) +// } +// +// Spacer(modifier = Modifier.height(8.dp)) +// +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/SettingsScreen.kt b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/SettingsScreen.kt new file mode 100644 index 0000000..695f1e1 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/ui/screen/SettingsScreen.kt @@ -0,0 +1,15 @@ +package com.cardinalnsk.volnahub.ui.screen + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.* + +@Composable +fun SettingsScreen(onLogout: () -> Unit) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Button(onClick = onLogout) { + Text("Выйти из аккаунта") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/ui/theme/Color.kt b/app/src/main/java/com/cardinalnsk/volnahub/ui/theme/Color.kt new file mode 100644 index 0000000..df0ddad --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/ui/theme/Color.kt @@ -0,0 +1,13 @@ +package com.cardinalnsk.volnahub.ui.theme + +import androidx.compose.ui.graphics.Color + +// Light Theme +val BlueLightPrimary = Color(0xFF1976D2) // синий +val BlueLightSecondary = Color(0xFF90CAF9) // светло-синий +val BlueLightTertiary = Color(0xFF64B5F6) + +// Dark Theme +val BlueDarkPrimary = Color(0xFF0D47A1) +val BlueDarkSecondary = Color(0xFF42A5F5) +val BlueDarkTertiary = Color(0xFF2196F3) diff --git a/app/src/main/java/com/cardinalnsk/volnahub/ui/theme/Theme.kt b/app/src/main/java/com/cardinalnsk/volnahub/ui/theme/Theme.kt new file mode 100644 index 0000000..f10e40b --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/ui/theme/Theme.kt @@ -0,0 +1,46 @@ +package com.cardinalnsk.volnahub.ui.theme + +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = BlueDarkPrimary, + secondary = BlueDarkSecondary, + tertiary = BlueDarkTertiary +) + +private val LightColorScheme = lightColorScheme( + primary = BlueLightPrimary, + secondary = BlueLightSecondary, + tertiary = BlueLightTertiary +) + +@Composable +fun MyApplicationTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/app/src/main/java/com/cardinalnsk/volnahub/ui/theme/Type.kt b/app/src/main/java/com/cardinalnsk/volnahub/ui/theme/Type.kt new file mode 100644 index 0000000..ee27e79 --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/ui/theme/Type.kt @@ -0,0 +1,17 @@ +package com.cardinalnsk.volnahub.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) +) diff --git a/app/src/main/java/com/cardinalnsk/volnahub/viewmodel/AuthViewModel.kt b/app/src/main/java/com/cardinalnsk/volnahub/viewmodel/AuthViewModel.kt new file mode 100644 index 0000000..acb093f --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/viewmodel/AuthViewModel.kt @@ -0,0 +1,83 @@ +package com.cardinalnsk.volnahub.viewmodel + +import androidx.compose.runtime.* +import androidx.lifecycle.* +import com.cardinalnsk.volnahub.repository.AuthRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import jakarta.inject.Inject +import kotlinx.coroutines.launch + + +@HiltViewModel +class AuthViewModel @Inject constructor(private val repository: AuthRepository) : ViewModel() { + var login by mutableStateOf("") + var password by mutableStateOf("") + var confirmPassword by mutableStateOf("") + var invite by mutableStateOf("") + var isLoading by mutableStateOf(false) + var errorMessage by mutableStateOf("") + var token by mutableStateOf("") + var passwordError by mutableStateOf(null) + + + fun login(onSuccess: (String) -> Unit) { + viewModelScope.launch { + isLoading = true + errorMessage = "" + val result = repository.login(login, password) + isLoading = false + + result.onSuccess { + token = it.accessToken + onSuccess(token) + }.onFailure { + errorMessage = it.message ?: "Login error" + } + } + } + + fun register(onSuccess: (String) -> Unit) { + if (!validatePassword()) { + return + } + viewModelScope.launch { + isLoading = true + errorMessage = "" + val result = repository.register(login, password, invite) + isLoading = false + + result.onSuccess { + onSuccess("") + }.onFailure { + errorMessage = it.message ?: "Registration error" + } + + } + } + + + fun validatePassword(): Boolean { + return when { + password.length < 6 -> { + passwordError = "Пароль должен быть не менее 6 символов" + false + } + + !password.any { it.isDigit() } -> { + passwordError = "Пароль должен содержать хотя бы одну цифру" + false + } + + password != confirmPassword -> { + passwordError = "Пароли не совпадают" + false + } + + else -> { + passwordError = null + true + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/cardinalnsk/volnahub/viewmodel/ThemeViewModel.kt b/app/src/main/java/com/cardinalnsk/volnahub/viewmodel/ThemeViewModel.kt new file mode 100644 index 0000000..d0b49ca --- /dev/null +++ b/app/src/main/java/com/cardinalnsk/volnahub/viewmodel/ThemeViewModel.kt @@ -0,0 +1,22 @@ +package com.cardinalnsk.volnahub.viewmodel + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import jakarta.inject.Inject +import androidx.compose.runtime.State + + +@HiltViewModel +class ThemeViewModel @Inject constructor( + // Можно сюда репозиторий с DataStore, если хочешь сохранять тему навсегда +) : ViewModel() { + + // Простое хранение (или через State/DataStore) + private val _isDarkTheme = mutableStateOf(false) + val isDarkTheme: State = _isDarkTheme + + fun toggleTheme() { + _isDarkTheme.value = !_isDarkTheme.value + } +} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml new file mode 100644 index 0000000..fb7e811 --- /dev/null +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -0,0 +1,16 @@ + + + VolnaHub + Пароль + Логин + Войти + Авторизация + Нет аккаунта? Зарегистрироваться + Регистрация + Логин + Пароль + Подтвердите пароль + Инвайт-код + Зарегистрироваться + Уже есть аккаунт? Войти + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..64cc1d3 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,15 @@ + + VolnaHub + Login + Password + Login + Sign-in + No account? Sign up + Registration + Login + Password + Confirm password + Invite-code + Registration + Account already exists? Sign in + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..e48770a --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +