פונקציות עיקריות ב-PresenceActivity:
protected void onCreate(Bundle savedInstanceState) {
public void onPartialResults(String text) {
public void onResults(String text) {
/// was used in the dev process before Speech was introduced.
private void uploadMockDataToFirebase() {
/// was used in the dev process before speech was introduced.
/// to work with this you need to revive the btnDebug that was
/// btn deleted on build of 18Jul 07:48am
private void populateMockData() {
/**
* 🔄 Pushes a single transcript entry to RTDB using rounded-time + lesson key
* /P{YY}_{uid}/W{current week}/{MMDDMMM_HHmmL#}/
* /P25_tTe3W4vIjHe0HSqRXxAIUBxIzKg1/W29/0718Jul_0830L1
*/
private void pushRawTranscript(String newEntry) {
/**
* 🔍 identifying the most probable class.
*/
private Boolean detectClass() {
/**
* Parses a single transcript line and updates student statuses.
*/
private void analyzeTranscriptLine(String line) {
/**
* Builds the summary strings and updates the TextViews.
*/
private void updateUI() {
Code Files
DoneTasksActivity.java
Path: DoneTasksActivity.java
package com.example.tasks.Activities;
import static com.example.tasks.FBRef.refDoneTasks;
import static com.example.tasks.FBRef.refTasks;
import static com.example.tasks.FBRef.refYears;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ProgressDialog;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import com.example.tasks.Adapters.DoneTaskAdapter;
import com.example.tasks.Adapters.TaskAdapter;
import com.example.tasks.Obj.MasterActivity;
import com.example.tasks.Obj.Task;
import com.example.tasks.R;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList;
/**
* @author Albert Levy albert.school2015@gmail.com
* @version 2.1
* @since 9/3/2024
* <p>
* Activity for displaying tasks that have been marked as "Done".
* <p>
* This activity extends {@link MasterActivity} to inherit its common options menu.
* It fetches and displays a list of completed tasks from the Firebase Realtime Database
* node "Done_Tasks". Users can filter the displayed tasks by academic year using a
* {@link Spinner}. The list of done tasks can also be sorted by class name or by the
* date they were marked as done.
* <p>
* Features:
* <ul>
* <li>Displays a {@link Spinner} to select an academic year to view its done tasks.</li>
* <li>Fetches and lists done tasks using a {@link DoneTaskAdapter}.</li>
* <li>Allows sorting of the displayed tasks by "Class Name" (ascending/descending).</li>
* <li>Allows sorting of the displayed tasks by "Date Checked" (ascending/descending).</li>
* <li>Shows a {@link ProgressDialog} while initially fetching data for a selected year.</li>
* <li>Retrieves the initially active year from {@link SharedPreferences}.</li>
* </ul>
*
* @see MasterActivity
* @see DoneTaskAdapter
* @see Task
* @see SharedPreferences
* @see com.example.tasks.FBRef
*/
public class DoneTasksActivity extends MasterActivity implements AdapterView.OnItemSelectedListener {
private Spinner spYear;
private TextView tVClass, tVChecked;
private ListView lVDone;
private ProgressDialog pd;
private ArrayList<Integer> years;
private ArrayAdapter<Integer> yearsAdp;
private ArrayList<Task> doneTasksList;
private DoneTaskAdapter doneTaskAdp;
private SharedPreferences settings;
private int activeYear;
private ValueEventListener vel;
private boolean orderChecked = false;
private boolean orderClass = false;
/**
* Listener for fetching the list of available academic years from Firebase.
* Populates the years spinner.
*/
private ValueEventListener velYears = new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dS) {
years.clear();
for(DataSnapshot data : dS.getChildren()) {
years.add(Integer.parseInt(data.getKey()));
}
yearsAdp.notifyDataSetChanged();
if (activeYear != 1970) {
spYear.setSelection(years.indexOf(activeYear));
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {}
};
/**
* Called when the activity is first created.
* Initializes views, SharedPreferences, and sets up adapters.
*
* @param savedInstanceState If the activity is being re-initialized after
* previously being shut down then this Bundle contains the data it most
* recently supplied in {@link #onSaveInstanceState}.
* <b><i>Note: Otherwise it is null.</i></b>
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_done_tasks);
settings=getSharedPreferences("PREFS_NAME",MODE_PRIVATE);
initViews();
}
/**
* Called when the activity is becoming visible to the user.
* If a valid {@code activeYear} is set, it attaches a Firebase ValueEventListener
* to listen for changes in the done tasks for that year.
*/
@Override
protected void onStart() {
super.onStart();
refDoneTasks.child(String.valueOf(activeYear)).addValueEventListener(vel);
}
/**
* Called when the activity is no longer visible to the user.
* Removes the Firebase ValueEventListener for done tasks to prevent memory leaks
* and unnecessary background updates.
*/
@Override
protected void onStop() {
super.onStop();
refDoneTasks.child(String.valueOf(activeYear)).removeEventListener(vel);
}
/**
* Initializes UI components, adapters, and the primary ValueEventListener for done tasks.
* Retrieves the {@code activeYear} from SharedPreferences and fetches the list of
* available years for the spinner.
*/
private void initViews() {
spYear = findViewById(R.id.spYears);
tVClass = findViewById(R.id.tVClass);
tVChecked = findViewById(R.id.tVChecked);
lVDone = findViewById(R.id.lVDone);
years = new ArrayList<>();
years.clear();
yearsAdp = new ArrayAdapter<>(DoneTasksActivity.this,
android.R.layout.simple_spinner_dropdown_item, years);
spYear.setAdapter(yearsAdp);
spYear.setOnItemSelectedListener(this);
activeYear = settings.getInt("activeYear",1970);
refYears.addListenerForSingleValueEvent(velYears);
pd= ProgressDialog.show(this,"Connecting Database","Gathering data...",true);
doneTasksList = new ArrayList<Task>();
doneTaskAdp = new DoneTaskAdapter(DoneTasksActivity.this, doneTasksList);
lVDone.setAdapter(doneTaskAdp);
vel = new ValueEventListener() {
/**
* Called when data for done tasks for the {@code activeYear} changes in Firebase.
* Clears the current list and repopulates it with the fetched tasks.
* Notifies the {@code doneTaskAdp} to refresh the ListView.
* Dismisses the {@link ProgressDialog} after data is loaded.
*
* @param dS The DataSnapshot containing the done tasks.
* The expected structure is: Year -> DateEnded -> TaskObject.
*/
@Override
public void onDataChange(@NonNull DataSnapshot dS) {
doneTasksList.clear();
for(DataSnapshot dataDate : dS.getChildren()) {
for(DataSnapshot data : dataDate.getChildren()) {
doneTasksList.add(data.getValue(Task.class));
}
}
doneTaskAdp.notifyDataSetChanged();
if (pd != null) {
pd.dismiss();
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
};
}
/**
* Sorts the {@code doneTasksList} by class name.
* Toggles between ascending and descending order on subsequent clicks.
* Updates the text of the {@code tVClass} header to indicate the current sort order.
*
* @param view The TextView (tVClass) that was clicked.
*/
public void orderByClass(View view) {
if (orderClass) {
doneTasksList.sort((o1, o2)
-> o1.getClassName().compareTo(
o2.getClassName()));
tVClass.setText("Class ↑");
} else {
doneTasksList.sort((o1, o2)
-> o2.getClassName().compareTo(
o1.getClassName()));
tVClass.setText("Class ↓");
}
orderClass = !orderClass;
doneTaskAdp.notifyDataSetChanged();
}
/**
* Sorts the {@code doneTasksList} by the date they were marked as done (checked date).
* Toggles between ascending and descending order on subsequent clicks.
* Updates the text of the {@code tVChecked} header to indicate the current sort order.
*
* @param view The TextView (tVChecked) that was clicked.
*/
public void orderByChecked(View view) {
if (orderChecked) {
doneTasksList.sort((o1, o2)
-> o1.getDateChecked().compareTo(
o2.getDateChecked()));
tVChecked.setText("Checked ↑");
} else {
doneTasksList.sort((o1, o2)
-> o2.getDateChecked().compareTo(
o1.getDateChecked()));
tVChecked.setText("Checked ↓");
}
orderChecked = !orderChecked;
doneTaskAdp.notifyDataSetChanged();
}
/**
* Callback method to be invoked when an item in the year {@link Spinner} has been selected.
* Updates the {@code activeYear} to the newly selected year.
* Removes the Firebase listener from the previously selected year's done tasks (if any)
* and attaches a new listener for the done tasks of the newly selected year.
* Shows a {@link ProgressDialog} while new data is being fetched.
*
* @param adapterView The AdapterView where the selection happened.
* @param view The view within the AdapterView that was clicked.
* @param pos The position of the view in the adapter.
* @param l The row id of the item that was selected.
*/
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long l) {
activeYear = years.get(pos);
refDoneTasks.child(String.valueOf(activeYear)).addValueEventListener(vel);
}
/**
* Callback method to be invoked when the selection disappears from the year {@link Spinner}.
* Currently, this method has no specific implementation.
*
* @param adapterView The AdapterView that now contains no selected item.
*/
@Override
public void onNothingSelected(AdapterView<?> adapterView) {}
}
LoginActivity.java
Path: LoginActivity.java
package com.example.tasks.Activities;
import static com.example.tasks.FBRef.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.ClickableSpan;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.example.tasks.FBRef;
import com.example.tasks.Obj.User;
import com.example.tasks.R;
import com.example.tasks.models.Student;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthUserCollisionException;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
import com.google.firebase.database.ValueEventListener;
/**
* @author Albert Levy albert.school2015@gmail.com
* @version 2.1
* @see AppCompatActivity
* @see FirebaseAuth
* @see FirebaseDatabase
* @see SharedPreferences
* @since 9/3/2024 <p>
* Activity for handling user login and registration.
* <p>
* This activity allows users to either log in with existing credentials or
* register a new account using their email and password. It interacts with
* Firebase Authentication for user management and Firebase Realtime Database
* to store additional user information (username).
* <p>
* Features:
* <ul>
* <li>User login with email and password.</li>
* <li>New user registration with name, email, and password.</li>
* <li>"Stay connected" option to remember the user's login session using SharedPreferences.</li>
* <li>Retrieval of the last active year or setting a new active year for new users
* via {@link YearsActivity}.</li>
* <li>Dynamic UI changes to switch between login and registration forms.</li>
* </ul>
* Upon successful login or registration, the user is navigated to {@link MainActivity}.
*/
public class LoginActivity extends AppCompatActivity {
private TextView tVtitle, tVregister;
private EditText eTname, eTemail, eTpass;
private CheckBox cBstayconnect;
private Button btn;
private String name, email, password, uid;
private User userdb;
private Boolean stayConnect, registered;
private SharedPreferences settings;
private int activeYear = 1970;
private final int REQUEST_CODE = 100;
private FirebaseAuth refAuth;
private DatabaseReference profileRef;
/**
* Called when the activity is first created.
* Initializes views, SharedPreferences, and sets up the initial UI state for login.
*
* @param savedInstanceState If the activity is being re-initialized after
* previously being shut down then this Bundle contains the data it most
* recently supplied in {@link #onSaveInstanceState}.
* <b><i>Note: Otherwise it is null.</i></b>
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
settings = getSharedPreferences("PREFS_NAME", MODE_PRIVATE);
refAuth = FirebaseAuth.getInstance();
initViews();
stayConnect = false;
registered = true;
regoption();
}
/**
* Called when the activity is becoming visible to the user.
* <p>
* Checks if a user is already logged in and if the "stay connected" option
* was previously selected. If both conditions are true, it bypasses the login screen
* and navigates directly to {@link MainActivity}.
*/
@Override
protected void onStart() {
super.onStart();
boolean isChecked = settings.getBoolean("stayConnect", false);
FirebaseUser user = refAuth.getCurrentUser();
if (user != null && isChecked) {
FBRef.getUser(user);
// Launch preferred activity after loading students
profileRef = FirebaseDatabase.getInstance()
.getReference("Users")
.child(user.getUid());
profileRef.child("preferredActivity")
.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snap) {
String pref = snap.getValue(String.class);
Class<?> target;
if ("Tasks".equals(pref)) {
target = TaskActivity.class;
} else if ("Reports".equals(pref)) {
target = ReportsActivity.class;
} else {
target = PresenceActivity.class;
}
Intent i = new Intent(LoginActivity.this, target);
FBRef.loadAllStudents(() -> startActivity(i));
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
}
}
/**
* Initializes all UI view components by finding them by their ID.
*/
private void initViews() {
tVtitle = findViewById(R.id.tVtitle);
eTname = findViewById(R.id.eTname);
eTemail = findViewById(R.id.eTemail);
eTpass = findViewById(R.id.eTpass);
cBstayconnect = findViewById(R.id.cBstayconnect);
tVregister = findViewById(R.id.tVregister);
btn = findViewById(R.id.btn);
}
/**
* Called when the activity is no longer visible to the user.
* If the {@code stayConnect} flag is true (meaning the user logged in and chose
* to be remembered, and was not auto-logged out), this activity is finished.
* This typically happens if MainActivity is started from here.
*/
@Override
protected void onPause() {
super.onPause();
if (stayConnect) finish();
}
/**
* Sets up the clickable text ("Register here!") to switch to registration mode.
* When clicked, it updates the UI to show the registration form elements
* (e.g., name field, changes button text) and sets {@code registered} to false.
* It then calls {@link #logoption()} to set up the "Login here!" text.
*/
private void regoption() {
SpannableString ss = new SpannableString("Don't have an account? Register here!");
ClickableSpan span = new ClickableSpan() {
@Override
public void onClick(View textView) {
tVtitle.setText("Register");
eTname.setVisibility(View.VISIBLE);
btn.setText("Register");
registered = false;
logoption();
}
};
ss.setSpan(span, 24, 38, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tVregister.setText(ss);
tVregister.setMovementMethod(LinkMovementMethod.getInstance());
}
/**
* Sets up the clickable text ("Login here!") to switch to login mode.
* When clicked, it updates the UI to show the login form elements
* (e.g., hides name field, changes button text) and sets {@code registered} to true.
* It then calls {@link #regoption()} to set up the "Register here!" text.
*/
private void logoption() {
SpannableString ss = new SpannableString("Already have an account? Login here!");
ClickableSpan span = new ClickableSpan() {
@Override
public void onClick(View textView) {
tVtitle.setText("Login");
eTname.setVisibility(View.INVISIBLE);
btn.setText("Login");
registered = true;
regoption();
}
};
ss.setSpan(span, 26, 37, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tVregister.setText(ss);
tVregister.setMovementMethod(LinkMovementMethod.getInstance());
}
/**
* Handles the login or registration process when the main button is clicked.
* <p>
* If {@code registered} is true, it attempts to sign in the user with Firebase Authentication
* using the provided email and password.
* If {@code registered} is false, it attempts to create a new user account with Firebase
* Authentication and stores the user's name and UID in the Firebase Realtime Database.
* <p>
* Displays a {@link ProgressDialog} during the Firebase operations.
* On successful login/registration, it saves the "stay connected" preference,
* handles active year retrieval/setting, and navigates to {@link MainActivity}.
* Toasts are shown for success or failure messages.
*
* @param view The view that was clicked (the login/register button).
*/
public void logorreg(View view) {
if (registered) {
// Login flow
String email = eTemail.getText().toString();
String password = eTpass.getText().toString();
ProgressDialog pd = ProgressDialog.show(this, "Login", "Connecting...", true);
refAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(this, task -> {
pd.dismiss();
if (task.isSuccessful()) {
FirebaseUser user = refAuth.getCurrentUser();
FBRef.getUser(user);
settings.edit()
.putBoolean("stayConnect", cBstayconnect.isChecked())
.apply();
profileRef = FirebaseDatabase.getInstance()
.getReference("Users")
.child(user.getUid());
profileRef.child("preferredActivity")
.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snap) {
String pref = snap.getValue(String.class);
Class<?> target;
if ("Tasks".equals(pref)) {
target = TaskActivity.class;
} else if ("MainTask".equals(pref)) {
target = MainActivity.class;
} else if ("Profile".equals(pref)) {
target = ProfileActivity.class;
} else if ("Reports".equals(pref)) {
target = ReportsActivity.class;
} else {
target = PresenceActivity.class;
}
Intent i = new Intent(LoginActivity.this, target);
FBRef.loadAllStudents(() -> startActivity(i));
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
} else {
Toast.makeText(LoginActivity.this,
"E-mail or password are wrong!", Toast.LENGTH_LONG).show();
}
});
} else {
// Registration flow unchanged
name = eTname.getText().toString();
email = eTemail.getText().toString();
password = eTpass.getText().toString();
ProgressDialog pd = ProgressDialog.show(this, "Register", "Registering...", true);
refAuth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(this, task -> {
pd.dismiss();
if (task.isSuccessful()) {
FirebaseUser user = refAuth.getCurrentUser();
FBRef.getUser(user);
Log.d("PresenceActivity", "createUserWithEmail:success");
uid = user.getUid();
userdb = new User(uid, name);
refUsers.child(uid).setValue(userdb);
settings.edit()
.putBoolean("stayConnect", cBstayconnect.isChecked())
.apply();
Toast.makeText(LoginActivity.this, "Successful registration", Toast.LENGTH_SHORT).show();
setActiveYear();
} else if (task.getException() instanceof FirebaseAuthUserCollisionException) {
Toast.makeText(LoginActivity.this,
"User with e-mail already exists!", Toast.LENGTH_SHORT).show();
} else {
if (task.getException() instanceof FirebaseAuthUserCollisionException)
Toast.makeText(LoginActivity.this, "User with e-mail already exist!", Toast.LENGTH_SHORT).show();
else {
Log.w("PresenceActivity", "createUserWithEmail:failure", task.getException());
Toast.makeText(LoginActivity.this, "User create failed.", Toast.LENGTH_LONG).show();
}
}
});
}
}
/**
* Attempts to retrieve the last active year for the user from Firebase.
* This method is called if a logged-in user's active year is not found in local SharedPreferences.
* <p>
* It queries the "Years" node in Firebase for the latest entry.
* If an active year is found, it's saved to SharedPreferences, and the user is navigated to {@link PresenceActivity}.
* If no active year is found (e.g., for a user who previously registered but didn't set an active year),
* it calls {@link #setActiveYear()} to prompt the user to choose one.
*
* @deprecated This method seems to fetch a global last year, not user-specific.
* Consider fetching user-specific active year from their profile or redesigning.
* If it's intended to be a global last year, its usage after login is questionable.
*/
private void lostData() {
Query query = refYears.orderByKey().limitToLast(1);
query.get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
@Override
public void onComplete(@NonNull com.google.android.gms.tasks.Task<DataSnapshot> tsk) {
if (tsk.isSuccessful()) {
DataSnapshot dS = tsk.getResult();
for (DataSnapshot data : dS.getChildren()) {
activeYear = Integer.parseInt(data.getKey());
}
if (activeYear == 1970) {
setActiveYear();
} else {
SharedPreferences.Editor editor = settings.edit();
editor.putInt("activeYear", activeYear);
editor.putBoolean("stayConnect", cBstayconnect.isChecked());
editor.commit();
Intent si = new Intent(LoginActivity.this, PresenceActivity.class);
startActivity(si);
}
} else {
Log.e("firebase", "Error getting data", tsk.getException());
}
}
});
}
/**
* Navigates to {@link YearsActivity} to allow a new user to set their initial active year.
* The result (the selected year) is expected back in {@link #onActivityResult(int, int, Intent)}.
*/
private void setActiveYear() {
Intent sifr = new Intent(LoginActivity.this, YearsActivity.class);
sifr.putExtra("isNewUser", true);
startActivityForResult(sifr, REQUEST_CODE);
}
/**
* Callback for the result from starting an activity with {@link #startActivityForResult(Intent, int)}.
* <p>
* This method handles the result from {@link YearsActivity}. If the result is {@code Activity.RESULT_OK}
* and a valid active year is returned, it saves this year to SharedPreferences,
* updates the "stay connected" preference, and navigates to {@link PresenceActivity}.
*
* @param requestCode The integer request code originally supplied to
* startActivityForResult(), allowing you to identify who this
* result came from.
* @param resultCode The integer result code returned by the child activity
* through its setResult().
* @param data An Intent, which can return result data to the caller
* (various data can be attached to Intent "extras").
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {
if (data != null) {
activeYear = data.getIntExtra("activeYear", 1970);
SharedPreferences.Editor editor = settings.edit();
editor.putInt("activeYear", activeYear);
editor.putBoolean("stayConnect", cBstayconnect.isChecked());
editor.commit();
final Intent si = new Intent(LoginActivity.this, PresenceActivity.class);
// delay navigation until students are ready
FBRef.loadAllStudents(() -> startActivity(si));
}
}
}
}
}
MaakavActivity.java
Path: MaakavActivity.java
package com.example.tasks.Activities;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.tasks.Obj.MasterActivity;
import com.example.tasks.R;
public class MaakavActivity extends MasterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_maakav);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}
MainActivity.java
Path: MainActivity.java
package com.example.tasks.Activities;
import static com.example.tasks.FBRef.refDoneTasks;
import static com.example.tasks.FBRef.refTasks;
import static com.example.tasks.FBRef.refUsers;
import static com.example.tasks.Utilities.db2Dsiplay;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.tasks.FBRef;
import com.example.tasks.Obj.MasterActivity;
import com.example.tasks.Obj.Task;
import com.example.tasks.Obj.User;
import com.example.tasks.R;
import com.example.tasks.Adapters.TaskAdapter;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.ValueEventListener;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
/**
* @author Albert Levy albert.school2015@gmail.com
* @version 2.1
* @since 9/3/2024
* <p>
* The main activity of the application, displaying a list of active tasks for the current user.
* <p>
* This activity extends {@link MasterActivity} to inherit its common options menu.
* It retrieves and displays tasks from Firebase Realtime Database for the currently
* selected "active year". Users can interact with tasks through a context menu
* to edit, delete, or mark them as done.
* <p>
* Features:
* <ul>
* <li>Displays a personalized welcome message with the user's name.</li>
* <li>Fetches and lists active tasks for the current {@code activeYear} using a {@link TaskAdapter}.</li>
* <li>Provides an "Add Task" button to navigate to {@link TaskActivity} for creating new tasks.</li>
* <li>Implements a context menu on task items for actions: Edit, Delete, Done.</li>
* <li>Handles task completion by moving tasks from the active tasks list to a "done tasks" list in Firebase.</li>
* <li>Shows a {@link ProgressDialog} while initially fetching data.</li>
* </ul>
* The {@code activeYear} is retrieved from {@link SharedPreferences}.
*
* @see MasterActivity
* @see TaskAdapter
* @see Task
* @see User
* @see FBRef
*/
public class MainActivity extends MasterActivity implements AdapterView.OnItemClickListener,
View.OnCreateContextMenuListener {
private TextView tVMainHeader;
private ListView lVMain;
private ProgressDialog pd;
private User user;
/**
* Static list holding the current tasks displayed in the ListView.
* Making this static can have implications if multiple instances of MainActivity
* could potentially exist or if data persistence across activity instances is desired
* without relying on standard Android lifecycle methods like onSaveInstanceState.
* Consider if a ViewModel or non-static field with proper state handling is more appropriate.
*/
public static ArrayList<Task> tasksList;
private TaskAdapter taskAdp;
private int choose;
private int activeYear;
private ValueEventListener vel;
SharedPreferences settings;
/**
* Called when the activity is first created.
* Initializes views, SharedPreferences, and fetches initial user and task data.
*
* @param savedInstanceState If the activity is being re-initialized after
* previously being shut down then this Bundle contains the data it most
* recently supplied in {@link #onSaveInstanceState}.
* <b><i>Note: Otherwise it is null.</i></b>
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
settings = getSharedPreferences("PREFS_NAME",MODE_PRIVATE);
initViews();
}
/**
* Initializes UI components, sets up the ListView with its adapter,
* fetches the current user's details, and loads tasks for the active year.
* <p>
* A {@link ProgressDialog} is shown while data is being fetched from Firebase.
* The {@code activeYear} is read from SharedPreferences.
* A {@link ValueEventListener} is attached to Firebase to listen for real-time
* updates to the tasks list.
*/
private void initViews() {
tVMainHeader = findViewById(R.id.tVMainHeader);
lVMain = findViewById(R.id.lVMain);
tasksList = new ArrayList<Task>();
taskAdp = new TaskAdapter(MainActivity.this, tasksList);
lVMain.setAdapter(taskAdp);
lVMain.setOnItemClickListener(this);
registerForContextMenu(lVMain);
pd=ProgressDialog.show(this,"Connecting Database","Gathering data...",true);
activeYear = settings.getInt("activeYear",1970);
refUsers.child(FBRef.uid).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>(){
@Override
public void onComplete(@NonNull com.google.android.gms.tasks.Task<DataSnapshot> tsk) {
if (tsk.isSuccessful()) {
user = tsk.getResult().getValue(User.class);
tVMainHeader.setText("Hi "+user.getUsername()+",\nYour active tasks:");
}
else {
Log.e("firebase", "Error getting data", tsk.getException());
}
}
});
vel = new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dS) {
tasksList.clear();
for(DataSnapshot dataFull : dS.getChildren()) {
for(DataSnapshot dataDate : dataFull.getChildren()) {
for(DataSnapshot data : dataDate.getChildren()) {
tasksList.add(data.getValue(Task.class));
}
}
}
taskAdp.notifyDataSetChanged();
if (pd != null) {
pd.dismiss();
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {}
};
refTasks.child(String.valueOf(activeYear)).addValueEventListener(vel);
}
/**
* Called when the activity will start interacting with the user.
* Re-attaches the Firebase ValueEventListener to ensure data is fresh if
* the activity was paused and resumed.
*/
@Override
protected void onResume() {
super.onResume();
refTasks.child(String.valueOf(activeYear)).addValueEventListener(vel);
}
/**
* Called when the system is about to start resuming a previous activity.
* Removes the Firebase ValueEventListener to prevent unnecessary background updates
* and potential memory leaks.
*/
@Override
protected void onStop() {
super.onStop();
refTasks.child(String.valueOf(activeYear)).removeEventListener(vel);
}
/**
* Handles the "Add Task" button click.
* Navigates to {@link TaskActivity} to allow the user to create a new task.
* Passes the current {@code activeYear} and a flag indicating it's a new task.
*
* @param view The view that was clicked (the "Add Task" button).
*/
public void addTask(View view) {
Intent intent = new Intent(this, TaskActivity.class);
intent.putExtra("isNewTask",true);
intent.putExtra("activeYear",activeYear);
startActivity(intent);
}
/**
* Callback method to be invoked when an item in this AdapterView has been clicked.
* Displays a {@link Toast} message showing the start date of the clicked task.
*
* @param parent The AdapterView where the click happened.
* @param view The view within the AdapterView that was clicked (this
* will be a view provided by the adapter).
* @param pos The position of the view in the adapter.
* @param id The row id of the item that was clicked.
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
String date = db2Dsiplay(tasksList.get(pos).getDateStart());
Toast.makeText(this, "Given in "+date, Toast.LENGTH_SHORT).show();
}
/**
* Called when the context menu for a view is about to be shown.
* This method inflates the context menu with options: "Edit", "Delete", and "Done"
* for the selected task item in the ListView.
*
* @param menu The context menu that is being built.
* @param v The view for which the context menu is being built.
* @param menuInfo Extra information about the item for which the context menu should be shown.
* This object will vary depending on the class of v.
*/
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (v.getId() == R.id.lVMain) {
ListView lv = (ListView) v;
AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo) menuInfo;
choose = acmi.position;
menu.setHeaderTitle("Choose Action:");
menu.add("Edit");
menu.add("Delete");
menu.add("Done");
}
}
/**
* This hook is called whenever an item in a context menu is selected.
* Handles actions for "Edit", "Delete", and "Done".
* <p>
* "Edit": Navigates to {@link TaskActivity} with task details to allow editing.
* "Delete": Removes the task from Firebase.
* "Done": Moves the task to the "Done_Tasks" node in Firebase. A confirmation dialog
* is shown if the task is not for the 'full class'.
*
* @param item The context menu item that was selected.
* @return boolean Return false to allow normal context menu processing to
* proceed, true to consume it here.
*/
@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
Log.i("MainActivity", "onContextItemSelected");
String action = item.getTitle().toString();
if (action.equals("Edit")) {
Intent intent = new Intent(this, TaskActivity.class);
intent.putExtra("isNewTask",false);
intent.putExtra("activeYear",activeYear);
intent.putExtra("choose",choose);
startActivity(intent);
} else if (action.equals("Delete")) {
Task task = tasksList.get(choose);
refTasks.child(String.valueOf(activeYear))
.child(String.valueOf(!task.isFullClass()))
.child(task.getDateEnd())
.child(task.getClassName()+task.getSerNum())
.removeValue();
} else if (action.equals("Done")) {
Task task = tasksList.get(choose);
if (!task.isFullClass()){
AlertDialog.Builder adb =new AlertDialog.Builder(MainActivity.this);
adb.setTitle("Mark as Done");
adb.setMessage("The task is only for part of the class.\nAre you sure you want to mark it as Done ?");
adb.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SimpleDateFormat sdfSave = new SimpleDateFormat("yyyyMMdd");
String dateSave = sdfSave.format(Calendar.getInstance().getTime());
task.setDateChecked(dateSave);
refDoneTasks.child(String.valueOf(activeYear)).child(task.getDateEnd()).child(task.getClassName()+task.getSerNum()).setValue(task);
refTasks.child(String.valueOf(activeYear))
.child(String.valueOf(!task.isFullClass()))
.child(task.getDateEnd())
.child(task.getClassName()+task.getSerNum())
.removeValue();
dialog.dismiss();
}
});
adb.setNeutralButton("Cancel",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
adb.setCancelable(false);
adb.create().show();
} else {
SimpleDateFormat sdfSave = new SimpleDateFormat("yyyyMMdd");
String dateSave = sdfSave.format(Calendar.getInstance().getTime());
task.setDateChecked(dateSave);
refDoneTasks.child(String.valueOf(activeYear)).child(task.getDateEnd()).child(task.getClassName()+task.getSerNum()).setValue(task);
refTasks.child(String.valueOf(activeYear))
.child(String.valueOf(!task.isFullClass()))
.child(task.getDateEnd())
.child(task.getClassName()+task.getSerNum())
.removeValue();
taskAdp.notifyDataSetChanged();
}
}
return super.onContextItemSelected(item);
}
}
PresenceActivity.java
Path: PresenceActivity.java
package com.example.tasks.Activities;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.tasks.Obj.MasterActivity;
import com.example.tasks.R;
import com.example.tasks.SpeechToTextService;
import com.example.tasks.models.Student;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/// all students is loaded asynchronouly from RTDB during Login, and openning of the
/// PresenceActivity is conditioned by its completion.
import static com.example.tasks.FBRef.allStudents;
/// this is a narrow branch calculated reference.
/// An efficient flat tree branch (3 levels):
/// /P{YY}_{uid}/W{current week}/{MMDDMMM_HHmmL#}/
/// /P25_tTe3W4vIjHe0HSqRXxAIUBxIzKg1/W29/0718Jul_0830L1
/// the month apears twich - 1st for lexicograpic sort, and then for legibility of 18Jul date
/// it holds a lesson report (here the lesson at 0830 is L1 meaning lesson #1 of the day)
import static com.example.tasks.FBRef.refPresUidCurrentWeek;
public class PresenceActivity extends MasterActivity {
private TextView statusSummaryTextView;
private TextView disturbanceLabel;
// Data model to hold each student's current status and disturbances flag
private Map<String, String> studentStatus = new HashMap<>();
private Set<String> disturbanceSet = new HashSet<>();
//private List<Student> allStudents; // loaded from RTDB
private Set<String> collectedWords = new HashSet<>();
List<String> classNicks;
private String detectedClassName = null;
private TextView classNameTextView;
private SpeechToTextService speechService;
private final List<String> rawTranscripts = new ArrayList<>();
private TextView transcriptTextView;
private TextView classNameLabel, missingStudentsLabel;
private ListView attendanceList;
private Button btnStart, btnStop, btnDebug;
private static final int REQUEST_RECORD_AUDIO = 1001;
private ArrayAdapter<String> adapter;
private final List<String> mockDetectedNames = Arrays.asList("Alice", "Bob", "Charlie");
private final List<String> mockMissingNames = Arrays.asList("David", "Eve");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Ensure content takes up full screen (EdgeToEdge behavior)
EdgeToEdge.enable(this);
setContentView(R.layout.activity_presence);
// Respect system bars (status/nav bar insets)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
classNameLabel = findViewById(R.id.classNameLabel);
missingStudentsLabel = findViewById(R.id.missingStudentsLabel);
transcriptTextView = findViewById(R.id.transcriptTextView);
attendanceList = findViewById(R.id.attendanceList);
btnStart = findViewById(R.id.btnStart);
btnStop = findViewById(R.id.btnStop);
//btnDebug = findViewById(R.id.btnDebug);
statusSummaryTextView = findViewById(R.id.statusSummaryTextView);
disturbanceLabel = findViewById(R.id.disturbanceLabel);
//updateStatusUI("some data", "some data2");
// Prepare list adapter for final results
adapter = new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
new java.util.ArrayList<>()
);
attendanceList.setAdapter(adapter);
/// The speech service will do Speech To Text and after accumulating some names
/// will identify which class they belong to, and display the data of
/// the names that are yet to be read and accounted for.
/// all this is stored in RTDB
/// in an efficient flat tree branch (3 levels):
/// /P{YY}_{uid}/W{current week}/{MMDDMMM_HHmmL#}/
/// /P25_tTe3W4vIjHe0HSqRXxAIUBxIzKg1/W29/0718Jul_0650L-1/
/// the storage topology is aimed at give the teacher a NARROW branch to look at (the Week level)
/// instead of a monthly or yearly view which contains too many reports.
speechService = new SpeechToTextService(this,
new SpeechToTextService.OnSpeechRecognizedListener() {
@Override
public void onPartialResults(String text) {
runOnUiThread(() -> transcriptTextView.setText(text));
}
@Override
public void onResults(String text) {
runOnUiThread(() -> {
// 1. Timestamp + push to UI & RTDB
String ts = new SimpleDateFormat("HH:mm:ss", Locale.getDefault())
.format(new Date());
String entry = "[" + ts + "] " + text;
adapter.add(entry);
rawTranscripts.add(entry);
/// 2. we need this collection to be able to compare against nicknames
/// what we do is: Split into words, normalize, collect
/// later on the collected words will be used by detectClass
/// to identify the class.
for (String w : text.split("\\s+")) {
String norm = w
.replaceAll("[^\\p{L}\\p{Nd}]", "") // strip punctuation
.toLowerCase(Locale.getDefault());
if (!norm.isEmpty()) {
collectedWords.add(norm);
}
}
/// 3. Once we have enough words, try to detect class
/// here - we meet the entry condition for detectClass() >= 8 >= 6
/// so we try detecting againts the collectedWords
if (detectedClassName == null && collectedWords.size() > 3 &&
detectClass()) { // run onces
classNameLabel.setText("כיתה: " + detectedClassName);
// build list of this class’s nicknames
classNicks = new ArrayList<>();
for (Student s : allStudents) {
if (detectedClassName.equals(s.getClassName())) {
classNicks.add(s.getNickName());
}
}
}
if (classNicks != null) {
// filter out the ones we heard
List<String> missing = new ArrayList<>();
for (String nick : classNicks) {
/// now that we have the class's nicknames
/// we can check which names are missing (i.e. teacher has not
/// called out their name - and they may be present).
if (!collectedWords.contains(nick.toLowerCase(Locale.getDefault()))) {
missing.add(nick);
}
}
/// This red missingStudentsLabel will keep getting updated
/// again and again (each time teacher stops talking) and will
/// become smaller and smaller
/// Eventually, the teacher has every student accounted for
/// and all is stored in DB.
/// /class: יוד571
/// /rawTranscript/
/// [08:31:15] נאיה אלה הילה
/// [08:31:22] ארז יותם יובל
/// [08:31:27] אורי נועם עומר
/// while this data may seem useless (inaccurate) it can be
/// reverse mapped to the Student's id, so this is in fact
/// both legible and useful.
String missingText = missing.isEmpty()
? "לא דווחו: כל התלמידים"
: "לא דווחו: " + TextUtils.join(", ", missing);
missingStudentsLabel.setText(missingText);
analyzeTranscriptLine(text); // send text for special anaysis for being late / missing etc.
pushRawTranscript(entry);
}
//pushRawTranscript(entry);
});
}
}
);
// 🧪 Hook debug button
//btnDebug.setOnClickListener(v -> populateMockData());
// 🔊 Start recognition
btnStart.setOnClickListener(v -> {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.RECORD_AUDIO},
REQUEST_RECORD_AUDIO);
} else {
transcriptTextView.setText(""); // clear on new session
speechService.startRecognition();
btnStop.setEnabled(true);
}
});
// 🔇 Stop recognition
btnStop.setOnClickListener(v -> {
speechService.stopRecognition();
btnStop.setEnabled(false);
});
}
/// was used in the dev process before Speech was introduced.
private void uploadMockDataToFirebase() {
// 🗓 Get current time
Calendar now = Calendar.getInstance();
// 🔢 Build key parts
int week = now.get(Calendar.WEEK_OF_YEAR);
String weekStr = "W" + week;
String MMdd = String.format("%02d%02d", now.get(Calendar.MONTH) + 1, now.get(Calendar.DAY_OF_MONTH));
String MMM = new SimpleDateFormat("MMM", Locale.ENGLISH).format(now.getTime());
Pair<String, Integer> lessonInfo = getRoundedTimeAndLessonSlot(Calendar.getInstance());
String roundedHHmm = lessonInfo.first;
int lesson = lessonInfo.second;
if (lesson == -1) {
Log.e("PresenceActivity", "Time not in valid school hours");
return;
}
String timestampAndLesson = MMdd + MMM + "_" + roundedHHmm + "L" + lesson;
// 🔄 MOCK Data payload
Map<String, Object> payload = new HashMap<>();
payload.put("class", "יוד571");
payload.put("statusSummary", statusSummaryTextView.getText().toString());
payload.put("disturbances", disturbanceLabel.getText().toString());
payload.put("rawTranscript", rawTranscripts);
//payload.put("timestamp", System.currentTimeMillis());
// 🪄 Write using the clean FBRef
refPresUidCurrentWeek
.child(timestampAndLesson)
.setValue(payload)
.addOnSuccessListener(unused -> Log.i("PresenceActivity", "Upload success"))
.addOnFailureListener(e -> Log.e("PresenceActivity", "Upload failed", e));
}
/// was used in the dev process before speech was introduced.
/// to work with this you need to revive the btnDebug that was
/// btn deleted on build of 18Jul 07:48am
private void populateMockData() {
// Set class name
classNameLabel.setText("כיתה: יוד571");
// set raw data too:
rawTranscripts.clear();
rawTranscripts.add("Alice is present");
rawTranscripts.add("Bob is also here");
rawTranscripts.add("I think Charlie said something funny");
// Set detected names
StringBuilder detected = new StringBuilder("Recorded Names:");
for (String name : mockDetectedNames) {
detected.append("\n• ").append(name);
}
// Set missing students
StringBuilder missing = new StringBuilder("לא דווחו:");
for (String name : mockMissingNames) {
missing.append("\n• ").append(name);
}
missingStudentsLabel.setText(missing.toString());
// Fill attendance list
ArrayAdapter<String> adapter = new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
mockDetectedNames
);
attendanceList.setAdapter(adapter);
// Store timestamp for Firebase (not shown yet)
String isoDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault())
.format(new Date());
Log.d("PresenceActivity", "Simulated date: " + isoDate);
// 🔜 Next: send this data to Firebase RTDB
uploadMockDataToFirebase();
}
/**
* TEMPORARY: Heuristic to calculate lesson number (L#) from current time.
* Assumes:
* - First lesson starts at 08:30
* - Each lesson is 50 minutes
* - Round to the nearest lesson slot using ±25 min tolerance
* <p>
* NOTE: This logic will be replaced:
* - First with school-specific if-clauses
* - Then later with profile info from teacher's settings
*/
/**
* TEMPORARY: Determine rounded time and lesson number based on current time.
* Assumes:
* - L1 starts at 08:30
* - 50-minute slots (roughly covers for intermissions)
* - Round to nearest slot start using ±25 min
* 💡 A more robust solution could work againt a list of start/end tuples representing school day schedules
*
* @param now current time
* @return Pair of ("HHmm" rounded start time string, lesson number), or (-1) if out of range
*/
public static Pair<String, Integer> getRoundedTimeAndLessonSlot(Calendar now) {
int totalMinutes = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE);
int baseStart = 8 * 60 + 30; // 08:30 in minutes
int maxLessons = 20;
int lessonDuration = 50;
int minValid = baseStart - 25;
int maxValid = baseStart + lessonDuration * (maxLessons - 1) + 25;
// I prefer all hours of the day return some result and not -1.
// if (totalMinutes < minValid || totalMinutes > maxValid) {
// return new Pair<>(null, -1); // outside school time
// }
int roundedIndex = Math.round((totalMinutes - baseStart) / (float) lessonDuration);
int lessonStartMinutes = baseStart + roundedIndex * lessonDuration;
int hour = lessonStartMinutes / 60;
int minute = lessonStartMinutes % 60;
String roundedHHmm = String.format("%02d%02d", hour, minute);
return new Pair<>(roundedHHmm, roundedIndex + 1);
}
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions,
int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_RECORD_AUDIO) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
speechService.startRecognition();
} else {
Toast.makeText(this,
"Microphone permission is required",
Toast.LENGTH_SHORT).show();
}
}
}
/**
* 🔄 Pushes a single transcript entry to RTDB using rounded-time + lesson key
* /P{YY}_{uid}/W{current week}/{MMDDMMM_HHmmL#}/
* /P25_tTe3W4vIjHe0HSqRXxAIUBxIzKg1/W29/0718Jul_0830L1
*/
private void pushRawTranscript(String newEntry) {
// 🗓 Get current time
Calendar now = Calendar.getInstance();
// 🔢 Build key parts
int week = now.get(Calendar.WEEK_OF_YEAR);
String weekStr = "W" + week;
String MMdd = String.format(Locale.getDefault(), "%02d%02d",
now.get(Calendar.MONTH) + 1,
now.get(Calendar.DAY_OF_MONTH)
);
String MMM = new SimpleDateFormat("MMM", Locale.ENGLISH)
.format(now.getTime());
Pair<String, Integer> lessonInfo = getRoundedTimeAndLessonSlot(now);
String roundedHHmm = lessonInfo.first;
int lesson = lessonInfo.second;
if (lesson == -4) {
Log.e("PresenceActivity", "Time not in valid school hours");
return;
}
String timestampAndLesson = MMdd + MMM + "_" + roundedHHmm + "L" + lesson;
// 🔄 Data payload
Map<String, Object> payload = new HashMap<>();
payload.put("class", detectedClassName);
payload.put("statusSummary", statusSummaryTextView.getText().toString());
payload.put("disturbances", disturbanceLabel.getText().toString());
//payload.put("detected", rawTranscripts);
//payload.put("missing", new ArrayList<>());
payload.put("rawTranscript", rawTranscripts);
//payload.put("timestamp", System.currentTimeMillis());
// 🪄 Write using the clean FBRef
refPresUidCurrentWeek
.child(timestampAndLesson)
.setValue(payload)
.addOnSuccessListener(unused -> Log.i("PresenceActivity", "Upload success"))
.addOnFailureListener(e -> Log.e("PresenceActivity", "Upload failed", e));
}
/**
* 🔍 identifying the most probable class.
*/
private Boolean detectClass() {
// 1. Build map of ClassName → count
Map<String, Integer> classMatches = new HashMap<>();
for (Student s : allStudents) {
classMatches.putIfAbsent(s.getClassName(), 0);
}
/// 2. For each class, count how many nicknames appear
/// this is just counting how many times each nickname appears
for (String cls : new ArrayList<>(classMatches.keySet())) {
int count = 0;
for (Student s : allStudents) {
if (!s.getClassName().equals(cls)) continue;
String nick = s.getNickName().toLowerCase(Locale.ROOT);
if (collectedWords.contains(nick)) {
count++;
}
}
classMatches.put(cls, count);
}
// 3. Find best match - identifying the most probable class.
String bestClass = null;
int bestCount = 0;
for (Map.Entry<String, Integer> e : classMatches.entrySet()) {
if (e.getValue() > bestCount) {
bestCount = e.getValue();
bestClass = e.getKey();
}
}
// 4. If we have at least one hit, update UI once
if (bestCount >= 1 && detectedClassName == null) {
detectedClassName = bestClass;
runOnUiThread(() ->
classNameLabel.setText("כיתה: " + detectedClassName)
);
return true;
}
return false;
}
/**
* Parses a single transcript line and updates student statuses.
* Call this inside onResults() for each new full transcript.
*/
private void analyzeTranscriptLine(String line) {
// Normalize line
String text = line.trim();
if (text.isEmpty()) return;
// Determine category keyword and student names
String[] tokens = text.split("\\s+");
String keyword = tokens[0];
List<String> names = Arrays.asList(Arrays.copyOfRange(tokens, 1, tokens.length));
// Map Hebrew keyword to status
String status;
switch (keyword) {
case "בזמן":
status = "בזמן";
break;
case "איחור":
status = "מאחרים";
break;
case "מאחרים":
status = "מאחרים";
break;
case "חיסור":
status = "חסרים";
case "חסרים":
status = "חסרים";
break;
case "חסרות" /* alternative form */:
status = "חסרים";
break;
case "חסרה":
status = "חסרים";
break;
case "חולים":
status = "מחלה";
break;
case "הפרעה":
// disturbance only, keep existing status
disturbanceSet.addAll(names);
updateUI();
return;
default:
return; // unrecognized
}
// Update each student
for (String name : names) {
// If clearing (בזמן), remove disturbance flag and reset status only for these names
if (status.equals("בזמן")) {
studentStatus.put(name, "בזמן");
//disturbanceSet.remove(name);
} else {
// For lateness, always overwrite missing
studentStatus.put(name, status);
}
}
// Refresh UI after each line
updateUI();
}
/**
* Builds the summary strings and updates the TextViews.
*/
private void updateUI() {
// Group by status
Map<String, List<String>> grouped = new HashMap<>();
for (Map.Entry<String, String> e : studentStatus.entrySet()) {
grouped.computeIfAbsent(e.getValue(), k -> new ArrayList<>()).add(e.getKey());
}
// Build status summary
StringBuilder summary = new StringBuilder();
for (String key : Arrays.asList("בזמן", "מאחרים", "חסרים", "מחלה")) {
List<String> list = grouped.get(key);
if (list != null && !list.isEmpty()) {
summary.append(key).append(": ").append(TextUtils.join(", ", list)).append("\n");
}
}
// Build disturbance summary
String disturbanceText = disturbanceSet.isEmpty()
? "הפרעות: אין"
: "הפרעות: " + TextUtils.join(", ", disturbanceSet);
// Apply to UI
statusSummaryTextView.setText(summary.toString().trim());
disturbanceLabel.setText(disturbanceText);
}
// In onResults(String text):
// for (String line : text.split("\\r?\\n")) {
// analyzeTranscriptLine(line);
// }
// /**
// * Call this from your transcript update logic to refresh the UI
// */
// private void updateStatusUI(String summary, String disturbances) {
// statusSummaryTextView.setText(summary);
// disturbanceLabel.setText(disturbances);
// }
}
ProfileActivity.java
Path: ProfileActivity.java
package com.example.tasks.Activities;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Base64;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import com.example.tasks.Obj.MasterActivity;
import com.example.tasks.R;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.List;
public class ProfileActivity extends MasterActivity {
// ① Define a launcher for taking a picture thumbnail:
private ActivityResultLauncher<Void> takePictureLauncher;
private TextView tvStudentsSheetLink; // link to the students google sheet.
private static final int REQ_PHOTO = 123;
private EditText usernameEdit;
private Spinner activeYearSpinner;
private Spinner defaultScreenSpinner;
private Button takePictureBtn;
private ImageView profileImage;
private DatabaseReference userRef;
private FirebaseUser currentUser;
private boolean isSpinnersLoaded = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
// findViewById
tvStudentsSheetLink = findViewById(R.id.tvStudentsSheetLink);
// Views
usernameEdit = findViewById(R.id.usernameEdit);
activeYearSpinner = findViewById(R.id.activeYearSpinner);
defaultScreenSpinner = findViewById(R.id.defaultScreenSpinner);
takePictureBtn = findViewById(R.id.takePictureBtn);
profileImage = findViewById(R.id.profileImage);
// Firebase refs
currentUser = FirebaseAuth.getInstance().getCurrentUser();
userRef = FirebaseDatabase.getInstance()
.getReference("Users")
.child(currentUser.getUid());
// Populate spinners
List<String> years = Arrays.asList("2023", "2024", "2025", "2026");
ArrayAdapter<String> yearAdapter = new ArrayAdapter<>(
this, android.R.layout.simple_spinner_item, years);
yearAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
activeYearSpinner.setAdapter(yearAdapter);
List<String> screens = Arrays.asList("Presence", "Reports", "Profile", "Task", "MainTask");
ArrayAdapter<String> screenAdapter = new ArrayAdapter<>(
this, android.R.layout.simple_spinner_item, screens);
screenAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
defaultScreenSpinner.setAdapter(screenAdapter);
// Listeners for saving (ignore initial selections)
activeYearSpinner.setOnItemSelectedListener(new SimpleItemSelectedListener() {
@Override
public void onItemSelected(String value) {
if (isSpinnersLoaded) {
userRef.child("activvvYear").setValue(value);
}
}
});
defaultScreenSpinner.setOnItemSelectedListener(new SimpleItemSelectedListener() {
@Override
public void onItemSelected(String value) {
if (isSpinnersLoaded) {
userRef.child("preferredActivity").setValue(value);
}
}
});
// Load existing data
loadProfile();
// ② Register the launcher BEFORE you call it:
takePictureLauncher = registerForActivityResult(
new ActivityResultContracts.TakePicturePreview(),
bitmap -> {
if (bitmap != null) {
// Display immediately
profileImage.setImageBitmap(bitmap);
// And push to RTDB as Base64
uploadImage(bitmap);
}
}
);
// ③ Wire your button to launch the camera:
takePictureBtn.setOnClickListener(v -> {
takePictureLauncher.launch(null);
});
}
private void loadProfile() {
userRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snap) {
// Username
String uname = snap.child("username").getValue(String.class);
if (uname != null) usernameEdit.setText(uname);
// Active year: new key then fallback
String year = snap.child("activvvYear").getValue(String.class);
if (year == null) {
year = snap.child(currentUser.getUid()).getValue(String.class);
if (year != null) {
userRef.child("activvvYear").setValue(year);
}
}
if (year != null) {
ArrayAdapter<String> adapter = (ArrayAdapter<String>) activeYearSpinner.getAdapter();
int pos = adapter.getPosition(year);
if (pos >= 0) activeYearSpinner.setSelection(pos);
}
// Default screen
String screen = snap.child("preferredActivity").getValue(String.class);
if (screen != null) {
ArrayAdapter<String> adapter2 = (ArrayAdapter<String>) defaultScreenSpinner.getAdapter();
int pos2 = adapter2.getPosition(screen);
if (pos2 >= 0) defaultScreenSpinner.setSelection(pos2);
}
// Profile image
String b64 = snap.child("b64jpg").getValue(String.class);
if (b64 != null) {
byte[] data = Base64.decode(b64, Base64.DEFAULT);
profileImage.setImageBitmap(BitmapFactory.decodeByteArray(data, 0, data.length));
}
// Students Sheet URL
String sheetUrl = snap.child("studentsSheetUrl").getValue(String.class);
if (sheetUrl != null && !sheetUrl.isEmpty()) {
// הצגת טקסט תיאורי במקום ה-URL הארוך
tvStudentsSheetLink.setText(R.string.string_link_google_sheet);
tvStudentsSheetLink.setVisibility(View.VISIBLE);
tvStudentsSheetLink.setOnClickListener(v -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW,
Uri.parse(sheetUrl));
startActivity(browserIntent);
});
}
// Now allow spinner callbacks to write changes
isSpinnersLoaded = true;
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
}
// Utility to encode and upload image
private void uploadImage(Bitmap bmp) {
// Your existing code: compress → Base64 → write to "b64jpg"
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.JPEG, /*quality*/80, baos);
String b64 = Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT);
FirebaseDatabase
.getInstance()
.getReference("Users")
.child(FirebaseAuth.getInstance().getUid())
.child("b64jpg")
.setValue(b64);
}
// Spinner helper
private abstract static class SimpleItemSelectedListener implements android.widget.AdapterView.OnItemSelectedListener {
@Override
public void onNothingSelected(android.widget.AdapterView<?> parent) {
}
@Override
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int pos, long id) {
onItemSelected(parent.getItemAtPosition(pos).toString());
}
public abstract void onItemSelected(String value);
}
}
ReportsActivity.java
Path: ReportsActivity.java
package com.example.tasks.Activities;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.tasks.Obj.MasterActivity;
import com.example.tasks.R;
public class ReportsActivity extends MasterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_reports);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}
TaskActivity.java
Path: TaskActivity.java
package com.example.tasks.Activities;
import static com.example.tasks.FBRef.refTasks;
import static com.example.tasks.FBRef.refYears;
import static com.example.tasks.Utilities.db2Dsiplay;
import androidx.annotation.NonNull;
import android.app.DatePickerDialog;
import android.content.Intent;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.example.tasks.FBRef;
import com.example.tasks.Obj.MasterActivity;
import com.example.tasks.Obj.Task;
import com.example.tasks.R;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.firebase.database.DataSnapshot;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
/**
* @author Albert Levy albert.school2015@gmail.com
* @version 2.1
* @since 9/3/2024
* <p>
* Activity for creating a new task or editing an existing one.
* <p>
* This activity extends {@link MasterActivity} to inherit its common options menu.
* It provides a form for users to input task details such as class name,
* task number (serial number), start date, due date, and whether the task
* applies to the full class.
* <p>
* Features:
* <ul>
* <li>Supports both creating new tasks and editing existing tasks.</li>
* <li>Uses {@link DatePickerDialog} for selecting start and due dates.</li>
* <li>Populates a {@link Spinner} with class names fetched from Firebase for the active year.</li>
* <li>Validates that required data (dates, class name, task number) is provided.</li>
* <li>Saves new tasks or updates existing tasks in Firebase Realtime Database
* under the appropriate path based on the active year and task details.</li>
* <li>Prevents creation of duplicate tasks (based on current implementation of {@code Task.isIn()}).</li>
* </ul>
* The UI and button text ("Add Task" vs. "Set Task") change dynamically based on
* whether the activity is in "new task" or "edit task" mode.
*
* @see MasterActivity
* @see Task
* @see DatePickerDialog
* @see ArrayAdapter
* @see FBRef
*/
public class TaskActivity extends MasterActivity implements AdapterView.OnItemSelectedListener {
private TextView tVTaskHeader, tVStartDate, tVDueDate;
private Spinner spClass;
private EditText eTTaskNum;
private CheckBox cbFullClass;
private Button btnTask;
private Intent gi;
private boolean isNewTask, setDueDate;
private String className, startDate, dueDate, serNum;
private int activeYear;
private int choose;
private Task task;
private ArrayList<String> classList;
private ArrayAdapter<String> adp;
/**
* Called when the activity is first created.
* Initializes views, retrieves intent extras, and fetches class data for the spinner.
*
* @param savedInstanceState If the activity is being re-initialized after
* previously being shut down then this Bundle contains the data it most
* recently supplied in {@link #onSaveInstanceState}.
* <b><i>Note: Otherwise it is null.</i></b>
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_task);
initViews();
refYears.child(String.valueOf(activeYear)).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>(){
@Override
public void onComplete(@NonNull com.google.android.gms.tasks.Task<DataSnapshot> tsk) {
if (tsk.isSuccessful()) {
DataSnapshot dS = tsk.getResult();
classList.clear();
classList.add("Choose class:");
for(DataSnapshot data : dS.getChildren()) {
classList.add(data.getValue(String.class));
}
adp.notifyDataSetChanged();
if (!isNewTask){
spClass.setSelection(classList.indexOf(task.getClassName()));
}
}
else {
Log.e("firebase", "Error getting data", tsk.getException());
}
}
});
}
/**
* Initializes UI components, retrieves data from the calling intent,
* and sets up the UI elements based on whether a new task is being created
* or an existing one is being edited.
* <p>
* For new tasks, fields are empty. For editing tasks, fields are pre-populated
* with the existing task's details. Some fields like start date and task number
* are made non-editable when editing an existing task.
*/
private void initViews() {
tVTaskHeader = findViewById(R.id.tVTaskHeader);
spClass = findViewById(R.id.spClass);
tVStartDate = findViewById(R.id.tVStartDate);
tVDueDate = findViewById(R.id.tVDueDate);
eTTaskNum = findViewById(R.id.eTTaskNum);
cbFullClass = findViewById(R.id.cbFullClass);
btnTask = findViewById(R.id.btnTask);
gi = getIntent();
activeYear = gi.getIntExtra("activeYear",1970);
if (activeYear == 1970) {
Toast.makeText(this, "Wrong data sent", Toast.LENGTH_LONG).show();
finish();
}
isNewTask = gi.getBooleanExtra("isNewTask",true);
// if (!isNewTask){
// choose = gi.getIntExtra("choose",-1);
// task = MainActivity.tasksList.get(choose);
// }
if (isNewTask) {
tVTaskHeader.setText("Add new Task");
btnTask.setText("Add Task");
} else {
choose = gi.getIntExtra("choose",-1);
task = MainActivity.tasksList.get(choose);
tVTaskHeader.setText("Edit Task");
startDate = task.getDateStart();
tVStartDate.setText(db2Dsiplay(task.getDateStart()));
tVStartDate.setClickable(false);
dueDate = task.getDateEnd();
tVDueDate.setText(db2Dsiplay(task.getDateEnd()));
eTTaskNum.setText(task.getSerNum());
eTTaskNum.setEnabled(false);
eTTaskNum.setInputType(InputType.TYPE_NULL);
cbFullClass.setChecked(task.isFullClass());
btnTask.setText("Set Task");
}
classList = new ArrayList<String>();
classList.add("Choose class:");
adp = new ArrayAdapter<>(TaskActivity.this,
android.R.layout.simple_spinner_dropdown_item, classList);
spClass.setAdapter(adp);
spClass.setOnItemSelectedListener(this);
}
/**
* Handles clicks on the start date or due date TextViews to open a {@link DatePickerDialog}.
* Sets a flag {@code setDueDate} to indicate which date field is being selected.
*
* @param view The TextView (start date or due date) that was clicked.
*/
public void datePick(View view) {
if (view.getId() == R.id.tVDueDate){
setDueDate = true;
} else {
setDueDate = false;
}
openDatePickerDialog();
}
/**
* Opens a {@link DatePickerDialog} allowing the user to select a date.
* The dialog is initialized with the current system date.
* The selected date is handled by {@link #onDateSetListener}.
*/
private void openDatePickerDialog() {
Calendar calNow = Calendar.getInstance();
DatePickerDialog datePickerDialog = new DatePickerDialog(this, onDateSetListener,
calNow.get(Calendar.YEAR),
calNow.get(Calendar.MONTH),
calNow.get(Calendar.DAY_OF_MONTH));
datePickerDialog.setTitle("Choose date");
datePickerDialog.show();
}
/**
* Listener for handling date selection from the {@link DatePickerDialog}.
* <p>
* When a date is set, it updates either the {@code startDate} or {@code dueDate}
* field and the corresponding TextView, based on the {@code setDueDate} flag.
* Dates are stored in "yyyyMMdd" format and displayed in "dd-MM-yyyy" format.
*/
DatePickerDialog.OnDateSetListener onDateSetListener = new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
Calendar calNow = Calendar.getInstance();
Calendar calSet = (Calendar) calNow.clone();
calSet.set(Calendar.YEAR, year);
calSet.set(Calendar.MONTH, month);
calSet.set(Calendar.DAY_OF_MONTH, dayOfMonth);
SimpleDateFormat sdfSave = new SimpleDateFormat("yyyyMMdd");
String dateSave = sdfSave.format(calSet.getTime());
if (setDueDate) {
dueDate = dateSave;
tVDueDate.setText(db2Dsiplay(dateSave));
} else {
startDate = dateSave;
tVStartDate.setText(db2Dsiplay(dateSave));
}
}
};
/**
* Handles the confirmation (Add/Set Task button click).
* Validates input fields. If all required data is present:
* <ul>
* <li>For new tasks: Creates a new {@link Task} object and saves it to Firebase,
* checking for duplicates first.</li>
* <li>For existing tasks: Updates the existing task in Firebase. This involves
* removing the old task entry and adding the updated one, especially if
* key details like due date or 'full class' status (which affect path) change.</li>
* </ul>
* Finishes the activity upon successful save/update.
*
* @param view The view that was clicked (the confirmation button).
*/
public void confirmation(View view) {
int taskNum = Integer.parseInt(eTTaskNum.getText().toString());
serNum = String.format("%02d",taskNum);
if (startDate == null || dueDate == null || className == null || serNum == null) {
Toast.makeText(this, "Missing data", Toast.LENGTH_SHORT).show();
} else {
Task newTask = new Task(startDate, dueDate,className, serNum, activeYear, cbFullClass.isChecked());
if (!isNewTask) {
refTasks.child(String.valueOf(activeYear))
.child(String.valueOf(!task.isFullClass()))
.child(task.getDateEnd())
.child(task.getClassName()+task.getSerNum())
.removeValue();
refTasks.child(String.valueOf(activeYear))
.child(String.valueOf(!cbFullClass.isChecked()))
.child(dueDate)
.child(className+serNum)
.setValue(newTask);
finish();
} else if (newTask.isIn(MainActivity.tasksList)) {
Toast.makeText(this, "Task already exist!", Toast.LENGTH_SHORT).show();
} else {
refTasks.child(String.valueOf(activeYear))
.child(String.valueOf(!cbFullClass.isChecked()))
.child(dueDate)
.child(className+serNum)
.setValue(newTask);
finish();
}
}
}
/**
* Callback method to be invoked when an item in the class {@link Spinner} has been selected.
* Updates the {@code className} field if a valid class (not the default prompt) is selected.
*
* @param parent The AdapterView where the selection happened.
* @param view The view within the AdapterView that was clicked.
* @param pos The position of the view in the adapter.
* @param id The row id of the item that was selected.
*/
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (pos != 0) {
className = classList.get(pos);
}
}
/**
* Callback method to be invoked when the selection disappears from the class {@link Spinner}.
* This might happen when there are no items to select, or the adapter becomes empty.
* Shows a {@link Toast} message.
*
* @param parent The AdapterView that now contains no selected item.
*/
@Override
public void onNothingSelected(AdapterView<?> parent) {
Toast.makeText(this, "Nothing selected...", Toast.LENGTH_SHORT).show();
}
}
YearsActivity.java
Path: YearsActivity.java
package com.example.tasks.Activities;
import static com.example.tasks.FBRef.*;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.InputType;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.Toast;
import com.example.tasks.FBRef;
import com.example.tasks.Obj.MasterActivity;
import com.example.tasks.R;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList;
import java.util.Calendar;
/**
* @author Albert Levy albert.school2015@gmail.com
* @version 2.1
* @since 9/3/2024
* <p>
* Activity for managing academic years and associated classes.
* <p>
* This activity allows users to view, add, and select academic years. For each selected year,
* users can also view and add classes they teach. It plays a crucial role in setting up
* the application for new users by prompting them to add their first year and class.
* For existing users, it allows switching between active years or adding new ones.
* <p>
* Features:
* <ul>
* <li>Displays a {@link Spinner} to select an academic year.</li>
* <li>Displays a {@link ListView} to show classes for the selected year.</li>
* <li>Allows adding new academic years via an {@link AlertDialog}.</li>
* <li>Allows adding new classes for the currently selected year via an {@link AlertDialog}.</li>
* <li>Handles initial setup for new users, forcing them to add a year and class.</li>
* <li>Saves the selected active year to {@link SharedPreferences} for existing users.</li>
* <li>Returns the selected active year to {@link LoginActivity} for new users.</li>
* <li>Interacts with Firebase Realtime Database to store and retrieve year and class data.</li>
* <li>Implements context menu (via {@link #onItemClick} on ListView) for deleting classes.</li>
* </ul>
*
* @see MasterActivity
* @see SharedPreferences
* @see AlertDialog
* @see FBRef
*/
public class YearsActivity extends MasterActivity implements AdapterView.OnItemSelectedListener,
AdapterView.OnItemClickListener {
private Spinner spYears;
private ListView lVClasses;
private Intent gi;
private boolean isNewUser;
private int activeYear, newYear;
private String newClass;
private ArrayList<Integer> years;
private ArrayList<String> classes;
private ArrayAdapter<Integer> yearsAdp;
private ArrayAdapter<String> classesAdp;
private ValueEventListener velYears = new ValueEventListener() {
/**
* Called when data for the list of years changes in Firebase.
* Updates the {@code years} ArrayList and notifies the {@code yearsAdp} to refresh the spinner.
* Sets the spinner selection to the {@code activeYear} if it's valid.
*
* @param dS The DataSnapshot containing the updated list of years.
*/
@Override
public void onDataChange(@NonNull DataSnapshot dS) {
years.clear();
for(DataSnapshot data : dS.getChildren()) {
years.add(Integer.parseInt(data.getKey()));
}
yearsAdp.notifyDataSetChanged();
if (activeYear != 1970) {
spYears.setSelection(years.indexOf(activeYear));
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {}
};
private ValueEventListener velClass = new ValueEventListener() {
/**
* Called when data for the list of classes for the {@code activeYear} changes in Firebase.
* Updates the {@code classes} ArrayList and notifies the {@code classesAdp} to refresh the ListView.
*
* @param dS The DataSnapshot containing the updated list of classes for the active year.
*/
@Override
public void onDataChange(@NonNull DataSnapshot dS) {
classes.clear();
for(DataSnapshot data : dS.getChildren()) {
classes.add(data.getValue(String.class));
}
classesAdp.notifyDataSetChanged();
}
@Override
public void onCancelled(@NonNull DatabaseError error) {}
};
private SharedPreferences settings;
/**
* Called when the activity is first created.
* Initializes views, SharedPreferences, and sets up the initial state
* based on whether the user is new or existing.
*
* @param savedInstanceState If the activity is being re-initialized after
* previously being shut down then this Bundle contains the data it most
* recently supplied in {@link #onSaveInstanceState}.
* <b><i>Note: Otherwise it is null.</i></b>
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_years);
settings = getSharedPreferences("PREFS_NAME",MODE_PRIVATE);
initViews();
}
/**
* Initializes all UI view components and their adapters.
* Retrieves intent extras to determine if the user is new.
* If the user is new, it sets a default {@code activeYear} and immediately prompts
* to add a new year. Otherwise, it loads the previously active year from SharedPreferences.
*/
private void initViews() {
spYears = findViewById(R.id.spYears);
lVClasses = findViewById(R.id.lVClasses);
years = new ArrayList<>();
years.clear();
classes = new ArrayList<>();
classes.clear();
yearsAdp = new ArrayAdapter<>(YearsActivity.this,
android.R.layout.simple_spinner_dropdown_item, years);
spYears.setAdapter(yearsAdp);
spYears.setOnItemSelectedListener(this);
classesAdp = new ArrayAdapter<>(YearsActivity.this,
android.R.layout.simple_spinner_dropdown_item, classes);
lVClasses.setAdapter(classesAdp);
lVClasses.setOnItemClickListener(this);
gi = getIntent();
isNewUser = gi.getBooleanExtra("isNewUser", false);
if (isNewUser) {
activeYear = 1970;
addNewYear(null);
} else {
activeYear = settings.getInt("activeYear",1970);
}
}
/**
* Called when the activity is becoming visible to the user.
* Attaches Firebase ValueEventListeners to listen for changes in years and classes,
* but only if it's not a new user and a valid {@code activeYear} is set.
* For new users, listeners are typically attached after they add their first year/class.
*/
@Override
protected void onStart() {
super.onStart();
if (!isNewUser && activeYear != 1970) {
refYears.addValueEventListener(velYears);
refYears.child(String.valueOf(activeYear)).addValueEventListener(velClass);
}
}
/**
* Called when the activity is no longer visible to the user.
* Removes the Firebase ValueEventListeners to prevent memory leaks and
* unnecessary background updates.
*/
@Override
protected void onStop() {
super.onStop();
refYears.removeEventListener(velYears);
}
/**
* Displays an {@link AlertDialog} to prompt the user to enter a new academic year.
* The year is expected to be the year when the summer vacation occurs (e.g., 2024 for 2023-2024).
* <p>
* If the entered year doesn't already exist, it's added to Firebase.
* For new users, after adding the first year, it automatically calls {@link #addNewClass(View)}
* to prompt for adding the first class.
* If adding a new year, the {@code velYears} listener is attached here if not already.
*
* @param view The view that triggered this action (can be null if called programmatically).
*/
public void addNewYear(View view) {
AlertDialog.Builder adb =new AlertDialog.Builder(YearsActivity.this);
adb.setTitle("Add new year");
adb.setMessage("Enter the new year number:\n(when the summer vacation occur)");
final EditText eT = new EditText(this.getApplicationContext());
eT.setInputType(InputType.TYPE_CLASS_NUMBER);
Calendar calNow = Calendar.getInstance();
eT.setHint(String.valueOf(1+calNow.get(Calendar.YEAR)));
adb.setView(eT);
adb.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
newYear = Integer.parseInt(eT.getText().toString());
if (years.contains(newYear)){
Toast.makeText(YearsActivity.this, "Year already exist!\nTry again", Toast.LENGTH_SHORT).show();
} else {
refYears.child(String.valueOf(newYear)).setValue("Null");
dialog.dismiss();
if (isNewUser){
activeYear = newYear;
addNewClass(null);
}
}
}
});
adb.setNeutralButton("Cancel",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
adb.setCancelable(false);
adb.create().show();
}
/**
* Displays an {@link AlertDialog} to prompt the user to enter a new class name
* for the currently {@code activeYear}.
* <p>
* If the class doesn't already exist for that year, it's added to the list of classes
* in Firebase for the {@code activeYear}.
* If adding the first class for a year, the {@code velClass} listener is attached here if not already.
*
* @param view The view that triggered this action (can be null if called programmatically).
*/
public void addNewClass(View view) {
if (activeYear != 1970){
AlertDialog.Builder adb =new AlertDialog.Builder(YearsActivity.this);
adb.setTitle("Add new class");
adb.setMessage("Enter the new class you teach:");
final EditText eT = new EditText(this.getApplicationContext());
// eT.setInputType(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
adb.setView(eT);
adb.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
newClass = eT.getText().toString();
if (classes.contains(newClass)){
Toast.makeText(YearsActivity.this, "Class already exist!\nTry again", Toast.LENGTH_SHORT).show();
} else {
classes.add(newClass);
refYears.child(String.valueOf(activeYear)).setValue(classes);
dialog.dismiss();
}
}
});
adb.setNeutralButton("Cancel",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
adb.setCancelable(false);
adb.create().show();
}
}
/**
* Finalizes the year and class selection.
* <p>
* For new users, it returns the {@code activeYear} as a result to the calling activity
* (e.g., {@link LoginActivity}) and finishes this activity.
* For existing users, it saves the {@code activeYear} to SharedPreferences and navigates
* to {@link MainActivity}.
* <p>
* It prevents proceeding if no {@code activeYear} is set or (for new users) if no classes
* have been added for that year.
*
* @param view The view that was clicked (the "Done" button).
*/
public void done(View view) {
if (activeYear != 1970) {
if (isNewUser) {
gi.putExtra("activeYear", activeYear);
setResult(RESULT_OK,gi);
finish();
} else {
SharedPreferences.Editor editor = settings.edit();
editor.putInt("activeYear",activeYear);
editor.commit();
Intent intent = new Intent(this.getApplicationContext(), MainActivity.class);
startActivity(intent);
}
} else {
AlertDialog.Builder adb = new AlertDialog.Builder(YearsActivity.this);
adb.setTitle("Warning !!!");
adb.setMessage("You must set the Active Year");
adb.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
adb.setCancelable(false);
adb.create().show();
}
}
/**
* Callback method to be invoked when an item in the years {@link Spinner} has been selected.
* Updates the {@code activeYear} to the selected year.
* Removes any existing Firebase listener for classes of the previously selected year
* and attaches a new listener for the classes of the newly selected {@code activeYear}.
* This method is not called for the initial selection if {@code isNewUser} is true,
* as the flow is controlled by {@code addNewYear} and {@code addNewClass} initially.
*
* @param parent The AdapterView where the selection happened.
* @param view The view within the AdapterView that was clicked.
* @param pos The position of the view in the adapter.
* @param id The row id of the item that was selected.
*/
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (!isNewUser){
activeYear = years.get(pos);
refYears.child(String.valueOf(activeYear)).addValueEventListener(velClass);
}
}
/**
* Callback method to be invoked when the selection disappears from the years {@link Spinner}.
* Currently, this method has no specific implementation.
*
* @param parent The AdapterView that now contains no selected item.
*/
@Override
public void onNothingSelected(AdapterView<?> parent) {}
/**
* Callback method to be invoked when an item in the classes {@link ListView} has been clicked.
* This is used as a context menu trigger (though not a standard long-press context menu).
* It displays an {@link AlertDialog} to confirm deletion of the selected class.
* If confirmed, the class is removed from Firebase for the current {@code activeYear}.
*
* @param parent The AdapterView where the click happened (the ListView).
* @param view The view within the AdapterView that was clicked.
* @param pos The position of the view in the adapter.
* @param id The row id of the item that was clicked.
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int pos, long id) {
if (parent.getId() == R.id.lVClasses) {
String choosen = classes.get(pos);
AlertDialog.Builder adb =new AlertDialog.Builder(YearsActivity.this);
adb.setTitle("Delete class");
adb.setMessage("Are you sure you want to\ndelete task '"+choosen+"' ?");
adb.setPositiveButton("Ok",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
classes.remove(pos);
refYears.child(String.valueOf(activeYear)).setValue(classes);
dialog.dismiss();
}
});
adb.setNeutralButton("Cancel",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
adb.setCancelable(false);
adb.create().show();
}
}
}
DoneTaskAdapter.java
Path: DoneTaskAdapter.java
package com.example.tasks.Adapters;
import static com.example.tasks.Utilities.db2Dsiplay;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.example.tasks.Obj.Task;
import com.example.tasks.R;
import java.util.ArrayList;
/**
* @author Albert Levy albert.school2015@gmail.com
* @version 2.1
* @since 9/3/2024
* <p>
* Adapter for displaying a list of completed {@link Task} objects in a {@link android.widget.ListView}.
* <p>
* This adapter is responsible for creating and binding views for each completed task item.
* It displays details of a done task such as its original due date, class name,
* serial number (task identifier), and the date it was marked as checked/completed.
* It uses a {@link ViewHolderDone} pattern for efficient view recycling.
*
* @see BaseAdapter
* @see Task
*/
public class DoneTaskAdapter extends BaseAdapter {
private Context context;
private ArrayList<Task> tasks;
private LayoutInflater inflater;
/**
* Constructs a new {@code DoneTaskAdapter}.
*
* @param context The current context.
* @param tasks An ArrayList of completed {@link Task} objects to be displayed.
*/
public DoneTaskAdapter(Context context, ArrayList<Task> tasks) {
this.context = context;
this.tasks = tasks;
this.inflater = LayoutInflater.from(context);
}
/**
* How many items are in the data set represented by this Adapter.
*
* @return Count of items (completed tasks).
*/
@Override
public int getCount() {
return tasks.size();
}
/**
* Get the data item associated with the specified position in the data set.
*
* @param pos Position of the item whose data we want within the adapter's
* data set.
* @return The completed {@link Task} at the specified position.
*/
@Override
public Object getItem(int pos) {
return tasks.get(pos);
}
/**
* Get the row id associated with the specified position in the list.
*
* @param pos The position of the item within the adapter's data set whose row id we want.
* @return The id of the item at the specified position.
*/
@Override
public long getItemId(int pos) {
return pos;
}
/**
* Get a View that displays the data for a completed task at the specified position in the data set.
* <p>
* This method inflates the layout for each completed task item (if necessary) and populates
* it with the data from the {@link Task} object at the given position, including
* due date, class name, task serial number, and checked date.
*
* @param pos The position of the item within the adapter's data set of the item whose view
* we want.
* @param view The old view to reuse, if possible. Note: You should check that this view
* is non-null and of an appropriate type before using. If it is not possible to convert
* this view to display the correct data, this method can create a new view.
* @param parent The parent that this view will eventually be attached to.
* @return A View corresponding to the data at the specified position.
*/
@Override
public View getView(int pos, View view, ViewGroup parent) {
DoneTaskAdapter.ViewHolderDone viewHolderDone;
if (view == null) {
view = LayoutInflater.from(context).inflate(R.layout.donetask_layout, parent, false);
viewHolderDone = new DoneTaskAdapter.ViewHolderDone(view);
view.setTag(viewHolderDone);
} else {
viewHolderDone = (DoneTaskAdapter.ViewHolderDone) view.getTag();
}
Task task = tasks.get(pos);
viewHolderDone.itemDue.setText(db2Dsiplay(task.getDateEnd()));
viewHolderDone.itemClass.setText(task.getClassName());
viewHolderDone.itemTask.setText(task.getSerNum());
viewHolderDone.itemChecked.setText(db2Dsiplay(task.getDateChecked()));
return view;
}
/**
* ViewHolder pattern class to efficiently store and reuse views for completed task list items.
* <p>
* This class holds references to the {@link TextView}s within each row of the ListView
* that display the details of a completed task.
*/
private class ViewHolderDone {
TextView itemDue, itemClass, itemTask, itemChecked;
/**
* Constructs a new ViewHolderDone.
* Initializes all the {@link TextView}s within the item layout by finding them by their ID.
*
* @param view The root view of the item layout (e.g., a row in the ListView).
*/
public ViewHolderDone(View view) {
itemDue = (TextView)view.findViewById(R.id.tVDoneDue);
itemClass = (TextView) view.findViewById(R.id.tVDoneClass);
itemTask = (TextView) view.findViewById(R.id.tVDoneTask);
itemChecked = (TextView) view.findViewById(R.id.tVDoneChecked);
}
}
}
Student.java
Path: Student.java
/*
* Model class for a student
*/
package com.example.tasks.models;
public class Student {
/**
* ClassName : יא571
* FullName : עובד איל
* ID : 011972
* NickName : איל
* Sex : ז
*/
private String ClassName;
private String FullName;
private String ID;
private String NickName;
private String Sex;
public String getClassName() {
return ClassName;
}
public void setClassName(String ClassName) {
this.ClassName = ClassName;
}
public String getFullName() {
return FullName;
}
public void setFullName(String FullName) {
this.FullName = FullName;
}
public String getID() {
return ID;
}
public void setID(String ID) {
this.ID = ID;
}
public String getNickName() {
return NickName;
}
public void setNickName(String NickName) {
this.NickName = NickName;
}
public String getSex() {
return Sex;
}
public void setSex(String Sex) {
this.Sex = Sex;
}
}
MasterActivity.java
Path: MasterActivity.java
package com.example.tasks.Obj;
import android.app.ActivityManager;
import android.content.Intent;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.example.tasks.Activities.MainActivity;
import com.example.tasks.Activities.DoneTasksActivity;
import com.example.tasks.Activities.TaskActivity;
import com.example.tasks.Activities.YearsActivity;
import com.example.tasks.Activities.PresenceActivity;
import com.example.tasks.Activities.ReportsActivity;
import com.example.tasks.Activities.ProfileActivity;
import com.example.tasks.Activities.MaakavActivity;
import com.example.tasks.R;
import java.util.List;
/**
* Base activity providing common menu handling and dynamic title update.
* All feature Activities should extend this class instead of AppCompatActivity.
*/
public abstract class MasterActivity extends AppCompatActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
String activityName = taskInfo.get(0).topActivity.getClassName();
int itemId = item.getItemId();
if (itemId == R.id.idMain) {
if (!activityName.equals(MainActivity.class.getName())) {
Log.i("MasterActivity", "Changing to MainActivity");
startActivity(new Intent(this, MainActivity.class));
}
} else if (itemId == R.id.idTasksDone) {
if (!activityName.equals(DoneTasksActivity.class.getName())) {
Log.i("MasterActivity", "Changing to DoneTasksActivity");
startActivity(new Intent(this, DoneTasksActivity.class));
}
} else if (itemId == R.id.idYears) {
if (!activityName.equals(YearsActivity.class.getName())) {
Log.i("MasterActivity", "Changing to YearsActivity");
startActivity(new Intent(this, YearsActivity.class));
}
} else if (itemId == R.id.idReports) {
if (!activityName.equals(ReportsActivity.class.getName())) {
Log.i("MasterActivity", "Changing to ReportsActivity");
startActivity(new Intent(this, ReportsActivity.class));
}
} else if (itemId == R.id.idProfile) {
if (!activityName.equals(ProfileActivity.class.getName())) {
Log.i("MasterActivity", "Changing to ProfileActivity");
startActivity(new Intent(this, ProfileActivity.class));
}
} else if (itemId == R.id.idPresence) {
if (!activityName.equals(PresenceActivity.class.getName())) {
Log.i("MasterActivity", "Changing to PresenceActivity");
startActivity(new Intent(this, PresenceActivity.class));
}
} else if (itemId == R.id.idMaakav) {
if (!activityName.equals(MaakavActivity.class.getName())) {
Log.i("MasterActivity", "Changing to MaakavActivity");
startActivity(new Intent(this, MaakavActivity.class));
}
} else if (itemId == R.id.idDisconnect) {
showDisconnectDialog();
return true;
} else if (itemId == R.id.idExit) {
showExitDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onResume() {
super.onResume();
updateTitle();
}
/**
* Sets the ActionBar title based on the concrete subclass.
*/
private void updateTitle() {
String title = "Presence";
Class<?> cls = getClass();
if (cls.equals(PresenceActivity.class)) {
title += "/נוכחות";
} else if (cls.equals(ReportsActivity.class)) {
title += "/דיווחים";
} else if (cls.equals(ProfileActivity.class)) {
title += "/פרופיל";
} else if (cls.equals(MaakavActivity.class)) {
title += "/מעקב";
} else if (cls.equals(MainActivity.class)) {
title += "/משימות";
} else if (cls.equals(TaskActivity.class)) {
title += "/משימה";
} else if (cls.equals(YearsActivity.class)) {
title += "/שנה";
} else if (cls.equals(DoneTasksActivity.class)) {
title += "/משימות שהושלמו";
}
ActionBar ab = getSupportActionBar();
if (ab != null) {
ab.setTitle(title);
} else {
setTitle(title);
}
}
private void showDisconnectDialog() {
new AlertDialog.Builder(this)
.setTitle("Disconnect Account")
.setMessage("Are you sure you want to disconnect account & exit?")
.setPositiveButton("Ok", (dialog, which) -> {
// perform sign out logic
finishAffinity();
})
.setNeutralButton("Cancel", (dialog, which) -> dialog.cancel())
.setCancelable(false)
.show();
}
private void showExitDialog() {
new AlertDialog.Builder(this)
.setTitle("Quit Application")
.setMessage("Are you sure?")
.setPositiveButton("Ok", (dialog, which) -> finishAffinity())
.setNeutralButton("Cancel", (dialog, which) -> dialog.cancel())
.setCancelable(false)
.show();
}
}
FBRef.java
Path: FBRef.java
package com.example.tasks;
import com.example.tasks.models.Student;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import java.util.ArrayList;
import java.util.List;
import android.util.Log;
import com.google.firebase.database.ValueEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.example.tasks.models.Student;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
// author: Guy Siedes 3strategy@gmail.com
// with GPT o4 mini high, private chat : https://chatgpt.com/c/6878cad2-5d50-800e-8499-6db3a8fb9d88
// usage guideline: https://מבני.שלי.com/android/projectSteps/newFBref
public class FBRef {
// all students is loaded asynchronouly from RTDB during Login, and openning of the
// PresenceActivity is conditioned by its completion.
public static List<Student> allStudents = new ArrayList<>();
// ─── auth & root DB ───────────────────────────────────────────────
public static FirebaseAuth refAuth = FirebaseAuth.getInstance();
public static FirebaseDatabase FBDB = FirebaseDatabase.getInstance();
// ─── your existing roots ─────────────────────────────────────────
/** root of all users; you still do refUsers.child(uid)… elsewhere */
public static DatabaseReference refUsers = FBDB.getReference("Users");
public static DatabaseReference refTasks,
refDoneTasks,
refYears,
refStudents,
refMaakav;
// NEW: smart tree root: P{YY}.{uid}
public static DatabaseReference refPresenceRoot,
refStudentsYear,
refMaakavYear;
public static DatabaseReference refPresUidCurrentWeek;
public static String uid;
/**
* Call on login to set uid + your existing refs,
* and also set up Students/Presence/Maakav for this user.
*/
public static void getUser(FirebaseUser fbuser) {
uid = fbuser.getUid();
refTasks = FBDB.getReference("Tasks").child(uid);
refDoneTasks = FBDB.getReference("Done_Tasks").child(uid);
refYears = FBDB.getReference("Years").child(uid);
// ─── NEW branches ───
refStudents = FBDB.getReference("Students").child(uid);
refMaakav = FBDB.getReference("Maakav").child(uid);
int activeYear = Calendar.getInstance().get(Calendar.YEAR); // or SharedPreferences.getInt(...)
setActiveYear(activeYear);
}
/**
* Once you know activeYear (e.g. from SharedPreferences),
* call this so your three new branches narrow to {uid}/{year}.
*/
public static void setActiveYear(int activeYear) {
String yy = String.valueOf(activeYear).substring(2); // 👈 e.g. "25"
String rootKey = "P" + yy + "_" + uid; // 👈 P25.abcd123
refPresenceRoot = FBDB.getReference(rootKey); // 👈 single root for that teacher-year
refStudentsYear = refStudents.child(String.valueOf(activeYear));
refMaakavYear = refMaakav.child(String.valueOf(activeYear));
// Also set current week subnode reference
int week = Calendar.getInstance().get(Calendar.WEEK_OF_YEAR);
refPresUidCurrentWeek = refPresenceRoot.child("W" + week); // e.g., P25.abcd123/W29
}
public static void loadAllStudents(final Runnable onComplete) {
allStudents.clear();
refStudentsYear.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot child : snapshot.getChildren()) {
allStudents.add(child.getValue(Student.class));
}
if (onComplete != null) onComplete.run();
}
@Override
public void onCancelled(DatabaseError error) {
Log.e("FBRef", "Failed to load students", error.toException());
if (onComplete != null) onComplete.run();
}
});
}
/**
* Convenience overload: do both in one call.
*/
// public static void getUser(FirebaseUser fbuser, int activeYear) {
// getUser(fbuser);
// setActiveYear(activeYear);
// }
}
AndroidManifest.xml
Path: AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.DayNight.DarkActionBar"
tools:targetApi="31">
<activity
android:name=".Activities.MaakavActivity"
android:exported="false" />
<activity
android:name=".Activities.ProfileActivity"
android:exported="false" />
<!-- PresenceActivity locked to portrait to prevent restarts/data loss -->
<activity
android:name=".Activities.PresenceActivity"
android:screenOrientation="portrait"
android:exported="true" />
<activity
android:name=".Activities.ReportsActivity"
android:exported="false" />
<activity
android:name=".Activities.LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".Activities.YearsActivity"
android:exported="false" />
<activity
android:name=".Activities.DoneTasksActivity"
android:exported="false" />
<activity
android:name=".Activities.TaskActivity"
android:exported="false" />
<activity
android:name=".Obj.MasterActivity"
android:exported="false" />
<activity
android:name=".Activities.MainActivity"
android:exported="false" />
</application>
</manifest>
activity_done_tasks.xml
Path: activity_done_tasks.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context=".Activities.DoneTasksActivity">
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:text="Checked Tasks:"
android:textSize="36dp"
android:textStyle="bold" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:text="Set Year:"
android:textSize="18dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<Spinner
android:id="@+id/spYears"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:text="Due Date"
android:textSize="18dp"
android:textStyle="bold" />
<TextView
android:id="@+id/tVClass"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:onClick="orderByClass"
android:text="Class"
android:textSize="18dp"
android:textStyle="bold" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="Task"
android:textSize="18dp"
android:textStyle="bold" />
<TextView
android:id="@+id/tVChecked"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:onClick="orderByChecked"
android:text="Checked"
android:textSize="18dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="6"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<ListView
android:id="@+id/lVDone"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="9" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
activity_login.xml
Path: activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
tools:context=".Activities.LoginActivity">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5" />
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:id="@+id/tVtitle"
android:text="Login"
android:autoSizeTextType="uniform"
android:textStyle="bold" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:id="@+id/eTname"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:visibility="invisible"
android:ems="10"
android:hint="name"
android:inputType="textPersonName" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="7" />
<Button
android:id="@+id/btn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="5"
android:onClick="logorreg"
android:text="Login" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5" />
<EditText
android:id="@+id/eTemail"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:ems="10"
android:hint="e-mail"
android:inputType="textEmailAddress" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5" />
<EditText
android:id="@+id/eTpass"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:ems="10"
android:hint="password"
android:inputType="textPassword" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5" />
<CheckBox
android:id="@+id/cBstayconnect"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="Stay Connected" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tVregister"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.5" />
</LinearLayout>
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
activity_maakav.xml
Path: activity_maakav.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Activities.MaakavActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
activity_main.xml
Path: activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context=".Activities.MainActivity">
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5" />
<TextView
android:id="@+id/tVMainHeader"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:gravity="center"
android:text="Your Active Tasks:"
android:textSize="36dp"
android:textStyle="bold" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3.2"
android:gravity="center"
android:text="Due Date"
android:textSize="16dp"
android:textStyle="bold" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.6"
android:gravity="center"
android:text="Class"
android:textSize="16dp"
android:textStyle="bold" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.6"
android:gravity="center"
android:text="Task"
android:textSize="16dp"
android:textStyle="bold" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1.6"
android:gravity="center"
android:text="Full\nClass"
android:textSize="16dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="6"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<ListView
android:id="@+id/lVMain"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="8" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:onClick="addTask"
android:text="Add New Task"
android:textSize="24dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
activity_master.xml
Path: activity_master.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".Obj.MasterActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
activity_presence.xml
Path: activity_presence.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Activities.PresenceActivity">
<!-- Class name label -->
<TextView
android:id="@+id/classNameLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="כיתה: "
android:textSize="20sp"
android:textAlignment="viewEnd"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_margin="16dp" />
<!-- Missing students label -->
<TextView
android:id="@+id/missingStudentsLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="לא דווחו: "
android:textColor="#B00020"
android:background="#FFDDDD"
android:padding="10dp"
app:layout_constraintTop_toBottomOf="@id/classNameLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp" />
<!-- Raw transcript display -->
<TextView
android:id="@+id/transcriptTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="(waiting for speech...)"
android:textColor="#007F00"
android:background="#DDFFDD"
android:padding="10dp"
app:layout_constraintTop_toBottomOf="@id/missingStudentsLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"/>
<!-- Insert these TextViews just below the rawTranscriptTextView -->
<TextView
android:id="@+id/statusSummaryTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="סטטוס נוכחות:"
android:textSize="16sp"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/transcriptTextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/disturbanceLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="הפרעות:"
android:textColor="#B00020"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/statusSummaryTextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Start and Stop buttons -->
<LinearLayout
android:id="@+id/buttonRow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/disturbanceLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp">
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Record"
android:layout_marginEnd="10dp" />
<Button
android:id="@+id/btnStop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop Rec"
android:enabled="false" />
</LinearLayout>
<!-- Attendance list pushes off the bottom -->
<ListView
android:id="@+id/attendanceList"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/buttonRow"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_margin="16dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity_profile.xml
Path: activity_profile.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".Activities.ProfileActivity">
<TextView
android:id="@+id/nameLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Name:"
style="@style/TextAppearance.AppCompat.Large"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/usernameEdit"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:hint="User name"
android:padding="8dp"
android:enabled="false"
app:layout_constraintTop_toBottomOf="@id/nameLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/yearLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Active Year:"
style="@style/TextAppearance.AppCompat.Large"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/usernameEdit"
app:layout_constraintStart_toStartOf="parent" />
<Spinner
android:id="@+id/activeYearSpinner"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:contentDescription="Active Year"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/yearLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/defaultLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Default Screen:"
style="@style/TextAppearance.AppCompat.Large"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/activeYearSpinner"
app:layout_constraintStart_toStartOf="parent" />
<Spinner
android:id="@+id/defaultScreenSpinner"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:contentDescription="Default Screen"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@id/defaultLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?android:attr/listDivider"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/defaultScreenSpinner"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/takePictureBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Take Picture"
app:layout_constraintTop_toBottomOf="@id/defaultScreenSpinner"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="16dp" />
<ImageView
android:id="@+id/profileImage"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginTop="16dp"
android:scaleType="centerCrop"
app:layout_constraintTop_toBottomOf="@id/takePictureBtn"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/tvStudentsSheetLink"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Link to Google Sheet"
android:autoLink="web"
android:textColor="@android:color/holo_blue_dark"
android:clickable="true"
android:focusable="true"
android:padding="16dp"
app:layout_constraintTop_toBottomOf="@id/profileImage"
app:layout_constraintStart_toStartOf="parent"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>
activity_reports.xml
Path: activity_reports.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Activities.ReportsActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
activity_task.xml
Path: activity_task.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context=".Activities.TaskActivity">
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<TextView
android:id="@+id/tVTaskHeader"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:autoSizeTextType="uniform"
android:gravity="center"
android:text="Add Task"
android:textSize="28dp"
android:textStyle="bold" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<Spinner
android:id="@+id/spClass"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:textAlignment="center" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<EditText
android:id="@+id/eTTaskNum"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:ems="10"
android:gravity="center_horizontal"
android:hint="Task number"
android:inputType="number"
android:textSize="18dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<CheckBox
android:id="@+id/cbFullClass"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:layoutDirection="rtl"
android:text="Full Class"
android:textAlignment="center"
android:textSize="18dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:text="Start Date:"
android:textSize="20dp"
android:textStyle="bold" />
<TextView
android:id="@+id/tVStartDate"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="5"
android:gravity="center"
android:hint="set start date"
android:onClick="datePick"
android:textSize="18dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:text="Due Date:"
android:textSize="20dp"
android:textStyle="bold" />
<TextView
android:id="@+id/tVDueDate"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="5"
android:gravity="center"
android:hint="set due date"
android:onClick="datePick"
android:textSize="18dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<Button
android:id="@+id/btnTask"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:onClick="confirmation"
android:text="Add Task"
android:textSize="24dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4" />
</LinearLayout>
activity_years.xml
Path: activity_years.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="horizontal"
tools:context=".Activities.YearsActivity">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="10"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:onClick="addNewYear"
android:text="Add\nNew year"
android:textSize="20dp"
android:textStyle="bold" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:text="Set\nActive Year:"
android:textSize="18dp"
android:textStyle="bold" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<Spinner
android:id="@+id/spYears"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="3"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:onClick="addNewClass"
android:text="Add\nNew class"
android:textSize="18dp"
android:textStyle="bold" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<ListView
android:id="@+id/lVClasses"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<Button
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:onClick="done"
android:text="Done"
android:textSize="24dp"
android:textStyle="bold" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
donetask_layout.xml
Path: donetask_layout.xml
<?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="50dp">
<TextView
android:id="@+id/tVDoneDue"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:textSize="18dp" />
<TextView
android:id="@+id/tVDoneClass"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:textSize="18dp" />
<TextView
android:id="@+id/tVDoneTask"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:textSize="18dp" />
<TextView
android:id="@+id/tVDoneChecked"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:gravity="center"
android:textSize="18dp" />
</LinearLayout>
task_layout.xml
Path: task_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/llTask"
android:layout_width="match_parent"
android:layout_height="50dp"
android:descendantFocusability="blocksDescendants">
<TextView
android:id="@+id/tVDue"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:textSize="18dp" />
<TextView
android:id="@+id/tVClass"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:textSize="18dp" />
<TextView
android:id="@+id/tVTask"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:textSize="18dp" />
<ToggleButton
android:id="@+id/tBFull"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:clickable="false"
android:textOff="Part"
android:textOn="Full"
android:textSize="16dp" />
</LinearLayout>