diff --git a/.idea/.gitignore b/.idea/.gitignore index 26d3352..eaf91e2 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,3 +1,3 @@ -# Default ignored files -/shelf/ -/workspace.xml +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml index 4a53bee..d58d49b 100644 --- a/.idea/AndroidProjectSystem.xml +++ b/.idea/AndroidProjectSystem.xml @@ -1,6 +1,6 @@ - - - - + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml index b86273d..8fabff5 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 5a339e5..7603f3e 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -1,18 +1,18 @@ - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 0000000..1a28b86 --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,8 @@ + + + + ошибка + сети + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 639c779..05a77f7 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -1,19 +1,19 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..7061a0d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,61 @@ + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml index f8051a6..48052b2 100644 --- a/.idea/migrations.xml +++ b/.idea/migrations.xml @@ -1,10 +1,10 @@ - - - - - + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 29e60fc..ae9d479 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml index 16660f1..5bd6771 100644 --- a/.idea/runConfigurations.xml +++ b/.idea/runConfigurations.xml @@ -1,17 +1,17 @@ - - - - - + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..9661ac7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a4703f8..fbc8c86 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,40 +1,27 @@ plugins { alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.hilt) + alias(libs.plugins.kapt) } android { - packaging { - jniLibs { - useLegacyPackaging = false - } - } - // Для компиляции с поддержкой 16 KB - defaultConfig { - externalNativeBuild { - cmake { - // ИСПРАВЛЕНИЕ: здесь нужно использовать аргументы и скобки/равенство - cppFlags("-Wl,-z,max-page-size=16384") - } - } - } - - namespace = "com.example.myserver" - compileSdk { - version = release(36) - } + compileSdk = 35 defaultConfig { applicationId = "com.example.myserver" minSdk = 29 - targetSdk = 36 + targetSdk = 35 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + externalNativeBuild { cmake { - cppFlags += "" + arguments("-DANDROID_STL=c++_shared") + cppFlags("-Wl,-z,max-page-size=16384") } } } @@ -48,10 +35,31 @@ android { ) } } + compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + // Compose и Hilt лучше работают на Java 17 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } + + kotlinOptions { + jvmTarget = "17" + } + + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" // Соответствует Kotlin 1.9.0 + } + + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1,androidx.compose.material3_material3.version}" + } + } + externalNativeBuild { cmake { path = file("src/main/cpp/CMakeLists.txt") @@ -61,18 +69,42 @@ android { } dependencies { - implementation("com.squareup.retrofit2:retrofit:2.9.0") - implementation("com.squareup.retrofit2:converter-gson:2.9.0") + // 1. Сначала объявляем BOM (один раз в самом начале) + val composeBom = platform("androidx.compose:compose-bom:2024.02.01") + implementation(composeBom) + androidTestImplementation(composeBom) + + // 2. Jetpack Compose (версии берутся из BOM) + implementation(libs.compose.ui) + implementation(libs.compose.ui.graphics) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.activity.compose) + implementation(libs.lifecycle.viewmodel.compose) + implementation(libs.foundation) + + // 3. Material 3 (обязательно для PullToRefreshBox) + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.material3:material3-window-size-class") + implementation("androidx.compose.material:material-icons-extended") + + // 4. Сеть (Retrofit) + implementation(libs.retrofit) + implementation(libs.retrofit.gson) + implementation(libs.okhttp.logging) + + // 5. Dagger Hilt + implementation(libs.hilt.android) + kapt(libs.hilt.compiler) + implementation("androidx.hilt:hilt-navigation-compose:1.1.0") + + // 6. Остальное implementation(libs.appcompat) implementation(libs.material) - implementation(libs.activity) - implementation(libs.constraintlayout) - implementation(libs.recyclerview) - implementation(libs.cardview) - implementation(libs.viewpager2) - implementation(libs.legacy.support.v4) - implementation(libs.mediarouter) + implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1") + + // Тесты и отладка testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) -} \ No newline at end of file + debugImplementation(libs.compose.ui.tooling) +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fcee2f8..1956d1c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,49 +1,49 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/cpp/myserver.cpp b/app/src/main/cpp/myserver.cpp index c09b4bb..08874f3 100644 --- a/app/src/main/cpp/myserver.cpp +++ b/app/src/main/cpp/myserver.cpp @@ -4,7 +4,7 @@ // // For instance, // -// In MainActivity.java: +// In MainActivity.kt: // static { // System.loadLibrary("myserver"); // } diff --git a/app/src/main/java/com/example/myserver/AuthManager.java b/app/src/main/java/com/example/myserver/AuthManager.java deleted file mode 100644 index b55bc96..0000000 --- a/app/src/main/java/com/example/myserver/AuthManager.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.example.myserver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import android.widget.Toast; - -import androidx.annotation.NonNull; - - -public class AuthManager { - private final Context context; - - public AuthManager(Context context) { - this.context = context; - } - - public void authUser(String realname, String password, String email, boolean isRegistration) { - UserRequest request = new UserRequest(realname, password, email); - MyApiService api = RetrofitClient.getApiService(); - - // Выбираем метод в зависимости от флага - Call call = isRegistration ? api.register(request) : api.login(request); - - call.enqueue(new Callback<>() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response response) { - if (response.isSuccessful() && response.body() != null) { - saveUserToLocal(response.body()); - - // ДОБАВЬ ЭТОТ КОД ДЛЯ ПЕРЕХОДА: - Intent intent = new Intent(context, LoadScreen.class); // Укажи свою главную активность - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - context.startActivity(intent); - - Toast.makeText(context, "Успешный вход!", Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(context, "Ошибка сервера: " + response.code(), Toast.LENGTH_SHORT).show(); - } - } - - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - // ЕСЛИ СЕРВЕР ВООБЩЕ НЕ ДОСТУПЕН (не тот IP или выключен) - Toast.makeText(context, "Сеть недоступна: " + t.getMessage(), Toast.LENGTH_LONG).show(); - } - - }); - } - - private void saveUserToLocal(UserResponse user) { - SharedPreferences prefs = context.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE); - prefs.edit() - .putBoolean("isLoggedIn", true) - .putString("realname", user.getRealname()) - .apply(); - } -} diff --git a/app/src/main/java/com/example/myserver/AuthViewModel.kt b/app/src/main/java/com/example/myserver/AuthViewModel.kt new file mode 100644 index 0000000..7e46ae7 --- /dev/null +++ b/app/src/main/java/com/example/myserver/AuthViewModel.kt @@ -0,0 +1,48 @@ +package com.example.myserver + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class AuthViewModel @Inject constructor( + private val apiService: MyApiService +) : ViewModel() { + + // Состояние экрана (загрузка, успех, ошибка) + private val _authState = MutableStateFlow(AuthState.Idle) + val authState = _authState.asStateFlow() + + fun authUser(request: UserRequest, isRegistration: Boolean) { + viewModelScope.launch { + _authState.value = AuthState.Loading + try { + val response = if (isRegistration) { + apiService.register(request) + } else { + apiService.login(request) + } + + if (response.isSuccessful && response.body() != null) { + _authState.value = AuthState.Success(response.body()!!) + } else { + _authState.value = AuthState.Error("Ошибка: ${response.code()}") + } + } catch (e: Exception) { + _authState.value = AuthState.Error("Сеть недоступна: ${e.message}") + } + } + } +} + +// Описываем возможные состояния +sealed class AuthState { + object Idle : AuthState() + object Loading : AuthState() + data class Success(val user: UserResponse) : AuthState() + data class Error(val message: String) : AuthState() +} diff --git a/app/src/main/java/com/example/myserver/InformationActivity.java b/app/src/main/java/com/example/myserver/InformationActivity.java deleted file mode 100644 index c71e623..0000000 --- a/app/src/main/java/com/example/myserver/InformationActivity.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.example.myserver; - -import android.os.Bundle; - -import androidx.activity.EdgeToEdge; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; - -public class InformationActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_information); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); - String name = getSharedPreferences("AppPrefs", MODE_PRIVATE).getString("username", "Гость"); - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/InformationActivity.kt b/app/src/main/java/com/example/myserver/InformationActivity.kt new file mode 100644 index 0000000..2676813 --- /dev/null +++ b/app/src/main/java/com/example/myserver/InformationActivity.kt @@ -0,0 +1,115 @@ +package com.example.myserver + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class InformationActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE) + val username = prefs.getString("username", "Гость") ?: "Гость" + + setContent { + MaterialTheme { + InformationScreen( + username = username, + onBackClick = { finish() } + ) + } + } + } +} + +@Composable +fun InformationScreen( + username: String, + onBackClick: () -> Unit +) { + Surface( + modifier = Modifier.fillMaxSize(), + color = Color(0xFF3E4B54) + ) { + Column( + modifier = Modifier.fillMaxSize() + ) { + // Верхняя панель + InformationTopBar(onBackClick = onBackClick) + + // Контентная часть - исправлено! + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f), // ← Правильное использование weight + contentAlignment = Alignment.Center + ) { + InformationContent(username = username) + } + } + } +} + +@Composable +fun InformationTopBar( + onBackClick: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton(onClick = onBackClick) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = "Назад", + tint = Color.White + ) + } + + Text( + text = "Информация", + color = Color.White, + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.padding(start = 8.dp) + ) + } +} + +@Composable +fun InformationContent( + username: String +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = "Добро пожаловать, $username!", + color = Color.White, + style = MaterialTheme.typography.headlineMedium + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "Экран находится в разработке", + color = Color.White.copy(alpha = 0.7f), + style = MaterialTheme.typography.bodyLarge + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/LoadScreen.java b/app/src/main/java/com/example/myserver/LoadScreen.java deleted file mode 100644 index cd83842..0000000 --- a/app/src/main/java/com/example/myserver/LoadScreen.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.example.myserver; - -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.Handler; -import android.view.View; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.widget.TextView; - -import androidx.appcompat.app.AppCompatActivity; - -public class LoadScreen extends AppCompatActivity { - private TextView textViewWelcome; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_load_screen); - textViewWelcome = findViewById(R.id.textWelcome); - - // Получаем переданное имя - SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE); - String realName = prefs.getString("realname", null); - - if (realName != null) { - textViewWelcome.setText("Добро пожаловать, " + realName + "!"); - } else { - textViewWelcome.setText("Добро пожаловать!"); - } - - - // Запускаем анимацию для приветствия - animateWelcomeMessage(); - } - private void animateWelcomeMessage() { - if (textViewWelcome != null) { - // 1. Появление текста (1 секунда) - AlphaAnimation fadeIn = new AlphaAnimation(0.0f, 1.0f); - fadeIn.setDuration(1000); - textViewWelcome.startAnimation(fadeIn); - - // 2. Ждем, пока пользователь почитает (например, 2 секунды), и запускаем исчезновение - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - AlphaAnimation fadeOut = new AlphaAnimation(1.0f, 0.0f); - fadeOut.setDuration(1000); // время затухания - - fadeOut.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) {} - - @Override - public void onAnimationEnd(Animation animation) { - textViewWelcome.setVisibility(View.GONE); - - // ПЕРЕХОД ДЕЛАЕМ ТУТ (когда всё исчезло) - Intent intent = new Intent(LoadScreen.this, MainActivity2.class); - startActivity(intent); - finish(); - } - - @Override - public void onAnimationRepeat(Animation animation) {} - }); - - textViewWelcome.startAnimation(fadeOut); - } - }, 2000); - } - } - -} diff --git a/app/src/main/java/com/example/myserver/LoadScreen.kt b/app/src/main/java/com/example/myserver/LoadScreen.kt new file mode 100644 index 0000000..9eff281 --- /dev/null +++ b/app/src/main/java/com/example/myserver/LoadScreen.kt @@ -0,0 +1,131 @@ +package com.example.myserver + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.animation.core.* +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +@AndroidEntryPoint +class LoadScreen : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + // Важно: MaterialTheme должен быть корневым + MaterialTheme { + LoadScreenContent( + onNavigationToMain = { + startActivity(Intent(this, MainActivity::class.java)) + finish() + } + ) + } + } + } +} + +@Composable +fun LoadScreenContent( + onNavigationToMain: () -> Unit +) { + val context = LocalContext.current + val prefs = remember { context.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE) } + val realName by remember { + derivedStateOf { prefs.getString("realname", "") ?: "" } + } + + // Анимация для текста приветствия + val alpha = remember { Animatable(0f) } + val coroutineScope = rememberCoroutineScope() + + // Эффект для навигации и анимации + LaunchedEffect(Unit) { + // Появление текста + launch { + alpha.animateTo(1f, animationSpec = tween(500)) + } + + // Задержка перед переходом + delay(2000) + + // Исчезновение текста + alpha.animateTo(0f, animationSpec = tween(500)) + + // Переход на главный экран + onNavigationToMain() + } + + // Основной контейнер + Surface( + modifier = Modifier.fillMaxSize(), + color = Color(0xFF3E4B54) // Тот же цвет фона + ) { + Box( + modifier = Modifier.fillMaxSize() + ) { + // Центральная часть: Индикатор и текст "Загрузка" + Column( + modifier = Modifier.align(Alignment.Center), + horizontalAlignment = Alignment.CenterHorizontally + ) { + CircularProgressIndicator( + color = Color.White, + strokeWidth = 4.dp, + modifier = Modifier.size(48.dp) // Добавил фиксированный размер для красоты + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = "Загрузка...", + color = Color(0xFF888888), + fontSize = 16.sp + ) + } + + // Нижняя часть: Приветствие с анимацией + Text( + text = if (realName.isNotEmpty()) { + "Добро пожаловать,\n$realName!" + } else { + "Добро пожаловать!" + }, + color = Color.White, + fontSize = 20.sp, + textAlign = TextAlign.Center, + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 50.dp) + .alpha(alpha.value) + ) + } + } +} + +// Альтернативный вариант с использованием AnimatedVisibility (более "compose-way") +@Composable +fun AlternativeLoadScreenContent( + onNavigationToMain: () -> Unit +) { + // Этот вариант можно использовать вместо ручного управления alpha + // Он более декларативный, но требует больше кода +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/LogActivity.kt b/app/src/main/java/com/example/myserver/LogActivity.kt new file mode 100644 index 0000000..50b471a --- /dev/null +++ b/app/src/main/java/com/example/myserver/LogActivity.kt @@ -0,0 +1,357 @@ +package com.example.myserver + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.edit +import androidx.hilt.navigation.compose.hiltViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class LogActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE) + if (prefs.getBoolean("isLoggedIn", false)) { + startActivity(Intent(this, MainActivity::class.java)) + finish() + return + } + + setContent { + MaterialTheme { + LoginScreen() + } + } + } +} + +@Composable +fun LoginScreen(viewModel: AuthViewModel = hiltViewModel()) { + val context = LocalContext.current + val authState by viewModel.authState.collectAsState() + + var name by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var warningText by remember { mutableStateOf("") } + + // Обработка состояния авторизации + LaunchedEffect(authState) { + when (authState) { + is AuthState.Success -> { + context.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE).edit { + putBoolean("isLoggedIn", true) + putString("realname", name) + } + context.startActivity(Intent(context, MainActivity::class.java)) + (context as LogActivity).finish() + } + is AuthState.Error -> { + warningText = (authState as AuthState.Error).message + } + else -> { /* Loading or Idle */ } + } + } + + LoginScreenContent( + name = name, + onNameChange = { + name = it + warningText = "" // Сбрасываем ошибку при вводе + }, + password = password, + onPasswordChange = { + password = it + warningText = "" // Сбрасываем ошибку при вводе + }, + warningText = warningText, + isLoading = authState is AuthState.Loading, + onLoginClick = { + if (validateFields(name, password, context)) { + viewModel.authUser(UserRequest(name, password), isRegistration = false) + } + }, + onRegisterClick = { + context.startActivity(Intent(context, RegActivity::class.java)) + (context as LogActivity).finish() + } + ) +} + +@Composable +fun LoginScreenContent( + name: String, + onNameChange: (String) -> Unit, + password: String, + onPasswordChange: (String) -> Unit, + warningText: String, + isLoading: Boolean, + onLoginClick: () -> Unit, + onRegisterClick: () -> Unit +) { + // ScrollView аналог + Column( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFF3E4B54)) + .verticalScroll(rememberScrollState()) + ) { + // LinearLayout аналог с padding="16dp" + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + // TextView3 - Заголовок + Text( + text = "Авторизация", + color = Color.White, + fontSize = 30.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + textAlign = TextAlign.Center + ) + + // ImageView - Логотип + Image( + painter = painterResource(id = R.drawable.img5), + contentDescription = "logo", + modifier = Modifier + .padding(top = 20.dp) + .size(132.dp, 124.dp) + ) + + // TextView - Метка "Имя пользователя" + Text( + text = "Имя пользователя", + color = Color.White, + fontSize = 20.sp, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + textAlign = TextAlign.Center + ) + + // EditText - Поле ввода имени + // Используем Card для имитации @drawable/player_card_bg + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 50.dp) + .padding(top = 8.dp), + shape = RoundedCornerShape(15.dp), + colors = CardDefaults.cardColors( + containerColor = Color.Transparent + ), + border = ButtonDefaults.outlinedButtonBorder + ) { + BasicTextField( + value = name, + onValueChange = onNameChange, + textStyle = TextStyle( + color = Color.White, + fontSize = 16.sp + ), + singleLine = true, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + decorationBox = { innerTextField -> + Box( + modifier = Modifier + .fillMaxWidth() + .background(Color.Transparent) + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + if (name.isEmpty()) { + Text( + text = "Введите имя", + color = Color(0xFF666666), + fontSize = 16.sp, + fontStyle = FontStyle.Italic + ) + } + innerTextField() + } + }, + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(15.dp)) + .background(Color.Transparent) + ) + } + + // TextView2 - Метка "Пароль" + Text( + text = "Пароль", + color = Color.White, + fontSize = 20.sp, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + textAlign = TextAlign.Center + ) + + // EditText - Поле ввода пароля + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 50.dp) + .padding(top = 8.dp), + shape = RoundedCornerShape(15.dp), + colors = CardDefaults.cardColors( + containerColor = Color.Transparent + ), + border = ButtonDefaults.outlinedButtonBorder + ) { + BasicTextField( + value = password, + onValueChange = onPasswordChange, + textStyle = TextStyle( + color = Color.White, + fontSize = 16.sp + ), + singleLine = true, + visualTransformation = PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + decorationBox = { innerTextField -> + Box( + modifier = Modifier + .fillMaxWidth() + .background(Color.Transparent) + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + if (password.isEmpty()) { + Text( + text = "Введите пароль", + color = Color(0xFF666666), + fontSize = 16.sp, + fontStyle = FontStyle.Italic + ) + } + innerTextField() + } + }, + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(15.dp)) + .background(Color.Transparent) + ) + } + + // txtWarn - Текст ошибки + if (warningText.isNotEmpty()) { + Text( + text = warningText, + color = Color(0xFFFF6B6B), + fontSize = 14.sp, + modifier = Modifier + .fillMaxWidth() + .padding(top = 5.dp), + textAlign = TextAlign.Center + ) + } + + // Индикатор загрузки или кнопки + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier.padding(top = 20.dp), + color = Color.White + ) + } else { + // btnLogin - Кнопка входа + Button( + onClick = onLoginClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 50.dp) + .padding(top = 30.dp) + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF2196F3) + ), + shape = RoundedCornerShape(8.dp) + ) { + Text( + text = "Войти", + fontSize = 24.sp, + color = Color.White + ) + } + + // textView10 - Текст-подсказка + Text( + text = "Нет аккаунта? Зарегистрируйтесь!", + color = Color.White, + fontSize = 15.sp, + modifier = Modifier + .width(371.dp) + .padding(top = 16.dp), + textAlign = TextAlign.Center + ) + + // btnRegister - Кнопка регистрации + Button( + onClick = onRegisterClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 50.dp) + .padding(top = 16.dp, bottom = 20.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF4CAF50) + ), + shape = RoundedCornerShape(8.dp) + ) { + Text( + text = "Регистрация", + color = Color.White + ) + } + } + } + } +} + +// Функция валидации (добавь, если её нет) +fun validateFields(name: String, password: String, context: Context): Boolean { + return when { + name.isBlank() -> { + Toast.makeText(context, "Введите имя пользователя", Toast.LENGTH_SHORT).show() + false + } + password.isBlank() -> { + Toast.makeText(context, "Введите пароль", Toast.LENGTH_SHORT).show() + false + } + else -> true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/MainActivity.java b/app/src/main/java/com/example/myserver/MainActivity.java deleted file mode 100644 index 97a13a3..0000000 --- a/app/src/main/java/com/example/myserver/MainActivity.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.example.myserver; - -import android.os.Bundle; -import android.content.SharedPreferences; - -import android.text.TextUtils; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.activity.EdgeToEdge; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import android.content.Intent; -import android.view.View; -import android.widget.Button; - - -public class MainActivity extends AppCompatActivity { - private AuthManager authManager; - private EditText editTextName; - private EditText editTextPass; - private Button buttonSubmit; - private TextView textViewError; - - private Button buttonReg; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - // 1. Сначала проверяем сессию. Если вошел — уходим отсюда сразу. - SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE); - if (prefs.getBoolean("isLoggedIn", false)) { - startActivity(new Intent(this, LoadScreen.class)); - finish(); - return; // Обязательно выходим из метода, чтобы не грузить Layout ниже - } - - // 2. Только если НЕ залогинен, инициализируем интерфейс - EdgeToEdge.enable(this); - setContentView(R.layout.activity_main); - - // 3. Инициализируем остальное - authManager = new AuthManager(this); - editTextName = findViewById(R.id.editTextText); - editTextPass = findViewById(R.id.editTextTextPassword2); - textViewError = findViewById(R.id.txtWarn); - buttonSubmit = findViewById(R.id.btnLogin); - buttonReg = findViewById(R.id.btnRegister); - buttonSubmit.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - checkFieldAndProceed(); - } - }); - - buttonReg.setOnClickListener(v -> { - Intent intent = new Intent(MainActivity.this, RegActivity.class); - startActivity(intent); - finish(); - }); - } - - private void checkFieldAndProceed() { - String name = editTextName.getText().toString().trim(); - String password = editTextPass.getText().toString().trim(); - - if (TextUtils.isEmpty(name) || TextUtils.isEmpty(password)) { - showErrorMessage("Поля не могут быть пустыми!"); - } else { - hideErrorMessage(); - - // ВМЕСТО openSecondActivity(name) делаем запрос к серверу: - // false означает, что это попытка входа (Login) - authManager.authUser(name, password, "", false); - } - } - - - private void showErrorMessage(String message) { - if (textViewError != null) { - textViewError.setText(message); - textViewError.setAlpha(1f); - textViewError.setVisibility(View.VISIBLE); - - // Скрываем через 1 секунду - new android.os.Handler().postDelayed(new Runnable() { - @Override - public void run() { - hideErrorMessage(); - } - }, 1000); // 1000 миллисекунд = 1 секунда - } - } - - private void hideErrorMessage() { - if (textViewError != null && textViewError.getVisibility() == View.VISIBLE) { - textViewError.animate() - .alpha(0f) - .setDuration(500) // Анимация скрытия 0.5 секунды - .withEndAction(new Runnable() { - @Override - public void run() { - textViewError.setVisibility(View.GONE); - textViewError.setAlpha(1f); - } - }) - .start(); - } - } - - private void openSecondActivity(String realName) { - Intent intent = new Intent(MainActivity.this, LoadScreen.class); - intent.putExtra("USER_NAME", realName); - startActivity(intent); - } - - - - -} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/MainActivity.kt b/app/src/main/java/com/example/myserver/MainActivity.kt new file mode 100644 index 0000000..dcdc6e6 --- /dev/null +++ b/app/src/main/java/com/example/myserver/MainActivity.kt @@ -0,0 +1,357 @@ +package com.example.myserver + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.edit +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject +import kotlinx.coroutines.delay + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + + @Inject + lateinit var apiService: MyApiService + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MaterialTheme { + // Простая структура без сложных вложений + MainScreenContent( + apiService = apiService, + onLogout = { + val prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE) + prefs.edit { clear() } + startActivity(Intent(this@MainActivity, LogActivity::class.java)) + finish() + } + ) + } + } + } +} + +@Composable +fun MainScreenContent( + apiService: MyApiService, + onLogout: () -> Unit +) { + val context = LocalContext.current + var players by remember { mutableStateOf>(emptyList()) } + var isLoading by remember { mutableStateOf(true) } + + // Загружаем данные + LaunchedEffect(Unit) { + try { + val response = apiService.getPlayers() + if (response.isSuccessful) { + players = response.body() ?: emptyList() + } + } catch (_: Exception) { + Toast.makeText(context, "Ошибка сети", Toast.LENGTH_SHORT).show() + } finally { + isLoading = false + } + } + + // Простой Box с фоном + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFF3E4B54)) + ) { + // Верхняя часть с кнопками + MainHeader( + onLogout = onLogout, + context = context + ) + + // Список игроков или индикатор загрузки + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center), + color = Color.White + ) + } else { + PlayersList( + players = players, + context = context + ) + } + } +} + +@Composable +fun MainHeader( + onLogout: () -> Unit, + context: Context +) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(Color(0xFF3E4B54)) + .padding(bottom = 16.dp) + ) { + // Верхний ряд с меню и заголовком + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, top = 16.dp, end = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Кнопка меню + TopMenuButton( + modifier = Modifier.size(39.dp, 37.dp), + onLogout = onLogout + ) + + // Текст MySERVER + Text( + text = "MySERVER", + color = Color.White, + fontSize = 16.sp, + fontStyle = FontStyle.Italic, + modifier = Modifier.padding(start = 8.dp) + ) + + Spacer(modifier = Modifier.weight(1f)) + + // Изображение img21 + Image( + painter = painterResource(id = R.drawable.img21), + contentDescription = null, + modifier = Modifier + .size(width = 214.dp, height = 57.dp) + ) + } + + // Кнопки меню (Профиль, Карта мира, Информация) + MainMenuButtons( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + context = context + ) + } +} + +@Composable +fun TopMenuButton( + modifier: Modifier = Modifier, + onLogout: () -> Unit +) { + var expanded by remember { mutableStateOf(false) } + + Box { + Image( + painter = painterResource(id = R.drawable.img6), + contentDescription = "Menu", + modifier = modifier.clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { expanded = true } + ) + + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false }, + modifier = Modifier.background(Color(0xFF3E4B54)) + ) { + DropdownMenuItem( + text = { Text("Выйти", color = Color.Red) }, + onClick = { + expanded = false + onLogout() + } + ) + } + } +} + +@Composable +fun MainMenuButtons( + modifier: Modifier = Modifier, + context: Context +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.SpaceEvenly + ) { + // Кнопка Профиль + MenuButton( + iconRes = R.drawable.img19, + backgroundRes = R.drawable.img16, + label = "Профиль", + onClick = { + context.startActivity(Intent(context, YourProfile::class.java)) + } + ) + + // Кнопка Карта мира + MenuButton( + iconRes = R.drawable.img18, + backgroundRes = R.drawable.img16, + label = "Карта мира", + onClick = { + context.startActivity(Intent(context, MapActivity::class.java)) + } + ) + + // Кнопка Информация + MenuButton( + iconRes = R.drawable.img22, + backgroundRes = R.drawable.img16, + label = "Информация", + onClick = { + context.startActivity(Intent(context, InformationActivity::class.java)) + } + ) + } +} + +@Composable +fun MenuButton( + iconRes: Int, + backgroundRes: Int, + label: String, + onClick: () -> Unit +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.size(90.dp) + ) { + // Фон + Image( + painter = painterResource(id = backgroundRes), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.Fit + ) + + // Иконка + Image( + painter = painterResource(id = iconRes), + contentDescription = label, + modifier = Modifier + .size(60.dp) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { onClick() } + ) + } + + Text( + text = label, + color = Color.White, + fontSize = 12.sp, + fontWeight = FontWeight.Bold + ) + } +} + +@Composable +fun PlayersList( + players: List, + context: Context +) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(top = 210.dp) // Отступ для заголовка + .clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)) + .background(Color(0xFF2F4F4F)), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + items( + items = players // Добавляем ключ для стабильности + ) { player -> + PlayerCard( + player = player, + onClick = { + val intent = Intent(context, ProfileActivity::class.java).apply { + putExtra("PLAYER_NAME", player.realname ?: player.username) + } + context.startActivity(intent) + } + ) + } + } +} + +@Composable +fun PlayerCard( + player: UserResponse, + onClick: () -> Unit +) { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { onClick() }, + shape = RoundedCornerShape(20.dp), + border = BorderStroke(2.dp, Color.LightGray), + colors = CardDefaults.cardColors( + containerColor = Color(0xFF3E4B54) + ) + ) { + Row( + modifier = Modifier.padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column { + player.realname?.let { + Text( + text = it, + color = Color.White, + fontSize = 18.sp, + fontWeight = FontWeight.Bold + ) + } + Text( + text = "Активен", + color = Color(0xFF4CAF50), + fontSize = 12.sp + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + Text( + text = "▶", + color = Color.White, + fontSize = 20.sp + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/MainActivity2.java b/app/src/main/java/com/example/myserver/MainActivity2.java deleted file mode 100644 index 0042055..0000000 --- a/app/src/main/java/com/example/myserver/MainActivity2.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.example.myserver; - -import android.content.Intent; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import androidx.activity.EdgeToEdge; -import android.widget.PopupMenu; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; -import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; -import java.util.List; -import android.widget.Toast; -import android.util.Log; -import android.widget.ImageView; -import android.text.TextUtils; -import android.widget.EditText; -import android.widget.TextView; -import android.os.Handler; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.content.SharedPreferences; -import androidx.recyclerview.widget.RecyclerView; -import androidx.recyclerview.widget.LinearLayoutManager; - - - - - - -public class MainActivity2 extends AppCompatActivity { - private RecyclerView rvPlayers; - private PlayerAdapter adapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_main2); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); - rvPlayers = findViewById(R.id.rvPlayers); - rvPlayers.setLayoutManager(new LinearLayoutManager(this)); - - loadPlayers(); - - ImageView image = findViewById(R.id.buttonMenu); - image.setOnClickListener(v -> { - PopupMenu popup = new PopupMenu(MainActivity2.this, v); - popup.getMenuInflater().inflate(R.menu.popup_menu, popup.getMenu()); - - popup.setOnMenuItemClickListener(item -> { - if (item.getItemId() == R.id.option_2) { - //Intent intent = new Intent(MainActivity2.this, MainActivity3.class); - //startActivity(intent); - return true; - } - if (item.getItemId() == R.id.option1) { - getSharedPreferences("AppPrefs", MODE_PRIVATE).edit().clear().apply(); - startActivity(new Intent(MainActivity2.this, MainActivity.class)); - finish(); - return true; - } - - return false; - }); - popup.show(); - }); - ImageView map = findViewById(R.id.imageButtonMap); - map.setOnClickListener(v -> { - Intent intent = new Intent(MainActivity2.this, MapActivity.class); - startActivity(intent); - }); - - ImageView player = findViewById(R.id.imageButtonPlayer); - player.setOnClickListener(v -> { - Intent intent = new Intent(MainActivity2.this, MainActivity3.class); - startActivity(intent); - }); - ImageView inf = findViewById(R.id.imageButton); - inf.setOnClickListener(v -> { - Intent intent = new Intent(MainActivity2.this, InformationActivity.class); - startActivity(intent); - }); - } - private void loadPlayers() { - RetrofitClient.getApiService().getPlayers().enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (response.isSuccessful() && response.body() != null) { - adapter = new PlayerAdapter(response.body(), player -> { - // ПЕРЕХОД В ПРОФИЛЬ - Intent intent = new Intent(MainActivity2.this, ProfileActivity.class); - intent.putExtra("PLAYER_NAME", player.getRealname()); - startActivity(intent); - }); - rvPlayers.setAdapter(adapter); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - Toast.makeText(MainActivity2.this, "Ошибка списка", Toast.LENGTH_SHORT).show(); - } - }); - } - -} diff --git a/app/src/main/java/com/example/myserver/MainActivity3.java b/app/src/main/java/com/example/myserver/MainActivity3.java deleted file mode 100644 index b811da2..0000000 --- a/app/src/main/java/com/example/myserver/MainActivity3.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.example.myserver; - -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.ImageView; -import android.content.Intent; - -import androidx.activity.EdgeToEdge; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; - -public class MainActivity3 extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_main3); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); - ImageView back = findViewById(R.id.imageButton4); - back.setOnClickListener(v -> { - finish(); - }); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/MapActivity.java b/app/src/main/java/com/example/myserver/MapActivity.java deleted file mode 100644 index de92cc0..0000000 --- a/app/src/main/java/com/example/myserver/MapActivity.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.example.myserver; - -import android.content.Intent; -import android.os.Bundle; -import android.widget.ImageView; - -import androidx.activity.EdgeToEdge; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -public class MapActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_map); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); - WebView webView = findViewById(R.id.webView); - webView.getSettings().setJavaScriptEnabled(true); - webView.setWebViewClient(new WebViewClient()); - webView.loadUrl("http://quantumblocks.hopto.org:8123"); - ImageView back = findViewById(R.id.imageButton2); - back.setOnClickListener(v -> { - finish(); - }); - - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/MapActivity.kt b/app/src/main/java/com/example/myserver/MapActivity.kt new file mode 100644 index 0000000..2841fc6 --- /dev/null +++ b/app/src/main/java/com/example/myserver/MapActivity.kt @@ -0,0 +1,204 @@ +package com.example.myserver + +import android.annotation.SuppressLint +import android.os.Bundle +import android.webkit.WebResourceError +import android.webkit.WebResourceRequest +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MapActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MaterialTheme { + MapScreen( + onBackClick = { finish() } + ) + } + } + } +} + +@Composable +fun MapScreen( + onBackClick: () -> Unit +) { + // Состояние для отслеживания загрузки + var isLoading by remember { mutableStateOf(true) } + + Surface( + modifier = Modifier.fillMaxSize(), + color = Color(0xFF3E4B54) // Фирменный фон + ) { + Box( + modifier = Modifier.fillMaxSize() + ) { + // WebView на весь экран (с отступами как в XML) + WebViewContent( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 2.dp) // android:layout_marginStart="2dp" и marginEnd="2dp" + .padding(top = 76.dp) //Отступ для тулбара (примерно height ImageButton + отступы) + .padding(bottom = 16.dp), // Немного отступа снизу + onLoadingStateChange = { loading -> isLoading = loading } + ) + + // Верхняя панель (поверх WebView) + MapTopBar( + onBackClick = onBackClick, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.TopCenter) + ) + + // Индикатор загрузки + if (isLoading) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFF3E4B54).copy(alpha = 0.7f)), + contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator( + color = Color.White, + strokeWidth = 4.dp, + modifier = Modifier.size(48.dp) + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = "Загрузка карты...", + color = Color.White + ) + } + } + } + } + } +} + +@Composable +fun MapTopBar( + onBackClick: () -> Unit, + modifier: Modifier = Modifier +) { + Column( + modifier = modifier + .background(Color(0xFF3E4B54).copy(alpha = 0.9f)) // Полупрозрачный фон + .padding(top = 16.dp) + ) { + // Верхний ряд с кнопкой назад + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // Кнопка назад (ImageButton из XML с @drawable/img9) + IconButton( + onClick = onBackClick, + modifier = Modifier + .size(48.dp) + .padding(0.dp) + ) { + Icon( + painter = painterResource(id = R.drawable.img9), + contentDescription = "Назад", + tint = Color.Unspecified // Сохраняем оригинальный цвет + ) + } + + // Пустое пространство для баланса + Spacer(modifier = Modifier.weight(1f)) + } + + // Заголовок (TextView из XML) + Text( + text = "Карта режима Survival", // Исправлено "Surrvival" -> "Survival" + color = Color.White, + fontSize = 25.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(top = 4.dp) + ) + } +} + +@SuppressLint("SetJavaScriptEnabled") +@Composable +fun WebViewContent( + modifier: Modifier = Modifier, + onLoadingStateChange: (Boolean) -> Unit = {} +) { + AndroidView( + factory = { context -> + WebView(context).apply { + webViewClient = object : WebViewClient() { + override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) { + super.onReceivedError(view, request, error) + android.util.Log.e("WebView", "Error: ${error?.description}") + } + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + onLoadingStateChange(false) + } + + override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) { + super.onPageStarted(view, url, favicon) + onLoadingStateChange(true) + } + } + + settings.apply { + javaScriptEnabled = true + loadWithOverviewMode = true + useWideViewPort = true + builtInZoomControls = true + displayZoomControls = false + domStorageEnabled = true + mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW// Для современных карт + } + + // Загружаем URL карты + loadUrl("http://quantumblocks.hopto.org:8123/") + } + }, + modifier = modifier + ) +} + +// Предпросмотр для разработки +@Composable +@Preview(showBackground = true) +fun MapScreenPreview() { + MaterialTheme { + MapScreen( + onBackClick = {} + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/MyApiService.java b/app/src/main/java/com/example/myserver/MyApiService.java deleted file mode 100644 index b0bb478..0000000 --- a/app/src/main/java/com/example/myserver/MyApiService.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.myserver; -import retrofit2.Call; -import retrofit2.http.Body; -import retrofit2.http.POST; -import retrofit2.http.GET; -import java.util.List; -public interface MyApiService { - @POST("api/auth/login") - Call login(@Body UserRequest request); - - @POST("api/auth/register") - Call register(@Body UserRequest request); - - @GET("api/auth/players") // Тот же путь, что на сервере - Call> getPlayers(); - -} diff --git a/app/src/main/java/com/example/myserver/MyApiService.kt b/app/src/main/java/com/example/myserver/MyApiService.kt new file mode 100644 index 0000000..4c2655a --- /dev/null +++ b/app/src/main/java/com/example/myserver/MyApiService.kt @@ -0,0 +1,22 @@ +package com.example.myserver + +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST + +interface MyApiService { + + @POST("api/auth/login") + suspend fun login(@Body request: UserRequest): Response + + @POST("api/auth/register") + suspend fun register(@Body request: UserRequest): Response + + @GET("api/auth/players") + suspend fun getPlayers(): Response> + + @POST("api/auth/update") + suspend fun updateUser(@Body request: UserRequest): Response + +} diff --git a/app/src/main/java/com/example/myserver/MyApplication.kt b/app/src/main/java/com/example/myserver/MyApplication.kt new file mode 100644 index 0000000..7bc6de6 --- /dev/null +++ b/app/src/main/java/com/example/myserver/MyApplication.kt @@ -0,0 +1,7 @@ +package com.example.myserver + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class MyApplication : Application() diff --git a/app/src/main/java/com/example/myserver/NetworkModule.kt b/app/src/main/java/com/example/myserver/NetworkModule.kt new file mode 100644 index 0000000..a838c35 --- /dev/null +++ b/app/src/main/java/com/example/myserver/NetworkModule.kt @@ -0,0 +1,24 @@ +package com.example.myserver + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) // ИСПРАВЛЕНО: ::class вместо .class +object NetworkModule { + + @Provides + @Singleton + fun provideApiService(): MyApiService { + return Retrofit.Builder() + .baseUrl("http://quantumblocks.hopto.org:45678") // Твой URL со слэшем на конце + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(MyApiService::class.java) + } +} diff --git a/app/src/main/java/com/example/myserver/PlayerAdapter.java b/app/src/main/java/com/example/myserver/PlayerAdapter.java deleted file mode 100644 index a3a5dfa..0000000 --- a/app/src/main/java/com/example/myserver/PlayerAdapter.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.example.myserver; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; -import java.util.List; - - -public class PlayerAdapter extends RecyclerView.Adapter { - - private List playerList; - private OnPlayerClickListener listener; - - // Интерфейс для клика - public interface OnPlayerClickListener { - void onPlayerClick(UserResponse player); - } - - public PlayerAdapter(List playerList, OnPlayerClickListener listener) { - this.playerList = playerList; - this.listener = listener; - } - - @NonNull - @Override - public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_player, parent, false); - return new ViewHolder(v); - } - - @Override - public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - UserResponse player = playerList.get(position); - holder.tvName.setText(player.getRealname()); - - // Клик по прямоугольнику - holder.itemView.setOnClickListener(v -> listener.onPlayerClick(player)); - } - - @Override - public int getItemCount() { return playerList.size(); } - - public static class ViewHolder extends RecyclerView.ViewHolder { - TextView tvName; - public ViewHolder(View itemView) { - super(itemView); - tvName = itemView.findViewById(R.id.tvName); - } - } -} diff --git a/app/src/main/java/com/example/myserver/ProfileActivity.java b/app/src/main/java/com/example/myserver/ProfileActivity.java deleted file mode 100644 index 9aff5a0..0000000 --- a/app/src/main/java/com/example/myserver/ProfileActivity.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.example.myserver; - -import android.os.Bundle; -import android.widget.ImageView; - -import androidx.activity.EdgeToEdge; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import android.content.Intent; - -public class ProfileActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_profile); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); - ImageView back = findViewById(R.id.imageButton2); - back.setOnClickListener(v -> { - finish(); - }); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/ProfileActivity.kt b/app/src/main/java/com/example/myserver/ProfileActivity.kt new file mode 100644 index 0000000..1a69e09 --- /dev/null +++ b/app/src/main/java/com/example/myserver/ProfileActivity.kt @@ -0,0 +1,174 @@ +package com.example.myserver + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class ProfileActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Получаем имя игрока, которое передали из списка + val playerName = intent.getStringExtra("PLAYER_NAME") ?: "Неизвестный игрок" + + setContent { + // Важно: MaterialTheme должен быть корневым + MaterialTheme { + ViewProfileScreen( + name = playerName, + onBackClick = { finish() } + ) + } + } + } +} + +@Composable +fun ViewProfileScreen( + name: String, + onBackClick: () -> Unit +) { + Surface( + modifier = Modifier.fillMaxSize(), + color = Color(0xFF3E4B54) // Фирменный фон + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + // Верхняя панель с кнопкой назад и заголовком + ProfileTopBar( + onBackClick = onBackClick + ) + + Spacer(modifier = Modifier.height(24.dp)) + + // Заголовок (можно убрать, если он уже есть в TopBar) + Text( + text = "Профиль игрока", + color = Color.White, + fontSize = 28.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(bottom = 32.dp) + ) + + // Карточка с именем (в твоем стиле с белой обводкой) + InfoCard( + label = "Игровой ник", + value = name + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Карточка со статусом + InfoCard( + label = "Статус", + value = "В игре" + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Карточка с информацией + InfoCard( + label = "Ранг", + value = "Игрок" + ) + + // Добавим немного места внизу для красоты + Spacer(modifier = Modifier.weight(1f)) + } + } +} + +@Composable +fun ProfileTopBar( + onBackClick: () -> Unit +) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + IconButton( + onClick = onBackClick, + modifier = Modifier.size(48.dp) + ) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = "Назад", + tint = Color.White + ) + } + + Text( + text = "Информация об игроке", + color = Color.White, + fontSize = 20.sp, + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(start = 8.dp) + ) + } +} + +@Composable +fun InfoCard( + label: String, + value: String +) { + Card( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + border = BorderStroke(2.dp, Color.White), + colors = CardDefaults.cardColors( + containerColor = Color(0xFF3E4B54) + ) + ) { + Column( + modifier = Modifier.padding(20.dp) + ) { + Text( + text = label, + color = Color.LightGray, + fontSize = 12.sp + ) + + Text( + text = value, + color = Color.White, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(top = 4.dp) + ) + } + } +} + +// Предпросмотр для разработки +@Composable +@Preview(showBackground = true) +fun ViewProfileScreenPreview() { + MaterialTheme { + ViewProfileScreen( + name = "Steve", + onBackClick = {} + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/RegActivity.java b/app/src/main/java/com/example/myserver/RegActivity.java deleted file mode 100644 index 3b5951c..0000000 --- a/app/src/main/java/com/example/myserver/RegActivity.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.example.myserver; - -import android.os.Bundle; -import android.text.TextUtils; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.activity.EdgeToEdge; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import android.content.Intent; -import android.view.View; -import android.widget.Button; - -public class RegActivity extends AppCompatActivity { - private AuthManager authManager; - private EditText editTextName; - private EditText editTextPass; - private EditText editTextEmail; - private Button buttonSubmit; - private TextView textViewError; - private Button buttonReg; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_reg_activity); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); - editTextName = findViewById(R.id.editTextText); - editTextEmail = findViewById(R.id.editTextEmail); - editTextPass = findViewById(R.id.editTextTextPassword2); - textViewError = findViewById(R.id.txtWarn); - buttonSubmit = findViewById(R.id.btnRegister); - buttonSubmit.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - checkFieldAndProceed(); - } - }); - authManager = new AuthManager(this); // 2. Инициализируем - buttonReg = findViewById(R.id.btnLogin); - buttonReg.setOnClickListener(v -> { - Intent intent = new Intent(RegActivity.this, MainActivity.class); - startActivity(intent); - finish(); - }); - } - - private void checkFieldAndProceed() { - String name = editTextName.getText().toString().trim(); - String password = editTextPass.getText().toString().trim(); - String email = editTextEmail.getText().toString().trim(); - - if (TextUtils.isEmpty(name) || TextUtils.isEmpty(password) || TextUtils.isEmpty(email)) { - showErrorMessage("Поля не могут быть пустыми!"); - } else { - hideErrorMessage(); - - // 3. ВМЕСТО openSecondActivity и finish() вызываем сервер: - // true означает, что это РЕГИСТРАЦИЯ - authManager.authUser(name, password, email, true); - - // ПРИМЕЧАНИЕ: finish() делать ТУТ НЕ НУЖНО. - // AuthManager сам сделает переход и закроет экран при успешном ответе. - } - } - - private void showErrorMessage(String message) { - if (textViewError != null) { - textViewError.setText(message); - textViewError.setAlpha(1f); - textViewError.setVisibility(View.VISIBLE); - - // Скрываем через 1 секунду - new android.os.Handler().postDelayed(new Runnable() { - @Override - public void run() { - hideErrorMessage(); - } - }, 1000); // 1000 миллисекунд = 1 секунда - } - } - - private void hideErrorMessage() { - if (textViewError != null && textViewError.getVisibility() == View.VISIBLE) { - textViewError.animate() - .alpha(0f) - .setDuration(500) // Анимация скрытия 0.5 секунды - .withEndAction(new Runnable() { - @Override - public void run() { - textViewError.setVisibility(View.GONE); - textViewError.setAlpha(1f); - } - }) - .start(); - } - } - - private void openSecondActivity(String realName) { - Intent intent = new Intent(RegActivity.this, LoadScreen.class); - intent.putExtra("USER_NAME", realName); - startActivity(intent); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/RegActivity.kt b/app/src/main/java/com/example/myserver/RegActivity.kt new file mode 100644 index 0000000..15d68db --- /dev/null +++ b/app/src/main/java/com/example/myserver/RegActivity.kt @@ -0,0 +1,355 @@ +package com.example.myserver + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Patterns +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class RegActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MaterialTheme { + RegistrationScreen( + onNavigateToLogin = { + startActivity(Intent(this, MainActivity::class.java)) + finish() + } + ) + } + } + } +} + +@Composable +fun RegistrationScreen( + viewModel: AuthViewModel = viewModel(), + onNavigateToLogin: () -> Unit +) { + val context = LocalContext.current + var name by remember { mutableStateOf("") } + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var warningText by remember { mutableStateOf("") } + + val authState by viewModel.authState.collectAsState() + + // Слушатель состояния регистрации + LaunchedEffect(authState) { + when (authState) { + is AuthState.Success -> { + Toast.makeText(context, "Успешная регистрация!", Toast.LENGTH_SHORT).show() + onNavigateToLogin() + } + is AuthState.Error -> { + warningText = (authState as AuthState.Error).message + } + else -> { /* Loading or Idle */ } + } + } + + // ScrollView аналог с фоном + Column( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFF3E4B54)) // android:background="#3E4B54" + .verticalScroll(rememberScrollState()) + ) { + // LinearLayout аналог с padding + Column( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), // android:padding="16dp" + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Заголовок "Регистрация" (textView3) + Text( + text = "Регистрация", + color = Color.White, + fontSize = 30.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + textAlign = TextAlign.Center + ) + + // Метка "Придумайте имя" (textView) + Text( + text = "Придумайте имя", + color = Color.White, + fontSize = 20.sp, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + textAlign = TextAlign.Center + ) + + // Поле ввода имени (editTextText) + CustomTextField( + value = name, + onValueChange = { + name = it + warningText = "" + }, + placeholder = "Введите имя", + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 50.dp) + .padding(top = 8.dp) + ) + + // Метка "Придумайте пароль" (textView2) + Text( + text = "Придумайте пароль", + color = Color.White, + fontSize = 20.sp, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + textAlign = TextAlign.Center + ) + + // Поле ввода пароля (editTextTextPassword2) + CustomTextField( + value = password, + onValueChange = { + password = it + warningText = "" + }, + placeholder = "Введите пароль", + isPassword = true, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 50.dp) + .padding(top = 8.dp) + ) + + // Метка "E-mail" (textView9) + Text( + text = "E-mail", + color = Color.White, + fontSize = 20.sp, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), + textAlign = TextAlign.Center + ) + + // Поле ввода email (editTextEmail) + CustomTextField( + value = email, + onValueChange = { + email = it + warningText = "" + }, + placeholder = "Введите почту", + keyboardType = KeyboardType.Email, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 50.dp) + .padding(top = 8.dp) + ) + + // Текст ошибки (txtWarn) + if (warningText.isNotEmpty()) { + Text( + text = warningText, + color = Color(0xFFFF6B6B), + fontSize = 14.sp, + modifier = Modifier + .fillMaxWidth() + .padding(top = 5.dp), + textAlign = TextAlign.Center + ) + } + + if (authState is AuthState.Loading) { + CircularProgressIndicator( + modifier = Modifier.padding(top = 20.dp), + color = Color.White + ) + } else { + // Кнопка регистрации (btnRegister) + Button( + onClick = { + if (validateRegFields(name, email, password, context, + onError = { warningText = it } + )) { + viewModel.authUser( + UserRequest(username = name, password = password, email = email), + isRegistration = true + ) + } + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 50.dp) + .padding(top = 16.dp, bottom = 20.dp) + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF4CAF50) // Зеленый цвет из XML + ), + shape = RoundedCornerShape(8.dp) + ) { + Text( + text = "Зарегистрироваться", // Исправил опечатку "Зарегестрироваться" + color = Color.White, + fontSize = 16.sp + ) + } + + // Текст-подсказка (textView11) + Text( + text = "Уже есть аккаунт? Войдите!", + color = Color.White, + fontSize = 15.sp, + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + textAlign = TextAlign.Center + ) + + // Кнопка входа (btnLogin) + Button( + onClick = onNavigateToLogin, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 50.dp) + .padding(top = 16.dp, bottom = 20.dp) + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF2196F3) // Синий цвет из XML + ), + shape = RoundedCornerShape(8.dp) + ) { + Text( + text = "Войти", + color = Color.White, + fontSize = 24.sp + ) + } + } + } + } +} + +@Composable +fun CustomTextField( + value: String, + onValueChange: (String) -> Unit, + placeholder: String, + modifier: Modifier = Modifier, + isPassword: Boolean = false, + keyboardType: KeyboardType = KeyboardType.Text +) { + Card( + modifier = modifier, + shape = RoundedCornerShape(15.dp), + colors = CardDefaults.cardColors( + containerColor = Color.Transparent + ), + border = ButtonDefaults.outlinedButtonBorder + ) { + BasicTextField( + value = value, + onValueChange = onValueChange, + textStyle = TextStyle( + color = Color.White, + fontSize = 16.sp + ), + singleLine = true, + visualTransformation = if (isPassword) PasswordVisualTransformation() else VisualTransformation.None, + keyboardOptions = KeyboardOptions(keyboardType = keyboardType), + decorationBox = { innerTextField -> + Box( + modifier = Modifier + .fillMaxWidth() + .background(Color.Transparent) + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + if (value.isEmpty()) { + Text( + text = placeholder, + color = Color(0xFF666666), // android:textColorHint="#666666" + fontSize = 16.sp, + fontStyle = FontStyle.Italic // android:textStyle="italic" + ) + } + innerTextField() + } + }, + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(15.dp)) + .background(Color.Transparent) + ) + } +} + +private fun validateRegFields( + name: String, + email: String, + pass: String, + context: Context, + onError: (String) -> Unit +): Boolean { + if (name.isEmpty() || pass.isEmpty() || email.isEmpty()) { + onError("Заполните все поля!") + return false + } + if (name.length !in 3..16) { + onError("Имя должно быть от 3 до 16 символов") + return false + } + if (pass.length < 8) { + onError("Пароль должен быть минимум 8 символов") + return false + } + if (!Patterns.EMAIL_ADDRESS.matcher(email).matches()) { + onError("Некорректный Email") + return false + } + return true +} + +// Предпросмотр для разработки +@Composable +@Preview(showBackground = true) +fun RegistrationScreenPreview() { + MaterialTheme { + RegistrationScreen( + onNavigateToLogin = {} + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myserver/RetrofitClient.java b/app/src/main/java/com/example/myserver/RetrofitClient.java deleted file mode 100644 index eaa6a5f..0000000 --- a/app/src/main/java/com/example/myserver/RetrofitClient.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.example.myserver; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class RetrofitClient { - private static Retrofit retrofit = null; - private static final String BASE_URL = "http://quantumblocks.hopto.org:45678/"; - - public static MyApiService getApiService() { - if (retrofit == null) { - retrofit = new Retrofit.Builder() - .baseUrl(BASE_URL) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - } - return retrofit.create(MyApiService.class); - } -} diff --git a/app/src/main/java/com/example/myserver/UserModel.kt b/app/src/main/java/com/example/myserver/UserModel.kt new file mode 100644 index 0000000..23dbdab --- /dev/null +++ b/app/src/main/java/com/example/myserver/UserModel.kt @@ -0,0 +1,18 @@ +package com.example.myserver + +import com.google.gson.annotations.SerializedName + +// Модель для отправки данных (Login/Register) +data class UserRequest( + val username: String, + val password: String, + val email: String? = null // Опционально для регистрации +) + +// Модель для получения данных от сервера +data class UserResponse( + val id: Long, + val username: String, + val realname: String? = null, + val email: String? = null +) diff --git a/app/src/main/java/com/example/myserver/UserRequest.java b/app/src/main/java/com/example/myserver/UserRequest.java deleted file mode 100644 index a770688..0000000 --- a/app/src/main/java/com/example/myserver/UserRequest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.example.myserver; - -public class UserRequest { - private String realname; - private String password; - private String email; - - public UserRequest(String realname, String password, String email) { - this.realname = realname; - this.password = password; - this.email = email; - } - - public String getRealname() { return realname; } - public void setRealname(String realname) { this.realname = realname; } - public String getPassword() { return password; } - public void setPassword(String password) { this.password = password; } - public String getEmail() { return email; } - public void setEmail(String email) { this.email = email; } -} diff --git a/app/src/main/java/com/example/myserver/UserResponse.java b/app/src/main/java/com/example/myserver/UserResponse.java deleted file mode 100644 index c2571d5..0000000 --- a/app/src/main/java/com/example/myserver/UserResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.myserver; - - -public class UserResponse { - private Long id; - private String realname; - - public UserResponse() {} - - public Long getId() { return id; } - public String getRealname() { return realname; } - public void setRealname(String realname) { this.realname = realname; } -} diff --git a/app/src/main/java/com/example/myserver/YourProfile.kt b/app/src/main/java/com/example/myserver/YourProfile.kt new file mode 100644 index 0000000..0aed1d4 --- /dev/null +++ b/app/src/main/java/com/example/myserver/YourProfile.kt @@ -0,0 +1,191 @@ +package com.example.myserver + +import android.content.Context +import android.os.Bundle +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.edit +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class YourProfile : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MaterialTheme { + ProfileScreen( + onBackClick = { finish() } + ) + } + } + } +} + +@Composable +fun ProfileScreen( + onBackClick: () -> Unit +) { + val context = LocalContext.current + val prefs = context.getSharedPreferences("AppPrefs", Context.MODE_PRIVATE) + + // Состояния полей (подтягиваем из памяти) + var name by remember { mutableStateOf(prefs.getString("realname", "") ?: "") } + var email by remember { mutableStateOf(prefs.getString("email", "") ?: "") } + var password by remember { mutableStateOf("") } + + // Состояние для отслеживания сохранения + var isSaving by remember { mutableStateOf(false) } + + Surface( + modifier = Modifier.fillMaxSize(), + color = Color(0xFF3E4B54) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + // Кнопка назад + IconButton( + onClick = onBackClick, + modifier = Modifier.size(48.dp) + ) { + Icon( + imageVector = Icons.Default.ArrowBack, + contentDescription = "Назад", + tint = Color.White + ) + } + + Text( + text = "Ваш профиль", + color = Color.White, + fontSize = 28.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(vertical = 24.dp) + ) + + // Поле Имя + ProfileTextField( + value = name, + label = "Имя в игре", + onValueChange = { name = it } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Поле Email + ProfileTextField( + value = email, + label = "Электронная почта", + onValueChange = { email = it } + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // Поле Пароль + ProfileTextField( + value = password, + label = "Новый пароль (оставьте пустым)", + onValueChange = { password = it }, + isPassword = true + ) + + Spacer(modifier = Modifier.weight(1f)) + + // Кнопка сохранения + Button( + onClick = { + isSaving = true + // Сохраняем в SharedPreferences + prefs.edit { + putString("realname", name) + if (email.isNotBlank()) { + putString("email", email) + } + } + Toast.makeText(context, "Данные обновлены!", Toast.LENGTH_SHORT).show() + isSaving = false + }, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(20.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + disabledContainerColor = Color.White.copy(alpha = 0.5f) + ), + border = BorderStroke(2.dp, Color.LightGray), + enabled = !isSaving + ) { + if (isSaving) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = Color(0xFF3E4B54), + strokeWidth = 2.dp + ) + } else { + Text( + text = "СОХРАНИТЬ ИЗМЕНЕНИЯ", + color = Color(0xFF3E4B54), + fontWeight = FontWeight.Bold + ) + } + } + } + } +} + +@Composable +fun ProfileTextField( + value: String, + label: String, + onValueChange: (String) -> Unit, + isPassword: Boolean = false +) { + OutlinedTextField( + value = value, + onValueChange = onValueChange, + label = { + Text( + text = label, + color = Color.LightGray + ) + }, + modifier = Modifier.fillMaxWidth(), + textStyle = LocalTextStyle.current.copy( + color = Color.White, + fontSize = 16.sp + ), + shape = RoundedCornerShape(15.dp), + colors = OutlinedTextFieldDefaults.colors( // ← Исправлено: используем OutlinedTextFieldDefaults + focusedBorderColor = Color.White, + unfocusedBorderColor = Color.White, + focusedTextColor = Color.White, + unfocusedTextColor = Color.White, + cursorColor = Color.White, + focusedLabelColor = Color.LightGray, + unfocusedLabelColor = Color.LightGray + ), + visualTransformation = if (isPassword) PasswordVisualTransformation() else VisualTransformation.None, + singleLine = true + ) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index ca3826a..953ca56 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,74 +1,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/player_card_bg.xml b/app/src/main/res/drawable/player_card_bg.xml deleted file mode 100644 index 593ed8c..0000000 --- a/app/src/main/res/drawable/player_card_bg.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/activity_information.xml b/app/src/main/res/layout/activity_information.xml deleted file mode 100644 index 2892200..0000000 --- a/app/src/main/res/layout/activity_information.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_load_screen.xml b/app/src/main/res/layout/activity_load_screen.xml deleted file mode 100644 index 420c9ff..0000000 --- a/app/src/main/res/layout/activity_load_screen.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 22ae615..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - - - - - - - - - - - -