Крупное обновление 2.0

This commit is contained in:
Алексей Сырчин 2025-05-17 14:46:03 +03:00
parent f6d29f1fb9
commit a67d6d4e7b
20 changed files with 911 additions and 62 deletions

View File

@ -4,6 +4,14 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-05-17T08:55:13.914480200Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=28161JEGR06778" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState> </SelectionState>
</selectionStates> </selectionStates>
</component> </component>

View File

@ -14,6 +14,8 @@ android {
versionName = "1.0" versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
@ -35,7 +37,6 @@ android {
} }
dependencies { dependencies {
implementation(libs.appcompat) implementation(libs.appcompat)
implementation(libs.material) implementation(libs.material)
implementation(libs.constraintlayout) implementation(libs.constraintlayout)
@ -43,6 +44,11 @@ dependencies {
implementation(libs.lifecycle.viewmodel.ktx) implementation(libs.lifecycle.viewmodel.ktx)
implementation(libs.navigation.fragment) implementation(libs.navigation.fragment)
implementation(libs.navigation.ui) 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) testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core) androidTestImplementation(libs.espresso.core)

View File

@ -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;
}
}

View File

@ -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<List<LevelRecord>> getAllRecords() {
MutableLiveData<List<LevelRecord>> liveData = new MutableLiveData<>();
new Thread(() -> {
liveData.postValue(recordDao.getAllRecords());
}).start();
return liveData;
}
// Сбросить все данные
public void resetAllData() {
new Thread(() -> {
recordDao.deleteAllRecords();
}).start();
}
}

View File

@ -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();
}
}

View File

@ -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<LevelRecord> 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);
}

View File

@ -10,18 +10,33 @@ import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI; import androidx.navigation.ui.NavigationUI;
import androidx.room.Room;
import com.kolobochki.memory.databinding.ActivityMainBinding; import com.kolobochki.memory.databinding.ActivityMainBinding;
import com.kolobochki.memory.AppDatabase;
import com.kolobochki.memory.GameRepository;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding; private ActivityMainBinding binding;
private NavController navController; private NavController navController;
private GameRepository gameRepository;
private static AppDatabase database;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
database = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "memory-db")
.fallbackToDestructiveMigration()
.build();
gameRepository = new GameRepository(database.levelRecordDao());
binding = ActivityMainBinding.inflate(getLayoutInflater()); binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
@ -33,7 +48,9 @@ public class MainActivity extends AppCompatActivity {
R.id.navigation_task1, R.id.navigation_task1,
R.id.navigation_task2, R.id.navigation_task2,
R.id.navigation_task3, R.id.navigation_task3,
R.id.navigation_task4) R.id.navigation_task4,
R.id.navigation_info,
R.id.navigation_records)
.build(); .build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController); NavigationUI.setupWithNavController(binding.navView, navController);
@ -42,12 +59,31 @@ public class MainActivity extends AppCompatActivity {
if (destination.getId() == R.id.navigation_task1 if (destination.getId() == R.id.navigation_task1
|| destination.getId() == R.id.navigation_task2 || destination.getId() == R.id.navigation_task2
|| destination.getId() == R.id.navigation_task3 || 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); binding.navView.setVisibility(View.GONE);
} else { } else {
binding.navView.setVisibility(View.VISIBLE); 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 @Override

View File

@ -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);
}
}
}
}

View File

@ -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<LevelRecord> 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<LevelRecord> 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<StatsAdapter.StatsViewHolder> {
private List<LevelRecord> records;
public void setRecords(List<LevelRecord> 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);
}
}
}
}

View File

