fix gitignore

This commit is contained in:
2026-02-26 21:32:08 -05:00
parent a6a76c90b9
commit 06873ca27b
83 changed files with 2675 additions and 2 deletions
+42
View File
@@ -0,0 +1,42 @@
plugins {
id("com.android.application")
}
android {
namespace = "com.example.todojava"
compileSdk = 35
defaultConfig {
applicationId = "com.example.todojava"
minSdk = 26
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation(libs.material)
implementation(libs.room.runtime)
annotationProcessor(libs.room.compiler)
implementation(libs.lifecycle.viewmodel)
implementation(libs.lifecycle.livedata)
implementation(libs.recyclerview)
implementation(libs.appcompat)
implementation(libs.constraintlayout)
}
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.TodoJava">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,127 @@
package com.example.todojava;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.example.todojava.data.Todo;
import com.example.todojava.databinding.ActivityMainBinding;
import com.google.android.material.snackbar.Snackbar;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private TodoViewModel viewModel;
private TodoAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
viewModel = new ViewModelProvider(this).get(TodoViewModel.class);
setupRecyclerView();
setupFilterChips();
setupFab();
observeData();
}
private void setupRecyclerView() {
adapter = new TodoAdapter();
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
binding.recyclerView.setAdapter(adapter);
adapter.setOnTodoClickListener(todo -> {
TodoFormDialogFragment dialog = TodoFormDialogFragment.newInstance(todo);
dialog.show(getSupportFragmentManager(), "edit_todo");
});
adapter.setOnTodoCheckedListener(todo -> viewModel.toggleCompleted(todo));
// Swipe to delete
SwipeToDeleteCallback swipeCallback = new SwipeToDeleteCallback(position -> {
Todo todo = adapter.getCurrentList().get(position);
viewModel.delete(todo);
Snackbar.make(binding.getRoot(), R.string.todo_deleted, Snackbar.LENGTH_LONG)
.setAction(R.string.undo, v -> viewModel.insert(todo))
.show();
});
new ItemTouchHelper(swipeCallback).attachToRecyclerView(binding.recyclerView);
}
private void setupFilterChips() {
binding.chipAll.setOnClickListener(v -> viewModel.setFilter(TodoViewModel.Filter.ALL));
binding.chipActive.setOnClickListener(v -> viewModel.setFilter(TodoViewModel.Filter.ACTIVE));
binding.chipDone.setOnClickListener(v -> viewModel.setFilter(TodoViewModel.Filter.DONE));
}
private void setupFab() {
binding.fab.setOnClickListener(v -> {
TodoFormDialogFragment dialog = TodoFormDialogFragment.newInstance();
dialog.show(getSupportFragmentManager(), "add_todo");
});
}
private void observeData() {
viewModel.getFilteredTodos().observe(this, todos -> {
adapter.submitList(todos);
updateEmptyState(todos);
});
// Update chip text with counts
viewModel.getAllCount().observe(this, count -> {
String text = getString(R.string.filter_all) + " (" + count + ")";
binding.chipAll.setText(text);
});
viewModel.getActiveCount().observe(this, count -> {
String text = getString(R.string.filter_active) + " (" + count + ")";
binding.chipActive.setText(text);
});
viewModel.getCompletedCount().observe(this, count -> {
String text = getString(R.string.filter_done) + " (" + count + ")";
binding.chipDone.setText(text);
});
}
private void updateEmptyState(List<Todo> todos) {
boolean isEmpty = todos == null || todos.isEmpty();
binding.emptyState.getRoot().setVisibility(isEmpty ? View.VISIBLE : View.GONE);
binding.recyclerView.setVisibility(isEmpty ? View.GONE : View.VISIBLE);
if (isEmpty) {
TodoViewModel.Filter filter = viewModel.getCurrentFilter().getValue();
if (filter == null) filter = TodoViewModel.Filter.ALL;
View emptyRoot = binding.emptyState.getRoot();
android.widget.TextView titleView = emptyRoot.findViewById(R.id.emptyTitle);
android.widget.TextView subtitleView = emptyRoot.findViewById(R.id.emptySubtitle);
switch (filter) {
case ACTIVE:
titleView.setText(R.string.empty_active_title);
subtitleView.setText(R.string.empty_active_subtitle);
break;
case DONE:
titleView.setText(R.string.empty_done_title);
subtitleView.setText(R.string.empty_done_subtitle);
break;
default:
titleView.setText(R.string.empty_state_title);
subtitleView.setText(R.string.empty_state_subtitle);
break;
}
}
}
}
@@ -0,0 +1,84 @@
package com.example.todojava;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.ColorDrawable;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
public interface OnSwipeListener {
void onSwiped(int position);
}
private final OnSwipeListener listener;
private final ColorDrawable background;
private final Paint textPaint;
public SwipeToDeleteCallback(OnSwipeListener listener) {
super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
this.listener = listener;
this.background = new ColorDrawable(Color.parseColor("#E53935"));
this.textPaint = new Paint();
this.textPaint.setColor(Color.WHITE);
this.textPaint.setTextSize(40f);
this.textPaint.setAntiAlias(true);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
listener.onSwiped(viewHolder.getAdapterPosition());
}
@Override
public void onChildDraw(@NonNull Canvas c,
@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
float dX, float dY,
int actionState, boolean isCurrentlyActive) {
View itemView = viewHolder.itemView;
if (dX > 0) {
// Swiping right
background.setBounds(itemView.getLeft(), itemView.getTop(),
itemView.getLeft() + (int) dX, itemView.getBottom());
} else if (dX < 0) {
// Swiping left
background.setBounds(itemView.getRight() + (int) dX, itemView.getTop(),
itemView.getRight(), itemView.getBottom());
} else {
background.setBounds(0, 0, 0, 0);
}
background.draw(c);
// Draw "Delete" text
String deleteText = "Delete";
float textWidth = textPaint.measureText(deleteText);
float textY = itemView.getTop() + (itemView.getHeight() + textPaint.getTextSize()) / 2f - 4f;
if (dX > 0) {
float textX = itemView.getLeft() + 32f;
c.drawText(deleteText, textX, textY, textPaint);
} else if (dX < 0) {
float textX = itemView.getRight() - textWidth - 32f;
c.drawText(deleteText, textX, textY, textPaint);
}
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
@@ -0,0 +1,131 @@
package com.example.todojava;
import android.graphics.Paint;
import android.graphics.drawable.GradientDrawable;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.example.todojava.data.Todo;
import com.example.todojava.databinding.ItemTodoBinding;
public class TodoAdapter extends ListAdapter<Todo, TodoAdapter.TodoViewHolder> {
public interface OnTodoClickListener {
void onTodoClick(Todo todo);
}
public interface OnTodoCheckedListener {
void onTodoChecked(Todo todo);
}
private OnTodoClickListener clickListener;
private OnTodoCheckedListener checkedListener;
public TodoAdapter() {
super(DIFF_CALLBACK);
}
public void setOnTodoClickListener(OnTodoClickListener listener) {
this.clickListener = listener;
}
public void setOnTodoCheckedListener(OnTodoCheckedListener listener) {
this.checkedListener = listener;
}
private static final DiffUtil.ItemCallback<Todo> DIFF_CALLBACK =
new DiffUtil.ItemCallback<Todo>() {
@Override
public boolean areItemsTheSame(@NonNull Todo oldItem, @NonNull Todo newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull Todo oldItem, @NonNull Todo newItem) {
return oldItem.getTitle().equals(newItem.getTitle())
&& oldItem.isCompleted() == newItem.isCompleted()
&& oldItem.getCategory().equals(newItem.getCategory())
&& oldItem.getPriority().equals(newItem.getPriority());
}
};
@NonNull
@Override
public TodoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemTodoBinding binding = ItemTodoBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false);
return new TodoViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull TodoViewHolder holder, int position) {
Todo todo = getItem(position);
holder.bind(todo);
}
class TodoViewHolder extends RecyclerView.ViewHolder {
private final ItemTodoBinding binding;
TodoViewHolder(ItemTodoBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(Todo todo) {
binding.textTitle.setText(todo.getTitle());
binding.chipCategory.setText(todo.getCategory());
// Handle completed state
binding.checkBox.setOnCheckedChangeListener(null);
binding.checkBox.setChecked(todo.isCompleted());
if (todo.isCompleted()) {
binding.textTitle.setPaintFlags(
binding.textTitle.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
binding.textTitle.setTextColor(
ContextCompat.getColor(itemView.getContext(), R.color.completed_text));
} else {
binding.textTitle.setPaintFlags(
binding.textTitle.getPaintFlags() & ~Paint.STRIKE_THRU_TEXT_FLAG);
binding.textTitle.setTextColor(
ContextCompat.getColor(itemView.getContext(), R.color.on_surface));
}
// Priority dot color
int priorityColor;
switch (todo.getPriority()) {
case "High":
priorityColor = ContextCompat.getColor(itemView.getContext(), R.color.priority_high);
break;
case "Medium":
priorityColor = ContextCompat.getColor(itemView.getContext(), R.color.priority_medium);
break;
default:
priorityColor = ContextCompat.getColor(itemView.getContext(), R.color.priority_low);
break;
}
GradientDrawable dot = (GradientDrawable) binding.priorityDot.getBackground().mutate();
dot.setColor(priorityColor);
// Click listener for editing
itemView.setOnClickListener(v -> {
if (clickListener != null) {
clickListener.onTodoClick(todo);
}
});
// Checkbox listener
binding.checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (checkedListener != null) {
checkedListener.onTodoChecked(todo);
}
});
}
}
}
@@ -0,0 +1,156 @@
package com.example.todojava;
import android.app.Dialog;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import com.example.todojava.data.Todo;
import com.example.todojava.databinding.DialogTodoFormBinding;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
public class TodoFormDialogFragment extends DialogFragment {
private static final String ARG_TODO_ID = "todo_id";
private static final String ARG_TODO_TITLE = "todo_title";
private static final String ARG_TODO_CATEGORY = "todo_category";
private static final String ARG_TODO_PRIORITY = "todo_priority";
private static final String ARG_TODO_COMPLETED = "todo_completed";
private static final String ARG_TODO_CREATED_AT = "todo_created_at";
private DialogTodoFormBinding binding;
private TodoViewModel viewModel;
public static TodoFormDialogFragment newInstance() {
return new TodoFormDialogFragment();
}
public static TodoFormDialogFragment newInstance(Todo todo) {
TodoFormDialogFragment fragment = new TodoFormDialogFragment();
Bundle args = new Bundle();
args.putLong(ARG_TODO_ID, todo.getId());
args.putString(ARG_TODO_TITLE, todo.getTitle());
args.putString(ARG_TODO_CATEGORY, todo.getCategory());
args.putString(ARG_TODO_PRIORITY, todo.getPriority());
args.putBoolean(ARG_TODO_COMPLETED, todo.isCompleted());
args.putLong(ARG_TODO_CREATED_AT, todo.getCreatedAt());
fragment.setArguments(args);
return fragment;
}
private boolean isEditing() {
return getArguments() != null && getArguments().containsKey(ARG_TODO_ID);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
binding = DialogTodoFormBinding.inflate(LayoutInflater.from(getContext()));
viewModel = new ViewModelProvider(requireActivity()).get(TodoViewModel.class);
if (isEditing()) {
prefillForm();
}
String title = isEditing() ? getString(R.string.edit_todo) : getString(R.string.add_todo);
return new MaterialAlertDialogBuilder(requireContext())
.setTitle(title)
.setView(binding.getRoot())
.setPositiveButton(R.string.save, (dialog, which) -> saveTodo())
.setNegativeButton(R.string.cancel, null)
.create();
}
private void prefillForm() {
Bundle args = getArguments();
if (args == null) return;
binding.editTitle.setText(args.getString(ARG_TODO_TITLE, ""));
String category = args.getString(ARG_TODO_CATEGORY, "Personal");
switch (category) {
case "Personal":
binding.chipPersonal.setChecked(true);
break;
case "Work":
binding.chipWork.setChecked(true);
break;
case "Shopping":
binding.chipShopping.setChecked(true);
break;
case "Health":
binding.chipHealth.setChecked(true);
break;
case "Learning":
binding.chipLearning.setChecked(true);
break;
}
String priority = args.getString(ARG_TODO_PRIORITY, "Medium");
switch (priority) {
case "Low":
binding.chipLow.setChecked(true);
break;
case "Medium":
binding.chipMedium.setChecked(true);
break;
case "High":
binding.chipHigh.setChecked(true);
break;
}
}
private String getSelectedCategory() {
int checkedId = binding.categoryChipGroup.getCheckedChipId();
if (checkedId == R.id.chipPersonal) return "Personal";
if (checkedId == R.id.chipWork) return "Work";
if (checkedId == R.id.chipShopping) return "Shopping";
if (checkedId == R.id.chipHealth) return "Health";
if (checkedId == R.id.chipLearning) return "Learning";
return "Personal";
}
private String getSelectedPriority() {
int checkedId = binding.priorityChipGroup.getCheckedChipId();
if (checkedId == R.id.chipLow) return "Low";
if (checkedId == R.id.chipMedium) return "Medium";
if (checkedId == R.id.chipHigh) return "High";
return "Medium";
}
private void saveTodo() {
String title = binding.editTitle.getText() != null
? binding.editTitle.getText().toString().trim() : "";
if (title.isEmpty()) {
return;
}
String category = getSelectedCategory();
String priority = getSelectedPriority();
if (isEditing()) {
Bundle args = getArguments();
Todo todo = new Todo(title, category, priority);
todo.setId(args.getLong(ARG_TODO_ID));
todo.setCompleted(args.getBoolean(ARG_TODO_COMPLETED, false));
todo.setCreatedAt(args.getLong(ARG_TODO_CREATED_AT, System.currentTimeMillis()));
viewModel.update(todo);
} else {
Todo todo = new Todo(title, category, priority);
viewModel.insert(todo);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
@@ -0,0 +1,123 @@
package com.example.todojava;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
import com.example.todojava.data.Todo;
import com.example.todojava.data.TodoDao;
import com.example.todojava.data.TodoDatabase;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TodoViewModel extends AndroidViewModel {
public enum Filter {
ALL, ACTIVE, DONE
}
private final TodoDao todoDao;
private final ExecutorService executor;
private final LiveData<List<Todo>> allTodos;
private final LiveData<List<Todo>> activeTodos;
private final LiveData<List<Todo>> completedTodos;
private final LiveData<Integer> allCount;
private final LiveData<Integer> activeCount;
private final LiveData<Integer> completedCount;
private final MutableLiveData<Filter> currentFilter = new MutableLiveData<>(Filter.ALL);
private final MediatorLiveData<List<Todo>> filteredTodos = new MediatorLiveData<>();
public TodoViewModel(@NonNull Application application) {
super(application);
TodoDatabase db = TodoDatabase.getInstance(application);
todoDao = db.todoDao();
executor = Executors.newSingleThreadExecutor();
allTodos = todoDao.getAllTodos();
activeTodos = todoDao.getActiveTodos();
completedTodos = todoDao.getCompletedTodos();
allCount = todoDao.getAllCount();
activeCount = todoDao.getActiveCount();
completedCount = todoDao.getCompletedCount();
filteredTodos.addSource(allTodos, todos -> {
if (currentFilter.getValue() == Filter.ALL) {
filteredTodos.setValue(todos);
}
});
filteredTodos.addSource(activeTodos, todos -> {
if (currentFilter.getValue() == Filter.ACTIVE) {
filteredTodos.setValue(todos);
}
});
filteredTodos.addSource(completedTodos, todos -> {
if (currentFilter.getValue() == Filter.DONE) {
filteredTodos.setValue(todos);
}
});
filteredTodos.addSource(currentFilter, filter -> {
switch (filter) {
case ALL:
filteredTodos.setValue(allTodos.getValue());
break;
case ACTIVE:
filteredTodos.setValue(activeTodos.getValue());
break;
case DONE:
filteredTodos.setValue(completedTodos.getValue());
break;
}
});
}
public LiveData<List<Todo>> getFilteredTodos() {
return filteredTodos;
}
public LiveData<Integer> getAllCount() {
return allCount;
}
public LiveData<Integer> getActiveCount() {
return activeCount;
}
public LiveData<Integer> getCompletedCount() {
return completedCount;
}
public MutableLiveData<Filter> getCurrentFilter() {
return currentFilter;
}
public void setFilter(Filter filter) {
currentFilter.setValue(filter);
}
public void insert(Todo todo) {
executor.execute(() -> todoDao.insert(todo));
}
public void update(Todo todo) {
executor.execute(() -> todoDao.update(todo));
}
public void delete(Todo todo) {
executor.execute(() -> todoDao.delete(todo));
}
public void toggleCompleted(Todo todo) {
todo.setCompleted(!todo.isCompleted());
executor.execute(() -> todoDao.update(todo));
}
}
@@ -0,0 +1,88 @@
package com.example.todojava.data;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;
@Entity(tableName = "todos")
public class Todo {
@PrimaryKey(autoGenerate = true)
public long id;
@ColumnInfo(name = "title")
public String title;
@ColumnInfo(name = "completed")
public boolean completed;
@ColumnInfo(name = "category")
public String category;
@ColumnInfo(name = "priority")
public String priority;
@ColumnInfo(name = "created_at")
public long createdAt;
public Todo() {
}
@Ignore
public Todo(String title, String category, String priority) {
this.title = title;
this.completed = false;
this.category = category;
this.priority = priority;
this.createdAt = System.currentTimeMillis();
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public String getPriority() {
return priority;
}
public void setPriority(String priority) {
this.priority = priority;
}
public long getCreatedAt() {
return createdAt;
}
public void setCreatedAt(long createdAt) {
this.createdAt = createdAt;
}
}
@@ -0,0 +1,44 @@
package com.example.todojava.data;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
@Dao
public interface TodoDao {
@Query("SELECT * FROM todos ORDER BY created_at DESC")
LiveData<List<Todo>> getAllTodos();
@Query("SELECT * FROM todos WHERE completed = 0 ORDER BY created_at DESC")
LiveData<List<Todo>> getActiveTodos();
@Query("SELECT * FROM todos WHERE completed = 1 ORDER BY created_at DESC")
LiveData<List<Todo>> getCompletedTodos();
@Query("SELECT COUNT(*) FROM todos")
LiveData<Integer> getAllCount();
@Query("SELECT COUNT(*) FROM todos WHERE completed = 0")
LiveData<Integer> getActiveCount();
@Query("SELECT COUNT(*) FROM todos WHERE completed = 1")
LiveData<Integer> getCompletedCount();
@Insert
void insert(Todo todo);
@Update
void update(Todo todo);
@Delete
void delete(Todo todo);
@Query("SELECT * FROM todos WHERE id = :id")
Todo getTodoById(long id);
}
@@ -0,0 +1,30 @@
package com.example.todojava.data;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
@Database(entities = {Todo.class}, version = 1, exportSchema = false)
public abstract class TodoDatabase extends RoomDatabase {
public abstract TodoDao todoDao();
private static volatile TodoDatabase INSTANCE;
public static TodoDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (TodoDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
TodoDatabase.class,
"todo_database"
).build();
}
}
}
return INSTANCE;
}
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#43A047" />
<size
android:width="12dp"
android:height="12dp" />
</shape>
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/primary"
app:title="@string/toolbar_title"
app:titleTextColor="@color/on_primary" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.google.android.material.chip.ChipGroup
android:id="@+id/filterChipGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
app:singleSelection="true"
app:selectionRequired="true"
app:chipSpacingHorizontal="8dp">
<com.google.android.material.chip.Chip
android:id="@+id/chipAll"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/filter_all"
android:checked="true" />
<com.google.android.material.chip.Chip
android:id="@+id/chipActive"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/filter_active" />
<com.google.android.material.chip.Chip
android:id="@+id/chipDone"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/filter_done" />
</com.google.android.material.chip.ChipGroup>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/divider" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="8dp"
tools:listitem="@layout/item_todo" />
<include
android:id="@+id/emptyState"
layout="@layout/empty_state"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
android:contentDescription="@string/add_todo"
app:srcCompat="@android:drawable/ic_input_add"
app:tint="@color/on_primary"
app:backgroundTint="@color/primary" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/titleInputLayout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/title_hint">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapSentences"
android:maxLines="2" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/category_label"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="@color/on_surface"
android:layout_marginTop="20dp"
android:layout_marginBottom="8dp" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/categoryChipGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:singleSelection="true"
app:selectionRequired="true"
app:chipSpacingHorizontal="8dp">
<com.google.android.material.chip.Chip
android:id="@+id/chipPersonal"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/category_personal"
android:checked="true" />
<com.google.android.material.chip.Chip
android:id="@+id/chipWork"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/category_work" />
<com.google.android.material.chip.Chip
android:id="@+id/chipShopping"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/category_shopping" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHealth"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/category_health" />
<com.google.android.material.chip.Chip
android:id="@+id/chipLearning"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/category_learning" />
</com.google.android.material.chip.ChipGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/priority_label"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="@color/on_surface"
android:layout_marginTop="20dp"
android:layout_marginBottom="8dp" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/priorityChipGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:singleSelection="true"
app:selectionRequired="true"
app:chipSpacingHorizontal="8dp">
<com.google.android.material.chip.Chip
android:id="@+id/chipLow"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/priority_low" />
<com.google.android.material.chip.Chip
android:id="@+id/chipMedium"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/priority_medium"
android:checked="true" />
<com.google.android.material.chip.Chip
android:id="@+id/chipHigh"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/priority_high" />
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
</ScrollView>
@@ -0,0 +1,37 @@
<?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:gravity="center"
android:padding="32dp">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@android:drawable/ic_menu_agenda"
android:alpha="0.4"
android:importantForAccessibility="no" />
<TextView
android:id="@+id/emptyTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/empty_state_title"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/on_surface"
android:layout_marginTop="16dp" />
<TextView
android:id="@+id/emptySubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/empty_state_subtitle"
android:textSize="14sp"
android:textColor="@color/on_surface_variant"
android:layout_marginTop="8dp"
android:gravity="center" />
</LinearLayout>
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_marginVertical="4dp"
app:cardElevation="2dp"
app:cardCornerRadius="12dp"
android:clickable="true"
android:focusable="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="12dp">
<CheckBox
android:id="@+id/checkBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="0dp"
android:minHeight="0dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="8dp">
<TextView
android:id="@+id/textTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/on_surface"
tools:text="Buy groceries" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="4dp">
<com.google.android.material.chip.Chip
android:id="@+id/chipCategory"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="11sp"
app:chipMinHeight="24dp"
app:chipMinTouchTargetSize="0dp"
tools:text="Shopping" />
</LinearLayout>
</LinearLayout>
<View
android:id="@+id/priorityDot"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginStart="8dp"
android:background="@drawable/priority_dot" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primary">#6200EE</color>
<color name="primary_variant">#3700B3</color>
<color name="on_primary">#FFFFFF</color>
<color name="secondary">#03DAC6</color>
<color name="on_secondary">#000000</color>
<color name="background">#FAFAFA</color>
<color name="surface">#FFFFFF</color>
<color name="on_surface">#1C1B1F</color>
<color name="on_surface_variant">#49454F</color>
<color name="priority_low">#43A047</color>
<color name="priority_medium">#FB8C00</color>
<color name="priority_high">#E53935</color>
<color name="completed_text">#9E9E9E</color>
<color name="divider">#E0E0E0</color>
</resources>
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Todo Java</string>
<string name="toolbar_title">Todos \u2013 Java Android</string>
<string name="filter_all">All</string>
<string name="filter_active">Active</string>
<string name="filter_done">Done</string>
<string name="add_todo">Add Todo</string>
<string name="edit_todo">Edit Todo</string>
<string name="title_hint">What needs to be done?</string>
<string name="category_label">Category</string>
<string name="priority_label">Priority</string>
<string name="save">Save</string>
<string name="cancel">Cancel</string>
<string name="empty_state_title">No todos yet</string>
<string name="empty_state_subtitle">Tap the + button to add your first todo</string>
<string name="empty_active_title">All done!</string>
<string name="empty_active_subtitle">No active todos remaining</string>
<string name="empty_done_title">Nothing completed</string>
<string name="empty_done_subtitle">Complete a todo to see it here</string>
<string name="category_personal">Personal</string>
<string name="category_work">Work</string>
<string name="category_shopping">Shopping</string>
<string name="category_health">Health</string>
<string name="category_learning">Learning</string>
<string name="priority_low">Low</string>
<string name="priority_medium">Medium</string>
<string name="priority_high">High</string>
<string name="delete">Delete</string>
<string name="todo_deleted">Todo deleted</string>
<string name="undo">Undo</string>
</resources>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.TodoJava" parent="Theme.Material3.Light.NoActionBar">
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryVariant">@color/primary_variant</item>
<item name="colorOnPrimary">@color/on_primary</item>
<item name="colorSecondary">@color/secondary</item>
<item name="colorOnSecondary">@color/on_secondary</item>
<item name="android:colorBackground">@color/background</item>
<item name="colorSurface">@color/surface</item>
<item name="colorOnSurface">@color/on_surface</item>
<item name="colorOnSurfaceVariant">@color/on_surface_variant</item>
</style>
</resources>