From a67d6d4e7bc929322d5455bbb0058ea8caf03ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=A1=D1=8B?= =?UTF-8?q?=D1=80=D1=87=D0=B8=D0=BD?= Date: Sat, 17 May 2025 14:46:03 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D1=80=D1=83=D0=BF=D0=BD=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?2.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/deploymentTargetSelector.xml | 8 + app/build.gradle.kts | 8 +- .../com/kolobochki/memory/AppDatabase.java | 32 ++++ .../com/kolobochki/memory/GameRepository.java | 50 ++++++ .../com/kolobochki/memory/LevelRecord.java | 21 +++ .../com/kolobochki/memory/LevelRecordDao.java | 30 ++++ .../com/kolobochki/memory/MainActivity.java | 40 ++++- .../com/kolobochki/memory/fragment_info.java | 132 ++++++++++++++++ .../kolobochki/memory/fragment_records.java | 145 ++++++++++++++++++ .../com/kolobochki/memory/fragment_task1.java | 107 +++++++++---- .../com/kolobochki/memory/fragment_task2.java | 101 ++++++++++-- .../com/kolobochki/memory/fragment_task3.java | 50 ++++++ .../com/kolobochki/memory/fragment_task4.java | 59 ++++++- .../ui/dashboard/DashboardFragment.java | 84 +++++++++- .../main/res/layout/fragment_dashboard.xml | 1 + app/src/main/res/layout/fragment_info.xml | 23 +++ app/src/main/res/layout/fragment_records.xml | 21 +++ app/src/main/res/layout/item_level_stat.xml | 36 +++++ .../main/res/navigation/mobile_navigation.xml | 23 +++ gradle/libs.versions.toml | 2 + 20 files changed, 911 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/com/kolobochki/memory/AppDatabase.java create mode 100644 app/src/main/java/com/kolobochki/memory/GameRepository.java create mode 100644 app/src/main/java/com/kolobochki/memory/LevelRecord.java create mode 100644 app/src/main/java/com/kolobochki/memory/LevelRecordDao.java create mode 100644 app/src/main/java/com/kolobochki/memory/fragment_info.java create mode 100644 app/src/main/java/com/kolobochki/memory/fragment_records.java create mode 100644 app/src/main/res/layout/fragment_info.xml create mode 100644 app/src/main/res/layout/fragment_records.xml create mode 100644 app/src/main/res/layout/item_level_stat.xml diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index b268ef3..9621f9b 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,6 +4,14 @@ diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b6a8a0d..8e7121a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,6 +14,8 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + } buildTypes { @@ -35,7 +37,6 @@ android { } dependencies { - implementation(libs.appcompat) implementation(libs.material) implementation(libs.constraintlayout) @@ -43,6 +44,11 @@ dependencies { implementation(libs.lifecycle.viewmodel.ktx) implementation(libs.navigation.fragment) implementation(libs.navigation.ui) + + implementation("androidx.room:room-runtime:2.7.1") + implementation(libs.room.common.jvm) + annotationProcessor("androidx.room:room-compiler:2.7.1") + testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) diff --git a/app/src/main/java/com/kolobochki/memory/AppDatabase.java b/app/src/main/java/com/kolobochki/memory/AppDatabase.java new file mode 100644 index 0000000..dd63e82 --- /dev/null +++ b/app/src/main/java/com/kolobochki/memory/AppDatabase.java @@ -0,0 +1,32 @@ +package com.kolobochki.memory; + +import android.content.Context; + +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; + +@Database( + entities = {LevelRecord.class}, + version = 1, + exportSchema = true +) +public abstract class AppDatabase extends RoomDatabase { + public abstract LevelRecordDao levelRecordDao(); + + private static volatile AppDatabase INSTANCE; + + public static AppDatabase getDatabase(Context context) { + if (INSTANCE == null) { + synchronized (AppDatabase.class) { + if (INSTANCE == null) { + INSTANCE = Room.databaseBuilder(context.getApplicationContext(), + AppDatabase.class, "memory-db") + .fallbackToDestructiveMigration() + .build(); + } + } + } + return INSTANCE; + } +} diff --git a/app/src/main/java/com/kolobochki/memory/GameRepository.java b/app/src/main/java/com/kolobochki/memory/GameRepository.java new file mode 100644 index 0000000..a544959 --- /dev/null +++ b/app/src/main/java/com/kolobochki/memory/GameRepository.java @@ -0,0 +1,50 @@ +package com.kolobochki.memory; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import java.util.List; + +public class GameRepository { + private final LevelRecordDao recordDao; + + public GameRepository(LevelRecordDao recordDao) { + this.recordDao = recordDao; + } + + // Сохранить результат уровня + public void saveLevelResult(int levelId, int score) { + new Thread(() -> { + LevelRecord existing = recordDao.getRecordForLevel(levelId); + + if (existing == null) { + // Первая попытка на этом уровне + recordDao.insert(new LevelRecord(levelId, score, 1)); + } else { + // Обновляем если результат лучше + recordDao.updateIfBetter(levelId, score); + if (score <= existing.bestScore) { + // Просто увеличиваем счётчик попыток + existing.attemptsCount++; + recordDao.update(existing); + } + } + }).start(); + } + + // Получить все записи + public LiveData> getAllRecords() { + MutableLiveData> liveData = new MutableLiveData<>(); + new Thread(() -> { + liveData.postValue(recordDao.getAllRecords()); + }).start(); + return liveData; + } + + // Сбросить все данные + public void resetAllData() { + new Thread(() -> { + recordDao.deleteAllRecords(); + }).start(); + } +} diff --git a/app/src/main/java/com/kolobochki/memory/LevelRecord.java b/app/src/main/java/com/kolobochki/memory/LevelRecord.java new file mode 100644 index 0000000..41850f9 --- /dev/null +++ b/app/src/main/java/com/kolobochki/memory/LevelRecord.java @@ -0,0 +1,21 @@ +package com.kolobochki.memory; + +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "level_records") +public class LevelRecord { + @PrimaryKey + public int levelId; + + public int bestScore; + public int attemptsCount; + public long lastPlayedTime; + + public LevelRecord(int levelId, int bestScore, int attemptsCount) { + this.levelId = levelId; + this.bestScore = bestScore; + this.attemptsCount = attemptsCount; + this.lastPlayedTime = System.currentTimeMillis(); + } +} diff --git a/app/src/main/java/com/kolobochki/memory/LevelRecordDao.java b/app/src/main/java/com/kolobochki/memory/LevelRecordDao.java new file mode 100644 index 0000000..b386807 --- /dev/null +++ b/app/src/main/java/com/kolobochki/memory/LevelRecordDao.java @@ -0,0 +1,30 @@ +package com.kolobochki.memory; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; +import androidx.room.Update; + +import java.util.List; + +@Dao +public interface LevelRecordDao { + @Insert + void insert(LevelRecord record); + + @Update + void update(LevelRecord record); + + @Query("SELECT * FROM level_records WHERE levelId = :levelId") + LevelRecord getRecordForLevel(int levelId); + + @Query("SELECT * FROM level_records ORDER BY levelId ASC") + List getAllRecords(); + + @Query("DELETE FROM level_records") + void deleteAllRecords(); + + @Query("UPDATE level_records SET bestScore = :newScore, attemptsCount = attemptsCount + 1 WHERE levelId = :levelId AND :newScore > bestScore") + void updateIfBetter(int levelId, int newScore); + +} \ No newline at end of file diff --git a/app/src/main/java/com/kolobochki/memory/MainActivity.java b/app/src/main/java/com/kolobochki/memory/MainActivity.java index bea7d77..227b56d 100644 --- a/app/src/main/java/com/kolobochki/memory/MainActivity.java +++ b/app/src/main/java/com/kolobochki/memory/MainActivity.java @@ -10,18 +10,33 @@ import androidx.navigation.NavController; import androidx.navigation.Navigation; import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.NavigationUI; +import androidx.room.Room; import com.kolobochki.memory.databinding.ActivityMainBinding; +import com.kolobochki.memory.AppDatabase; +import com.kolobochki.memory.GameRepository; + public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; private NavController navController; + private GameRepository gameRepository; + private static AppDatabase database; + + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + database = Room.databaseBuilder(getApplicationContext(), + AppDatabase.class, "memory-db") + .fallbackToDestructiveMigration() + .build(); + + gameRepository = new GameRepository(database.levelRecordDao()); + binding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -33,7 +48,9 @@ public class MainActivity extends AppCompatActivity { R.id.navigation_task1, R.id.navigation_task2, R.id.navigation_task3, - R.id.navigation_task4) + R.id.navigation_task4, + R.id.navigation_info, + R.id.navigation_records) .build(); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); NavigationUI.setupWithNavController(binding.navView, navController); @@ -42,12 +59,31 @@ public class MainActivity extends AppCompatActivity { if (destination.getId() == R.id.navigation_task1 || destination.getId() == R.id.navigation_task2 || destination.getId() == R.id.navigation_task3 - || destination.getId() == R.id.navigation_task4) { + || destination.getId() == R.id.navigation_task4 + || destination.getId() == R.id.navigation_info + || destination.getId() == R.id.navigation_records) { binding.navView.setVisibility(View.GONE); } else { binding.navView.setVisibility(View.VISIBLE); } }); + + } + + public GameRepository getGameRepository() { + return gameRepository; + } + + public static AppDatabase getDatabase() { + return database; + } + + @Override + protected void onDestroy() { + if (database != null && database.isOpen()) { + database.close(); + } + super.onDestroy(); } @Override diff --git a/app/src/main/java/com/kolobochki/memory/fragment_info.java b/app/src/main/java/com/kolobochki/memory/fragment_info.java new file mode 100644 index 0000000..1d4fc24 --- /dev/null +++ b/app/src/main/java/com/kolobochki/memory/fragment_info.java @@ -0,0 +1,132 @@ +package com.kolobochki.memory; + +import android.os.Bundle; + +import androidx.activity.OnBackPressedCallback; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.os.Bundle; +import com.kolobochki.memory.R; +import com.kolobochki.memory.databinding.FragmentDashboardBinding; +import com.kolobochki.memory.databinding.FragmentInfoBinding; + +public class fragment_info extends Fragment { + + private FragmentInfoBinding binding; + private static final String ARG_PARAM1 = "param1"; + private static final String ARG_PARAM2 = "param2"; + + private TextView helpText; + + private String mParam1; + private String mParam2; + private OnBackPressedCallback backPressedCallback; + private String previousTitle; + + public fragment_info() { + // Required empty public constructor + } + + public static fragment_info newInstance(String param1, String param2) { + fragment_info fragment = new fragment_info(); + Bundle args = new Bundle(); + args.putString(ARG_PARAM1, param1); + args.putString(ARG_PARAM2, param2); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mParam1 = getArguments().getString(ARG_PARAM1); + mParam2 = getArguments().getString(ARG_PARAM2); + } + + if (getActivity() instanceof AppCompatActivity) { + ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle("Инструкция"); + } + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Inflate the layout for this fragment + binding = com.kolobochki.memory.databinding.FragmentInfoBinding .inflate(inflater, container, false); + View root = binding.getRoot(); + + String instructions = + "1. Найти пары:\n" + + " - На экране карточки перевернуты рубашкой вверх\n" + + " - Открывайте по две карточки, чтобы найти пары\n" + + " - Совпавшие карточки остаются открытыми\n" + + " - Цель: открыть все пары за минимальное время\n\n" + + + "2. Саймон говорит:\n" + + " - Саймон показывает последовательность цветов\n" + + " - Повторите показанную последовательность\n" + + " - С каждым уровнем последовательность удлиняется\n" + + " - Ошибка завершает игру\n\n" + + + "3. Числовая матрица:\n" + + " - Запомните расположение чисел в матрице\n" + + " - Восстановите числа после их исчезновения\n" + + " - Сложность повышается с каждым уровнем\n\n" + + + "4. Найти лишнее:\n" + + " - Среди нескольких объектов найдите отличающийся\n" + + " - Выберите лишний объект\n" + + " - Время на ответ ограничено"; + + binding.textView.setText(instructions); + + return root; + } + + private void navigateHome() { + NavController navController = Navigation.findNavController(requireView()); + navController.popBackStack(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (backPressedCallback != null) { + backPressedCallback.remove(); + } + } + + @Override + public void onResume() { + super.onResume(); + if (getActivity() != null && ((AppCompatActivity)getActivity()).getSupportActionBar() != null) { + ((AppCompatActivity)getActivity()).getSupportActionBar().hide(); + } + } + + @Override + public void onPause() { + super.onPause(); + if (getActivity() != null && ((AppCompatActivity)getActivity()).getSupportActionBar() != null) { + ((AppCompatActivity)getActivity()).getSupportActionBar().show(); + } + + if (getActivity() instanceof AppCompatActivity) { + ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); + if (actionBar != null && previousTitle != null) { + actionBar.setTitle(previousTitle); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kolobochki/memory/fragment_records.java b/app/src/main/java/com/kolobochki/memory/fragment_records.java new file mode 100644 index 0000000..9a89e59 --- /dev/null +++ b/app/src/main/java/com/kolobochki/memory/fragment_records.java @@ -0,0 +1,145 @@ +package com.kolobochki.memory; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.kolobochki.memory.AppDatabase; +import com.kolobochki.memory.LevelRecord; +import com.kolobochki.memory.LevelRecordDao; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class fragment_records extends Fragment { + + private RecyclerView recyclerView; + private StatsAdapter adapter; + private LevelRecordDao levelRecordDao; + private Executor executor = Executors.newSingleThreadExecutor(); + + private OnBackPressedCallback backPressedCallback; + private String previousTitle; + + // Названия уровней + private final String[] levelNames = { + "Найти пары", + "Саймон говорит", + "Числовая матрица", + "Найти лишнее" + }; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Инициализация базы данных + AppDatabase db = AppDatabase.getDatabase(requireContext().getApplicationContext()); + levelRecordDao = db.levelRecordDao(); + + if (getActivity() instanceof AppCompatActivity) { + ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle("Мой прогресс"); + } + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_records, container, false); + + recyclerView = view.findViewById(R.id.stats_recycler); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + + adapter = new StatsAdapter(); + recyclerView.setAdapter(adapter); + + loadStats(); + + return view; + } + + private void loadStats() { + executor.execute(() -> { + try { + List records = levelRecordDao.getAllRecords(); + + // Если записей нет - создаем пустые + if (records == null || records.isEmpty()) { + for (int i = 1; i <= 4; i++) { + LevelRecord record = new LevelRecord(i, 0, 0); + levelRecordDao.insert(record); + } + records = levelRecordDao.getAllRecords(); + } + + List finalRecords = records; + requireActivity().runOnUiThread(() -> { + if (adapter != null) { + adapter.setRecords(finalRecords); + } + }); + } catch (Exception e) { + requireActivity().runOnUiThread(() -> + Toast.makeText(requireContext(), "Ошибка загрузки данных", Toast.LENGTH_SHORT).show() + ); + } + }); + } + + // Адаптер для RecyclerView + private class StatsAdapter extends RecyclerView.Adapter { + + private List records; + + public void setRecords(List records) { + this.records = records; + notifyDataSetChanged(); + } + + @NonNull + @Override + public StatsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_level_stat, parent, false); + return new StatsViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull StatsViewHolder holder, int position) { + LevelRecord record = records.get(position); + holder.levelName.setText(levelNames[record.levelId - 1]); + holder.bestScore.setText("Рекорд: " + record.bestScore); + holder.attempts.setText("Попыток: " + record.attemptsCount); + } + + @Override + public int getItemCount() { + return records != null ? records.size() : 0; + } + + class StatsViewHolder extends RecyclerView.ViewHolder { + TextView levelName, bestScore, attempts; + + StatsViewHolder(View itemView) { + super(itemView); + levelName = itemView.findViewById(R.id.level_name); + bestScore = itemView.findViewById(R.id.best_score); + attempts = itemView.findViewById(R.id.attempts_count); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kolobochki/memory/fragment_task1.java b/app/src/main/java/com/kolobochki/memory/fragment_task1.java index 4b815b7..02dffc9 100644 --- a/app/src/main/java/com/kolobochki/memory/fragment_task1.java +++ b/app/src/main/java/com/kolobochki/memory/fragment_task1.java @@ -1,10 +1,8 @@ package com.kolobochki.memory; import android.os.Bundle; - import androidx.appcompat.app.ActionBar; import androidx.fragment.app.Fragment; - import android.os.Handler; import android.view.LayoutInflater; import android.view.View; @@ -14,17 +12,23 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; - +import android.widget.Toast; import androidx.activity.OnBackPressedCallback; import androidx.appcompat.app.AlertDialog; import androidx.navigation.NavController; import androidx.navigation.Navigation; import androidx.appcompat.app.AppCompatActivity; +import androidx.room.Room; + +import com.kolobochki.memory.AppDatabase; +import com.kolobochki.memory.LevelRecord; +import com.kolobochki.memory.LevelRecordDao; + import java.util.ArrayList; import java.util.Collections; import java.util.List; - -import com.kolobochki.memory.R; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; public class fragment_task1 extends Fragment { @@ -35,43 +39,34 @@ public class fragment_task1 extends Fragment { private OnBackPressedCallback backPressedCallback; private String previousTitle; - // ---------------------------------------------- - - private static final int PAIRS_COUNT = 6; // Кол-во пар - private static final int GRID_COLUMNS = 4; // Кол-во колонок - private static final int PREVIEW_TIME = 5000; // Задержка в начале в мс - private static final int SUCCESS_DELAY = 4000; // Задержка в конце в мс - private static final int INITIAL_LIVES = 3; // Кол-во жизек - - // ---------------------------------------------- + // Game constants + private static final int PAIRS_COUNT = 6; + private static final int GRID_COLUMNS = 4; + private static final int PREVIEW_TIME = 5000; + private static final int SUCCESS_DELAY = 4000; + private static final int INITIAL_LIVES = 3; + private static final int LEVEL_ID = 1; // ID для первого уровня + // Game variables private int score = 0; private int lives = INITIAL_LIVES; - private int flippedCount = 0; private ImageView firstFlipped = null; private boolean isPreview = true; private boolean isGameComplete = false; - // ---------------------------------------------- - + // Views private GridLayout gridLayout; private ProgressBar progressBar; private List tileImages = new ArrayList<>(); private List tiles = new ArrayList<>(); private LinearLayout livesLayout; - // ----------------------------------------------- + // Database + private AppDatabase database; + private LevelRecordDao levelRecordDao; + private Executor executor = Executors.newSingleThreadExecutor(); - public fragment_task1() { } - - public static fragment_task1 newInstance(String param1, String param2) { - fragment_task1 fragment = new fragment_task1(); - Bundle args = new Bundle(); - args.putString(ARG_PARAM1, param1); - args.putString(ARG_PARAM2, param2); - fragment.setArguments(args); - return fragment; - } + public fragment_task1() {} @Override public void onCreate(Bundle savedInstanceState) { @@ -81,7 +76,10 @@ public class fragment_task1 extends Fragment { mParam2 = getArguments().getString(ARG_PARAM2); } - OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) { + database = AppDatabase.getDatabase(requireContext()); + levelRecordDao = database.levelRecordDao(); + + backPressedCallback = new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { if (!isGameComplete) { @@ -103,7 +101,7 @@ public class fragment_task1 extends Fragment { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_task1, container, false); gridLayout = view.findViewById(R.id.gridLayout); @@ -168,7 +166,6 @@ public class fragment_task1 extends Fragment { }, PREVIEW_TIME); } - private void flipAllTiles() { for (ImageView tile : tiles) { tile.setImageResource(R.drawable.tile_back); @@ -233,11 +230,13 @@ public class fragment_task1 extends Fragment { private void gameComplete() { isGameComplete = true; + saveGameResult(true); new Handler().postDelayed(this::navigateHome, 1500); } private void gameOver() { isGameComplete = true; + saveGameResult(false); new AlertDialog.Builder(requireContext()) .setTitle("Игра окончена") .setMessage("Вы проиграли. Попробуйте еще раз!") @@ -246,6 +245,49 @@ public class fragment_task1 extends Fragment { .show(); } + private void saveGameResult(boolean isWin) { + executor.execute(() -> { + try { + LevelRecord existingRecord = levelRecordDao.getRecordForLevel(LEVEL_ID); + + if (existingRecord == null) { + // Первая запись для этого уровня + LevelRecord newRecord = new LevelRecord(LEVEL_ID, isWin ? score : 0, 1); + levelRecordDao.insert(newRecord); + + if (isWin) { + showNewRecordToast(score); + } + } else { + boolean isNewRecord = false; + + if (isWin && score > existingRecord.bestScore) { + existingRecord.bestScore = score; + isNewRecord = true; + } + + existingRecord.attemptsCount++; + levelRecordDao.update(existingRecord); + + if (isNewRecord) { + showNewRecordToast(score); + } + } + } catch (Exception e) { + requireActivity().runOnUiThread(() -> + Toast.makeText(requireContext(), "Ошибка сохранения: " + e.getMessage(), Toast.LENGTH_SHORT).show() + ); + } + }); + } + + private void showNewRecordToast(int score) { + requireActivity().runOnUiThread(() -> { + Toast.makeText(requireContext(), + "Новый рекорд: " + score, Toast.LENGTH_SHORT).show(); + }); + } + private void navigateHome() { NavController navController = Navigation.findNavController(requireView()); navController.popBackStack(); @@ -257,6 +299,9 @@ public class fragment_task1 extends Fragment { if (backPressedCallback != null) { backPressedCallback.remove(); } + if (database != null) { + database.close(); + } } private void showExitConfirmationDialog() { diff --git a/app/src/main/java/com/kolobochki/memory/fragment_task2.java b/app/src/main/java/com/kolobochki/memory/fragment_task2.java index d42b06d..ad200f8 100644 --- a/app/src/main/java/com/kolobochki/memory/fragment_task2.java +++ b/app/src/main/java/com/kolobochki/memory/fragment_task2.java @@ -3,35 +3,38 @@ package com.kolobochki.memory; import static com.kolobochki.memory.R.*; import android.os.Bundle; - import androidx.activity.OnBackPressedCallback; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; - import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.navigation.NavController; import androidx.navigation.Navigation; -import androidx.appcompat.app.AppCompatActivity; import android.widget.Button; import android.widget.GridLayout; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; -import com.kolobochki.memory.R; +import com.kolobochki.memory.AppDatabase; +import com.kolobochki.memory.LevelRecord; +import com.kolobochki.memory.LevelRecordDao; public class fragment_task2 extends Fragment { private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; + private static final int LEVEL_ID = 2; // ID для второго уровня private String mParam1; private String mParam2; @@ -50,6 +53,11 @@ public class fragment_task2 extends Fragment { private TextView roundText, livesText; private GridLayout buttonsGrid; + // Database + private AppDatabase database; + private LevelRecordDao levelRecordDao; + private Executor executor = Executors.newSingleThreadExecutor(); + private final int[] colorImages = { R.drawable.simon_red, R.drawable.simon_green, @@ -62,15 +70,6 @@ public class fragment_task2 extends Fragment { // Required empty public constructor } - public static fragment_task2 newInstance(String param1, String param2) { - fragment_task2 fragment = new fragment_task2(); - Bundle args = new Bundle(); - args.putString(ARG_PARAM1, param1); - args.putString(ARG_PARAM2, param2); - fragment.setArguments(args); - return fragment; - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -79,6 +78,9 @@ public class fragment_task2 extends Fragment { mParam2 = getArguments().getString(ARG_PARAM2); } + database = AppDatabase.getDatabase(requireContext()); + levelRecordDao = database.levelRecordDao(); + OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { @@ -92,6 +94,14 @@ public class fragment_task2 extends Fragment { }; requireActivity().getOnBackPressedDispatcher().addCallback(this, backPressedCallback); + executor.execute(() -> { + LevelRecord existingRecord = levelRecordDao.getRecordForLevel(LEVEL_ID); + if (existingRecord != null) { + existingRecord.attemptsCount++; + levelRecordDao.update(existingRecord); + } + }); + if (getActivity() instanceof AppCompatActivity) { ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); if (actionBar != null) { @@ -101,7 +111,7 @@ public class fragment_task2 extends Fragment { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_task2, container, false); redButton = view.findViewById(R.id.redButton); @@ -128,6 +138,7 @@ public class fragment_task2 extends Fragment { round = 1; lives = 3; currentStep = 0; + isGameComplete = false; updateUI(); for (int i = 0; i < 3; i++) { @@ -207,8 +218,10 @@ public class fragment_task2 extends Fragment { round++; currentStep = 0; updateUI(); - generateNextSequence(); + checkAndUpdateRecord(round); + + generateNextSequence(); new Handler().postDelayed(() -> { if (!isGameComplete) { showSequence(); @@ -231,6 +244,17 @@ public class fragment_task2 extends Fragment { } } + private void checkAndUpdateRecord(int currentRound) { + executor.execute(() -> { + LevelRecord record = levelRecordDao.getRecordForLevel(LEVEL_ID); + if (record != null && currentRound > record.bestScore) { + record.bestScore = currentRound; + levelRecordDao.update(record); + showNewRecordToast(currentRound); + } + }); + } + private void updateUI() { roundText.setText("Раунд: " + round); @@ -257,6 +281,45 @@ public class fragment_task2 extends Fragment { .show(); } + private void saveGameResult(int roundsCompleted) { + executor.execute(() -> { + try { + LevelRecord existingRecord = levelRecordDao.getRecordForLevel(LEVEL_ID); + + if (existingRecord == null) { + LevelRecord newRecord = new LevelRecord(LEVEL_ID, roundsCompleted, 1); + levelRecordDao.insert(newRecord); + showNewRecordToast(roundsCompleted); + } else { + boolean isNewRecord = false; + + if (roundsCompleted > existingRecord.bestScore) { + existingRecord.bestScore = roundsCompleted; + isNewRecord = true; + } + + existingRecord.attemptsCount++; + levelRecordDao.update(existingRecord); + + if (isNewRecord) { + showNewRecordToast(roundsCompleted); + } + } + } catch (Exception e) { + requireActivity().runOnUiThread(() -> + Toast.makeText(requireContext(), "Ошибка сохранения: " + e.getMessage(), Toast.LENGTH_SHORT).show() + ); + } + }); + } + + private void showNewRecordToast(int score) { + requireActivity().runOnUiThread(() -> { + Toast.makeText(requireContext(), + "Новый рекорд: " + score + " раундов", Toast.LENGTH_SHORT).show(); + }); + } + private void showExitConfirmationDialog() { new AlertDialog.Builder(requireContext()) .setTitle("Закончить упражнение?") @@ -296,4 +359,12 @@ public class fragment_task2 extends Fragment { } } } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (database != null) { + database.close(); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/kolobochki/memory/fragment_task3.java b/app/src/main/java/com/kolobochki/memory/fragment_task3.java index 5aae46c..1ecc0e5 100644 --- a/app/src/main/java/com/kolobochki/memory/fragment_task3.java +++ b/app/src/main/java/com/kolobochki/memory/fragment_task3.java @@ -23,11 +23,18 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; public class fragment_task3 extends Fragment { private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; + private static final int LEVEL_ID = 3; + + private AppDatabase database; + private LevelRecordDao levelRecordDao; + private Executor executor = Executors.newSingleThreadExecutor(); private String mParam1; private String mParam2; @@ -66,6 +73,20 @@ public class fragment_task3 extends Fragment { mParam2 = getArguments().getString(ARG_PARAM2); } + database = AppDatabase.getDatabase(requireContext()); + levelRecordDao = database.levelRecordDao(); + + executor.execute(() -> { + LevelRecord existingRecord = levelRecordDao.getRecordForLevel(LEVEL_ID); + if (existingRecord != null) { + existingRecord.attemptsCount++; + levelRecordDao.update(existingRecord); + } else { + LevelRecord newRecord = new LevelRecord(LEVEL_ID, 0, 1); + levelRecordDao.insert(newRecord); + } + }); + OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { @@ -172,6 +193,9 @@ public class fragment_task3 extends Fragment { if (isCorrect) { currentRound++; + // Проверяем и обновляем рекорд сразу при увеличении раунда + checkAndUpdateRecord(currentRound); + Toast.makeText(getContext(), "Правильно! Раунд " + currentRound, Toast.LENGTH_SHORT).show(); new Handler().postDelayed(this::setupGame, 1500); } else { @@ -185,6 +209,24 @@ public class fragment_task3 extends Fragment { } } + private void checkAndUpdateRecord(int currentRound) { + executor.execute(() -> { + LevelRecord record = levelRecordDao.getRecordForLevel(LEVEL_ID); + if (record != null && currentRound > record.bestScore) { + record.bestScore = currentRound; + levelRecordDao.update(record); + showNewRecordToast(currentRound); + } + }); + } + + private void showNewRecordToast(int round) { + requireActivity().runOnUiThread(() -> { + Toast.makeText(requireContext(), + "Новый рекорд: " + round + " раундов", Toast.LENGTH_SHORT).show(); + }); + } + private void gameOver() { isGameComplete = true; new AlertDialog.Builder(requireContext()) @@ -244,4 +286,12 @@ public class fragment_task3 extends Fragment { NavController navController = Navigation.findNavController(requireView()); navController.popBackStack(); } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (database != null) { + database.close(); + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/kolobochki/memory/fragment_task4.java b/app/src/main/java/com/kolobochki/memory/fragment_task4.java index 8ac5d56..1099204 100644 --- a/app/src/main/java/com/kolobochki/memory/fragment_task4.java +++ b/app/src/main/java/com/kolobochki/memory/fragment_task4.java @@ -8,6 +8,8 @@ import android.view.ViewGroup; import android.widget.GridLayout; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; + import androidx.activity.OnBackPressedCallback; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; @@ -19,11 +21,18 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Random; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; public class fragment_task4 extends Fragment { private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; + private static final int LEVEL_ID = 4; + + private AppDatabase database; + private LevelRecordDao levelRecordDao; + private Executor executor = Executors.newSingleThreadExecutor(); private String mParam1; private String mParam2; @@ -62,6 +71,20 @@ public class fragment_task4 extends Fragment { mParam2 = getArguments().getString(ARG_PARAM2); } + database = AppDatabase.getDatabase(requireContext()); + levelRecordDao = database.levelRecordDao(); + + executor.execute(() -> { + LevelRecord existingRecord = levelRecordDao.getRecordForLevel(LEVEL_ID); + if (existingRecord != null) { + existingRecord.attemptsCount++; + levelRecordDao.update(existingRecord); + } else { + LevelRecord newRecord = new LevelRecord(LEVEL_ID, 0, 1); + levelRecordDao.insert(newRecord); + } + }); + OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { @@ -181,8 +204,9 @@ public class fragment_task4 extends Fragment { setCardsClickable(false); if (row == changedRow && col == changedCol) { - // Correct guess round++; + checkAndUpdateRecord(round); + handler.postDelayed(() -> { if (isAdded()) startNewRound(); }, 1000); @@ -206,6 +230,33 @@ public class fragment_task4 extends Fragment { } } + private void checkAndUpdateRecord(int currentRound) { + executor.execute(() -> { + LevelRecord record = levelRecordDao.getRecordForLevel(LEVEL_ID); + if (record != null && currentRound > record.bestScore) { + record.bestScore = currentRound; + levelRecordDao.update(record); + showNewRecordToast(currentRound); + } + }); + } + + private void showNewRecordToast(int round) { + requireActivity().runOnUiThread(() -> { + Toast.makeText(requireContext(), + "Новый рекорд: " + round + " раундов", Toast.LENGTH_SHORT).show(); + }); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + handler.removeCallbacksAndMessages(null); + if (database != null) { + database.close(); + } + } + private void setCardImage(int row, int col, String imageName) { cards[row][col].setImageResource(getResourceId(imageName)); } @@ -281,10 +332,4 @@ public class fragment_task4 extends Fragment { NavController navController = Navigation.findNavController(requireView()); navController.popBackStack(); } - - @Override - public void onDestroyView() { - super.onDestroyView(); - handler.removeCallbacksAndMessages(null); - } } \ No newline at end of file diff --git a/app/src/main/java/com/kolobochki/memory/ui/dashboard/DashboardFragment.java b/app/src/main/java/com/kolobochki/memory/ui/dashboard/DashboardFragment.java index 3f19fc8..89b2ab6 100644 --- a/app/src/main/java/com/kolobochki/memory/ui/dashboard/DashboardFragment.java +++ b/app/src/main/java/com/kolobochki/memory/ui/dashboard/DashboardFragment.java @@ -1,34 +1,106 @@ package com.kolobochki.memory.ui.dashboard; +import android.app.AlertDialog; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.Navigation; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import com.kolobochki.memory.LevelRecord; +import com.kolobochki.memory.MainActivity; import com.kolobochki.memory.databinding.FragmentDashboardBinding; +import com.kolobochki.memory.R; +import com.kolobochki.memory.AppDatabase; +import com.kolobochki.memory.GameRepository; +import com.kolobochki.memory.LevelRecordDao; + public class DashboardFragment extends Fragment { private FragmentDashboardBinding binding; + private Executor executor = Executors.newSingleThreadExecutor(); + private LevelRecordDao levelRecordDao; - public View onCreateView(@NonNull LayoutInflater inflater, - ViewGroup container, Bundle savedInstanceState) { - DashboardViewModel dashboardViewModel = - new ViewModelProvider(this).get(DashboardViewModel.class); + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Инициализация базы данных + AppDatabase db = MainActivity.getDatabase(); + levelRecordDao = db.levelRecordDao(); + } + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentDashboardBinding.inflate(inflater, container, false); View root = binding.getRoot(); - //final TextView textView = binding.textDashboard; - //dashboardViewModel.getText().observe(getViewLifecycleOwner(), textView::setText); + binding.chip.setOnClickListener(view -> { + try { + Navigation.findNavController(view).navigate(R.id.action_navigation_dashboard_to_navigation_info); + } catch (Exception e) { + //e.printStackTrace(); + } + }); + + binding.chip2.setOnClickListener(view -> { + Navigation.findNavController(view).navigate(R.id.action_navigation_dashboard_to_navigation_records); + }); + + binding.chip4.setOnClickListener(view -> { + showResetConfirmationDialog(); + }); + + binding.chip3.setOnClickListener(view -> { + new AlertDialog.Builder(requireContext()) + .setTitle("Переход на другую платформу") + .setMessage("Скоро...") + .setPositiveButton("OK", null) + .show(); + }); + + return root; } + private void showResetConfirmationDialog() { + new AlertDialog.Builder(requireContext()) + .setTitle("Сброс статистики") + .setMessage("Вы уверены, что хотите сбросить все рекорды и статистику?") + .setPositiveButton("Сбросить", (dialog, which) -> resetAllStats()) + .setNegativeButton("Отмена", null) + .show(); + } + + private void resetAllStats() { + executor.execute(() -> { + // Удаляем все записи + levelRecordDao.deleteAllRecords(); + + // Создаем новые пустые записи для каждого уровня + for (int i = 1; i <= 4; i++) { + LevelRecord record = new LevelRecord(i, 0, 0); + levelRecordDao.insert(record); + } + + // Показываем уведомление в UI потоке + requireActivity().runOnUiThread(() -> { + Toast.makeText(requireContext(), + "Все данные сброшены", Toast.LENGTH_SHORT).show(); + }); + }); + } + @Override public void onDestroyView() { super.onDestroyView(); diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml index b94dd9c..9cfc2b2 100644 --- a/app/src/main/res/layout/fragment_dashboard.xml +++ b/app/src/main/res/layout/fragment_dashboard.xml @@ -79,4 +79,5 @@ android:text="Сбросить мои данные" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imageView" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_info.xml b/app/src/main/res/layout/fragment_info.xml new file mode 100644 index 0000000..78303f3 --- /dev/null +++ b/app/src/main/res/layout/fragment_info.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_records.xml b/app/src/main/res/layout/fragment_records.xml new file mode 100644 index 0000000..49bd17a --- /dev/null +++ b/app/src/main/res/layout/fragment_records.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_level_stat.xml b/app/src/main/res/layout/item_level_stat.xml new file mode 100644 index 0000000..ba5c368 --- /dev/null +++ b/app/src/main/res/layout/item_level_stat.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 1b6bbd5..988f786 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -35,6 +35,12 @@ + + + + + + + + \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1851377..7e963bf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,6 +10,7 @@ lifecycleLivedataKtx = "2.8.7" lifecycleViewmodelKtx = "2.8.7" navigationFragment = "2.8.8" navigationUi = "2.8.8" +roomCommonJvm = "2.7.1" [libraries] junit = { group = "junit", name = "junit", version.ref = "junit" } @@ -22,6 +23,7 @@ lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-lived lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" } navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigationUi" } +room-common-jvm = { group = "androidx.room", name = "room-common-jvm", version.ref = "roomCommonJvm" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }