diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index af0a23d80..af9c5dd82 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -848,6 +848,11 @@
android:name=".externalevents.opentracks.OpenTracksController"
android:label="OpenTracks controller and intent receiver"
android:exported="true"/>
+
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java
index 007b66783..9012d9131 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DashboardFragment.java
@@ -25,7 +25,6 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -42,13 +41,13 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.AbstractDashboardWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardActiveTimeWidget;
+import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardCalendarActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardDistanceWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardGoalsWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardSleepWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardStepsWidget;
import nodomain.freeyourgadget.gadgetbridge.activities.dashboard.DashboardTodayWidget;
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
-import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class DashboardFragment extends Fragment {
@@ -107,8 +106,9 @@ public class DashboardFragment extends Fragment {
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.dashboard_show_calendar:
- // TODO: implement calendar activity
- GB.toast("The calendar view is not implemented yet", Toast.LENGTH_SHORT, GB.INFO);
+ Intent intent = new Intent(requireActivity(), DashboardCalendarActivity.class);
+ intent.putExtra(DashboardCalendarActivity.EXTRA_TIMESTAMP, day.getTimeInMillis());
+ startActivityForResult(intent, 0);
return false;
}
return super.onOptionsItemSelected(item);
@@ -117,6 +117,12 @@ public class DashboardFragment extends Fragment {
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == 0 && resultCode == DashboardCalendarActivity.RESULT_OK && data != null) {
+ long timeMillis = data.getLongExtra(DashboardCalendarActivity.EXTRA_TIMESTAMP, 0);
+ if (timeMillis != 0) {
+ day.setTimeInMillis(timeMillis);
+ }
+ }
gridLayout.removeAllViews();
todayWidget = null;
goalsWidget = null;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCalendarActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCalendarActivity.java
new file mode 100644
index 000000000..4fb4a2c5a
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/dashboard/DashboardCalendarActivity.java
@@ -0,0 +1,164 @@
+/* Copyright (C) 2023-2024 Arjan Schrijver
+
+ This file is part of Gadgetbridge.
+
+ Gadgetbridge is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Gadgetbridge is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see . */
+package nodomain.freeyourgadget.gadgetbridge.activities.dashboard;
+
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.Gravity;
+import android.widget.TextView;
+
+import androidx.gridlayout.widget.GridLayout;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
+
+public class DashboardCalendarActivity extends AbstractGBActivity {
+ private static final Logger LOG = LoggerFactory.getLogger(DashboardCalendarActivity.class);
+ public static String EXTRA_TIMESTAMP = "dashboard_calendar_chosen_day";
+
+ TextView monthTextView;
+ TextView arrowLeft;
+ TextView arrowRight;
+ GridLayout calendarGrid;
+ Calendar cal;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_dashboard_calendar);
+ monthTextView = findViewById(R.id.calendar_month);
+ calendarGrid = findViewById(R.id.dashboard_calendar_grid);
+ cal = Calendar.getInstance();
+ long receivedTimestamp = getIntent().getLongExtra(EXTRA_TIMESTAMP, 0);
+ if (receivedTimestamp != 0) cal.setTimeInMillis(receivedTimestamp);
+
+ arrowLeft = findViewById(R.id.arrow_left);
+ arrowLeft.setOnClickListener(v -> {
+ cal.add(Calendar.MONTH, -1);
+ draw();
+ });
+ arrowRight = findViewById(R.id.arrow_right);
+ arrowRight.setOnClickListener(v -> {
+ Calendar today = GregorianCalendar.getInstance();
+ if (!DateTimeUtils.isSameMonth(today, cal)) {
+ cal.add(Calendar.MONTH, 1);
+ draw();
+ }
+ });
+
+ draw();
+ }
+
+ private void draw() {
+ // Remove previous calendar days
+ calendarGrid.removeAllViews();
+ // Update month display
+ SimpleDateFormat monthFormat = new SimpleDateFormat("LLLL yyyy", Locale.getDefault());
+ monthTextView.setText(monthFormat.format(cal.getTime()));
+ Calendar today = GregorianCalendar.getInstance();
+ today.set(Calendar.HOUR, 23);
+ today.set(Calendar.MINUTE, 59);
+ today.set(Calendar.SECOND, 59);
+ if (DateTimeUtils.isSameMonth(today, cal)) {
+ arrowRight.setAlpha(0.5f);
+ } else {
+ arrowRight.setAlpha(1);
+ }
+ // Calculate grid cell size for dates
+ DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ int screenWidth = displayMetrics.widthPixels;
+ int cellSize = screenWidth / 7;
+ // Determine first day that should be displayed
+ Calendar drawCal = (Calendar) cal.clone();
+ drawCal.set(Calendar.DAY_OF_MONTH, 1);
+ int displayMonth = drawCal.get(Calendar.MONTH);
+ int firstDayOfWeek = cal.getFirstDayOfWeek();
+ int daysToFirstDay = (drawCal.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek + 7) % 7;
+ drawCal.add(Calendar.DAY_OF_MONTH, -daysToFirstDay);
+ // Determine last day that should be displayed
+ Calendar lastDay = (Calendar) cal.clone();
+ lastDay.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
+ lastDay.add(Calendar.DAY_OF_MONTH, (firstDayOfWeek + 7 - lastDay.get(Calendar.DAY_OF_WEEK)) % 7);
+ // Add day names header
+ SimpleDateFormat dayFormat = new SimpleDateFormat("E", Locale.getDefault());
+ Calendar weekdays = Calendar.getInstance();
+ for (int i=0; i<7; i++) {
+ int currentDayOfWeek = (firstDayOfWeek + i - 1) % 7 + 1;
+ weekdays.set(Calendar.DAY_OF_WEEK, currentDayOfWeek);
+ createWeekdayCell(dayFormat.format(weekdays.getTime()), cellSize);
+ }
+ // Loop through month days and create grid cells for them
+ while (!DateTimeUtils.isSameDay(drawCal, lastDay)) {
+ boolean clickable = drawCal.get(Calendar.MONTH) == displayMonth;
+ if (drawCal.after(today)) clickable = false;
+ createDateCell(drawCal, cellSize, clickable);
+ drawCal.add(Calendar.DAY_OF_MONTH, 1);
+ }
+ }
+
+ private TextView prepareGridElement(int cellSize) {
+ GridLayout.LayoutParams layoutParams = new GridLayout.LayoutParams(
+ GridLayout.spec(GridLayout.UNDEFINED, GridLayout.FILL,1f),
+ GridLayout.spec(GridLayout.UNDEFINED, 1, GridLayout.FILL,1f)
+ );
+ int margin = cellSize / 10;
+ layoutParams.width = 0;
+ layoutParams.height = cellSize - 2 * margin;
+ layoutParams.setMargins(margin, margin, margin, margin);
+ TextView text = new TextView(this);
+ text.setLayoutParams(layoutParams);
+ text.setGravity(Gravity.CENTER);
+ return text;
+ }
+
+ private void createWeekdayCell(String day, int cellSize) {
+ TextView text = prepareGridElement(cellSize);
+ text.setText(day);
+ calendarGrid.addView(text);
+ }
+
+ private void createDateCell(Calendar day, int cellSize, boolean clickable) {
+ final long timestamp = day.getTimeInMillis();
+ TextView text = prepareGridElement(cellSize);
+ text.setText(String.valueOf(day.get(Calendar.DAY_OF_MONTH)));
+ if (clickable) {
+ GradientDrawable backgroundDrawable = new GradientDrawable();
+ backgroundDrawable.setShape(GradientDrawable.OVAL);
+ backgroundDrawable.setColor(Color.argb(50, 128, 128, 128));
+ text.setBackground(backgroundDrawable);
+ text.setOnClickListener(v -> {
+ Intent resultIntent = new Intent();
+ resultIntent.putExtra(EXTRA_TIMESTAMP, timestamp);
+ setResult(RESULT_OK, resultIntent);
+ finish();
+ });
+ }
+ calendarGrid.addView(text);
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java
index 7fb4bf638..2cacba8bc 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DateTimeUtils.java
@@ -236,4 +236,16 @@ public class DateTimeUtils {
&& calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH)
&& calendar1.get(Calendar.DAY_OF_MONTH) == calendar2.get(Calendar.DAY_OF_MONTH);
}
+
+ /**
+ * Determine whether two Calendar instances are in the same month
+ *
+ * @param calendar1 The first calendar to compare
+ * @param calendar2 The second calendar to compare
+ * @return true if the Calendar instances are in the same month
+ */
+ public static boolean isSameMonth(Calendar calendar1, Calendar calendar2) {
+ return calendar1.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR)
+ && calendar1.get(Calendar.MONTH) == calendar2.get(Calendar.MONTH);
+ }
}
diff --git a/app/src/main/res/layout/activity_dashboard_calendar.xml b/app/src/main/res/layout/activity_dashboard_calendar.xml
new file mode 100644
index 000000000..e7eb7cb34
--- /dev/null
+++ b/app/src/main/res/layout/activity_dashboard_calendar.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+