@ -1,10 +1,8 @@
package com.kolobochki.memory; package com.kolobochki.memory;
import android.os.Bundle; import android.os.Bundle;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import android.os.Handler; import android.os.Handler;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -14,17 +12,23 @@ import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import androidx.appcompat.app.AppCompatActivity; 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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor;
import com.kolobochki.memory.R; import java.util.concurrent.Executors;
public class fragment_task1 extends Fragment { public class fragment_task1 extends Fragment {
@ -35,43 +39,34 @@ public class fragment_task1 extends Fragment {
private OnBackPressedCallback backPressedCallback; private OnBackPressedCallback backPressedCallback;
private String previousTitle; private String previousTitle;
// ---------------------------------------------- // Game constants
private static final int PAIRS_COUNT = 6;
private static final int PAIRS_COUNT = 6; // Кол-во пар private static final int GRID_COLUMNS = 4;
private static final int GRID_COLUMNS = 4; // Кол-во колонок private static final int PREVIEW_TIME = 5000;
private static final int PREVIEW_TIME = 5000; // Задержка в начале в мс private static final int SUCCESS_DELAY = 4000;
private static final int SUCCESS_DELAY = 4000; // Задержка в конце в мс private static final int INITIAL_LIVES = 3;
private static final int INITIAL_LIVES = 3; // Кол-во жизек private static final int LEVEL_ID = 1; // ID для первого уровня
// ----------------------------------------------
// Game variables
private int score = 0; private int score = 0;
private int lives = INITIAL_LIVES; private int lives = INITIAL_LIVES;
private int flippedCount = 0;
private ImageView firstFlipped = null; private ImageView firstFlipped = null;
private boolean isPreview = true; private boolean isPreview = true;
private boolean isGameComplete = false; private boolean isGameComplete = false;
// ---------------------------------------------- // Views
private GridLayout gridLayout; private GridLayout gridLayout;
private ProgressBar progressBar; private ProgressBar progressBar;
private List<Integer> tileImages = new ArrayList<>(); private List<Integer> tileImages = new ArrayList<>();
private List<ImageView> tiles = new ArrayList<>(); private List<ImageView> tiles = new ArrayList<>();
private LinearLayout livesLayout; private LinearLayout livesLayout;
// ----------------------------------------------- // Database
private AppDatabase database;
private LevelRecordDao levelRecordDao;
private Executor executor = Executors.newSingleThreadExecutor();
public fragment_task1() { } 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;
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -81,7 +76,10 @@ public class fragment_task1 extends Fragment {
mParam2 = getArguments().getString(ARG_PARAM2); mParam2 = getArguments().getString(ARG_PARAM2);
} }
OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) { database = AppDatabase.getDatabase(requireContext());
levelRecordDao = database.levelRecordDao();
backPressedCallback = new OnBackPressedCallback(true) {
@Override @Override
public void handleOnBackPressed() { public void handleOnBackPressed() {
if (!isGameComplete) { if (!isGameComplete) {
@ -103,7 +101,7 @@ public class fragment_task1 extends Fragment {
} }
@Override @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); View view = inflater.inflate(R.layout.fragment_task1, container, false);
gridLayout = view.findViewById(R.id.gridLayout); gridLayout = view.findViewById(R.id.gridLayout);
@ -168,7 +166,6 @@ public class fragment_task1 extends Fragment {
}, PREVIEW_TIME); }, PREVIEW_TIME);
} }
private void flipAllTiles() { private void flipAllTiles() {
for (ImageView tile : tiles) { for (ImageView tile : tiles) {
tile.setImageResource(R.drawable.tile_back); tile.setImageResource(R.drawable.tile_back);
@ -233,11 +230,13 @@ public class fragment_task1 extends Fragment {
private void gameComplete() { private void gameComplete() {
isGameComplete = true; isGameComplete = true;
saveGameResult(true);
new Handler().postDelayed(this::navigateHome, 1500); new Handler().postDelayed(this::navigateHome, 1500);
} }
private void gameOver() { private void gameOver() {
isGameComplete = true; isGameComplete = true;
saveGameResult(false);
new AlertDialog.Builder(requireContext()) new AlertDialog.Builder(requireContext())
.setTitle("Игра окончена") .setTitle("Игра окончена")
.setMessage("Вы проиграли. Попробуйте еще раз!") .setMessage("Вы проиграли. Попробуйте еще раз!")
@ -246,6 +245,49 @@ public class fragment_task1 extends Fragment {
.show(); .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() { private void navigateHome() {
NavController navController = Navigation.findNavController(requireView()); NavController navController = Navigation.findNavController(requireView());
navController.popBackStack(); navController.popBackStack();
@ -257,6 +299,9 @@ public class fragment_task1 extends Fragment {
if (backPressedCallback != null) { if (backPressedCallback != null) {
backPressedCallback.remove(); backPressedCallback.remove();
} }
if (database != null) {
database.close();
}
} }
private void showExitConfirmationDialog() { private void showExitConfirmationDialog() {

View File

@ -3,35 +3,38 @@ package com.kolobochki.memory;
import static com.kolobochki.memory.R.*; import static com.kolobochki.memory.R.*;
import android.os.Bundle; import android.os.Bundle;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import android.os.Handler; import android.os.Handler;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.Navigation; import androidx.navigation.Navigation;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.Button; import android.widget.Button;
import android.widget.GridLayout; import android.widget.GridLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Random; 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 { public class fragment_task2 extends Fragment {
private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2"; private static final String ARG_PARAM2 = "param2";
private static final int LEVEL_ID = 2; // ID для второго уровня
private String mParam1; private String mParam1;
private String mParam2; private String mParam2;
@ -50,6 +53,11 @@ public class fragment_task2 extends Fragment {
private TextView roundText, livesText; private TextView roundText, livesText;
private GridLayout buttonsGrid; private GridLayout buttonsGrid;
// Database
private AppDatabase database;
private LevelRecordDao levelRecordDao;
private Executor executor = Executors.newSingleThreadExecutor();
private final int[] colorImages = { private final int[] colorImages = {
R.drawable.simon_red, R.drawable.simon_red,
R.drawable.simon_green, R.drawable.simon_green,
@ -62,15 +70,6 @@ public class fragment_task2 extends Fragment {
// Required empty public constructor // 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 @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -79,6 +78,9 @@ public class fragment_task2 extends Fragment {
mParam2 = getArguments().getString(ARG_PARAM2); mParam2 = getArguments().getString(ARG_PARAM2);
} }
database = AppDatabase.getDatabase(requireContext());
levelRecordDao = database.levelRecordDao();
OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) { OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
@Override @Override
public void handleOnBackPressed() { public void handleOnBackPressed() {
@ -92,6 +94,14 @@ public class fragment_task2 extends Fragment {
}; };
requireActivity().getOnBackPressedDispatcher().addCallback(this, backPressedCallback); 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) { if (getActivity() instanceof AppCompatActivity) {
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
@ -101,7 +111,7 @@ public class fragment_task2 extends Fragment {
} }
@Override @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); View view = inflater.inflate(R.layout.fragment_task2, container, false);
redButton = view.findViewById(R.id.redButton); redButton = view.findViewById(R.id.redButton);
@ -128,6 +138,7 @@ public class fragment_task2 extends Fragment {
round = 1; round = 1;
lives = 3; lives = 3;
currentStep = 0; currentStep = 0;
isGameComplete = false;
updateUI(); updateUI();
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
@ -207,8 +218,10 @@ public class fragment_task2 extends Fragment {
round++; round++;
currentStep = 0; currentStep = 0;
updateUI(); updateUI();
generateNextSequence();
checkAndUpdateRecord(round);
generateNextSequence();
new Handler().postDelayed(() -> { new Handler().postDelayed(() -> {
if (!isGameComplete) { if (!isGameComplete) {
showSequence(); 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() { private void updateUI() {
roundText.setText("Раунд: " + round); roundText.setText("Раунд: " + round);
@ -257,6 +281,45 @@ public class fragment_task2 extends Fragment {
.show(); .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() { private void showExitConfirmationDialog() {
new AlertDialog.Builder(requireContext()) new AlertDialog.Builder(requireContext())
.setTitle("Закончить упражнение?") .setTitle("Закончить упражнение?")
@ -296,4 +359,12 @@ public class fragment_task2 extends Fragment {
} }
} }
} }
@Override
public void onDestroyView() {
super.onDestroyView();
if (database != null) {
database.close();
}
}
} }

View File

@ -23,11 +23,18 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class fragment_task3 extends Fragment { public class fragment_task3 extends Fragment {
private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2"; 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 mParam1;
private String mParam2; private String mParam2;
@ -66,6 +73,20 @@ public class fragment_task3 extends Fragment {
mParam2 = getArguments().getString(ARG_PARAM2); 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) { OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
@Override @Override
public void handleOnBackPressed() { public void handleOnBackPressed() {
@ -172,6 +193,9 @@ public class fragment_task3 extends Fragment {
if (isCorrect) { if (isCorrect) {
currentRound++; currentRound++;
// Проверяем и обновляем рекорд сразу при увеличении раунда
checkAndUpdateRecord(currentRound);
Toast.makeText(getContext(), "Правильно! Раунд " + currentRound, Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), "Правильно! Раунд " + currentRound, Toast.LENGTH_SHORT).show();
new Handler().postDelayed(this::setupGame, 1500); new Handler().postDelayed(this::setupGame, 1500);
} else { } 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() { private void gameOver() {
isGameComplete = true; isGameComplete = true;
new AlertDialog.Builder(requireContext()) new AlertDialog.Builder(requireContext())
@ -244,4 +286,12 @@ public class fragment_task3 extends Fragment {
NavController navController = Navigation.findNavController(requireView()); NavController navController = Navigation.findNavController(requireView());
navController.popBackStack(); navController.popBackStack();
} }
@Override
public void onDestroyView() {
super.onDestroyView();
if (database != null) {
database.close();
}
}
} }

View File

@ -8,6 +8,8 @@ import android.view.ViewGroup;
import android.widget.GridLayout; import android.widget.GridLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
@ -19,11 +21,18 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class fragment_task4 extends Fragment { public class fragment_task4 extends Fragment {
private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2"; 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 mParam1;
private String mParam2; private String mParam2;
@ -62,6 +71,20 @@ public class fragment_task4 extends Fragment {
mParam2 = getArguments().getString(ARG_PARAM2); 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) { OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
@Override @Override
public void handleOnBackPressed() { public void handleOnBackPressed() {
@ -181,8 +204,9 @@ public class fragment_task4 extends Fragment {
setCardsClickable(false); setCardsClickable(false);
if (row == changedRow && col == changedCol) { if (row == changedRow && col == changedCol) {
// Correct guess
round++; round++;
checkAndUpdateRecord(round);
handler.postDelayed(() -> { handler.postDelayed(() -> {
if (isAdded()) startNewRound(); if (isAdded()) startNewRound();
}, 1000); }, 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) { private void setCardImage(int row, int col, String imageName) {
cards[row][col].setImageResource(getResourceId(imageName)); cards[row][col].setImageResource(getResourceId(imageName));
} }
@ -281,10 +332,4 @@ public class fragment_task4 extends Fragment {
NavController navController = Navigation.findNavController(requireView()); NavController navController = Navigation.findNavController(requireView());
navController.popBackStack(); navController.popBackStack();
} }
@Override
public void onDestroyView() {
super.onDestroyView();
handler.removeCallbacksAndMessages(null);
}
} }

View File

@ -1,34 +1,106 @@
package com.kolobochki.memory.ui.dashboard; package com.kolobochki.memory.ui.dashboard;
import android.app.AlertDialog;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; 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.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 { public class DashboardFragment extends Fragment {
private FragmentDashboardBinding binding; private FragmentDashboardBinding binding;
private Executor executor = Executors.newSingleThreadExecutor();
private LevelRecordDao levelRecordDao;
public View onCreateView(@NonNull LayoutInflater inflater, @Override
ViewGroup container, Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
DashboardViewModel dashboardViewModel = super.onCreate(savedInstanceState);
new ViewModelProvider(this).get(DashboardViewModel.class); // Инициализация базы данных
AppDatabase db = MainActivity.getDatabase();
levelRecordDao = db.levelRecordDao();
}
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
binding = FragmentDashboardBinding.inflate(inflater, container, false); binding = FragmentDashboardBinding.inflate(inflater, container, false);
View root = binding.getRoot(); View root = binding.getRoot();
//final TextView textView = binding.textDashboard; binding.chip.setOnClickListener(view -> {
//dashboardViewModel.getText().observe(getViewLifecycleOwner(), textView::setText); 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; 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 @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();

View File

@ -79,4 +79,5 @@
android:text="Сбросить мои данные" android:text="Сбросить мои данные"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" /> app:layout_constraintTop_toBottomOf="@+id/imageView" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment_info">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="@string/hello_blank_fragment"
android:visibility="invisible" />
<TextView
android:id="@+id/textView"
android:layout_width="375dp"
android:layout_height="wrap_content"
android:text="1. Найти пары"
android:translationX="25dp"
android:translationY="35dp" />
</FrameLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Статистика по уровням"
android:textSize="20sp"
android:textStyle="bold"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/stats_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="16dp"/>
</LinearLayout>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="?android:attr/selectableItemBackground"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/level_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<TextView
android:id="@+id/best_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"/>
<TextView
android:id="@+id/attempts_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>

View File

@ -35,6 +35,12 @@
<action <action
android:id="@+id/action_navigation_dashboard_to_navigation_home" android:id="@+id/action_navigation_dashboard_to_navigation_home"
app:destination="@id/navigation_home" /> app:destination="@id/navigation_home" />
<action
android:id="@+id/action_navigation_dashboard_to_navigation_info"
app:destination="@id/navigation_info" />
<action
android:id="@+id/action_navigation_dashboard_to_navigation_records"
app:destination="@id/navigation_records" />
</fragment> </fragment>
<fragment <fragment
@ -73,4 +79,21 @@
android:id="@+id/action_navigation_notifications_to_navigation_home" android:id="@+id/action_navigation_notifications_to_navigation_home"
app:destination="@id/navigation_home" /> app:destination="@id/navigation_home" />
</fragment> </fragment>
<fragment
android:id="@+id/navigation_info"
android:name="com.kolobochki.memory.fragment_info"
android:label="navigation_info"
tools:layout="@layout/fragment_info">
<action
android:id="@+id/action_navigation_info_to_navigation_dashboard2"
app:destination="@id/navigation_dashboard" />
</fragment>
<fragment
android:id="@+id/navigation_records"
android:name="com.kolobochki.memory.fragment_records"
android:label="navigation_records">
<action
android:id="@+id/action_navigation_records_to_navigation_dashboard"
app:destination="@id/navigation_dashboard" />
</fragment>
</navigation> </navigation>

View File

@ -10,6 +10,7 @@ lifecycleLivedataKtx = "2.8.7"
lifecycleViewmodelKtx = "2.8.7" lifecycleViewmodelKtx = "2.8.7"
navigationFragment = "2.8.8" navigationFragment = "2.8.8"
navigationUi = "2.8.8" navigationUi = "2.8.8"
roomCommonJvm = "2.7.1"
[libraries] [libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" } 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" } 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-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" }
navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigationUi" } 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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }