Skip to content

Commit

Permalink
Set alarms for a specific date - Fix #4
Browse files Browse the repository at this point in the history
- Only one date can be set for each alarm;
- The switch button for activating the alarm has been moved slightly downwards;
- Update Readme, screenshots and strings (about_dialog_message and first_launch_main_feature_message);
  • Loading branch information
BlackyHawky committed Nov 11, 2024
1 parent 5640713 commit 00d6ef2
Show file tree
Hide file tree
Showing 21 changed files with 523 additions and 89 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Clock is a privacy-conscious open-source clock, based on AOSP Clock.
* Turn off/postpone the alarm with the power button or volume buttons;
* For Snapdragon phones only, the alarm is triggered when the phone is switched off;
* Unfortunately, this feature may not work on some devices despite the presence of the _“com.qualcomm.qti.poweroffalarm”_ system app. See the discussion [here](https://github.com/BlackyHawky/Clock/issues/88).
* Set alarms for a specific date;
* Swipe to delete an alarm;
* Duplicate alarms;
* Customizable alarm title;
Expand Down Expand Up @@ -56,7 +57,7 @@ Before opening a new issue, be sure to check the following:
- **Does the issue already exist?** Make sure a similar issue has not been reported by browsing [existing issues](https://github.com/BlackyHawky/Clock/issues). Please search open and closed issues.
- **Is the issue still relevant?** Make sure your issue is not already fixed in the latest version of Clock.
- **Did you use the issue template?** It is important to make life of our kind contributors easier by avoiding issues that miss key information to their resolution.
Note that issues that that ignore part of the issue template will likely get treated with very low priority, as often they are needlessly hard to read or understand (e.g. huge screenshots, or addressing multiple topics).
Note that issues that ignore part of the issue template will likely get treated with very low priority, as often they are needlessly hard to read or understand (e.g. huge screenshots, or addressing multiple topics).

## Translation
Translations can be added using [Weblate](https://translate.codeberg.org/projects/clock/). You will need an account to update translations and add languages. Add the language you want to translate to in Languages -> Manage translated languages in the top menu bar.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,6 @@ public void onUpdateFabButtons(@NonNull ImageView left, @NonNull ImageView right
right.setVisibility(View.INVISIBLE);
}


public void startCreatingAlarm() {
// Clear the currently selected alarm.
mAlarmTimeClickHandler.setSelectedAlarm(null);
Expand Down
15 changes: 9 additions & 6 deletions app/src/main/java/com/best/deskclock/AlarmUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,19 @@ static String formatElapsedTimeUntilAlarm(Context context, long delta) {
final long remainder = delta % DateUtils.MINUTE_IN_MILLIS;
delta += remainder == 0 ? 0 : (DateUtils.MINUTE_IN_MILLIS - remainder);

int hours = (int) delta / (1000 * 60 * 60);
final int minutes = (int) delta / (1000 * 60) % 60;
final int days = hours / 24;
hours = hours % 24;
// Calculating the number of days
long totalDays = delta / (1000 * 60 * 60 * 24);

String daySeq = Utils.getNumberFormattedQuantityString(context, R.plurals.days, days);
// Calculating the remainder in hours and minutes after extracting the days
long remainingMillis = delta % (1000 * 60 * 60 * 24);
int hours = (int) (remainingMillis / (1000 * 60 * 60));
int minutes = (int) (remainingMillis % (1000 * 60 * 60)) / (1000 * 60);

String daySeq = Utils.getNumberFormattedQuantityString(context, R.plurals.days, (int) totalDays);
String minSeq = Utils.getNumberFormattedQuantityString(context, R.plurals.minutes, minutes);
String hourSeq = Utils.getNumberFormattedQuantityString(context, R.plurals.hours, hours);

final boolean showDays = days > 0;
final boolean showDays = totalDays > 0;
final boolean showHours = hours > 0;
final boolean showMinutes = minutes > 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,14 @@
import com.best.deskclock.events.Events;
import com.best.deskclock.provider.Alarm;
import com.best.deskclock.ringtone.RingtonePickerActivity;
import com.google.android.material.datepicker.CalendarConstraints;
import com.google.android.material.datepicker.DateValidatorPointForward;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.timepicker.MaterialTimePicker;
import com.google.android.material.timepicker.TimeFormat;

import java.util.Calendar;
import java.util.TimeZone;

/**
* Click handler for an alarm time item.
Expand Down Expand Up @@ -154,8 +158,13 @@ public void onClockClicked(Alarm alarm) {
ShowMaterialTimePicker(alarm.hour, alarm.minutes);
}

private void ShowMaterialTimePicker(int hour, int minute) {
public void onDateClicked(Alarm alarm) {
mSelectedAlarm = alarm;
Events.sendAlarmEvent(R.string.action_set_date, R.string.label_deskclock);
ShowMaterialDatePicker(alarm);
}

private void ShowMaterialTimePicker(int hour, int minute) {
@TimeFormat int clockFormat;
boolean isSystem24Hour = DateFormat.is24HourFormat(mFragment.getContext());
clockFormat = isSystem24Hour ? TimeFormat.CLOCK_24H : TimeFormat.CLOCK_12H;
Expand All @@ -179,6 +188,37 @@ private void ShowMaterialTimePicker(int hour, int minute) {
});
}

public void ShowMaterialDatePicker(Alarm alarm) {
MaterialDatePicker.Builder<Long> builder = MaterialDatePicker.Builder.datePicker();
// If a date has already been selected, select it when opening the MaterialDatePicker.
if (alarm.isDateSpecified()) {
Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
calendar.set(alarm.year, alarm.month, alarm.day);
long date = calendar.getTimeInMillis();
builder.setSelection(date);
}

// Do not allow selection of past dates.
CalendarConstraints.Builder constraintsBuilder = new CalendarConstraints.Builder();
constraintsBuilder.setValidator(DateValidatorPointForward.now());
builder.setCalendarConstraints(constraintsBuilder.build());
MaterialDatePicker<Long> materialDatePicker = builder.build();

Context context = mFragment.requireContext();
materialDatePicker.show(((AppCompatActivity) context).getSupportFragmentManager(), TAG);
materialDatePicker.addOnPositiveButtonClickListener(selection -> {
// Selection contains the selected date as a timestamp (long)
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(selection);

int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);

onDateSet(year, month, dayOfMonth, alarm.hour, alarm.minutes);
});
}

public void onRingtoneClicked(Context context, Alarm alarm) {
mSelectedAlarm = alarm;
Events.sendAlarmEvent(R.string.action_set_ringtone, R.string.label_deskclock);
Expand Down Expand Up @@ -210,7 +250,49 @@ public void onTimeSet(int hourOfDay, int minute) {
} else {
mSelectedAlarm.hour = hourOfDay;
mSelectedAlarm.minutes = minute;
mSelectedAlarm.enabled = true;
mAlarmUpdateHandler.asyncUpdateAlarm(mSelectedAlarm, true, false);
mSelectedAlarm = null;
}
}

public void onDateSet(int year, int month, int day, int hourOfDay, int minute) {
if (mSelectedAlarm == null) {
// If mSelectedAlarm is null then we're creating a new alarm.
final Alarm alarm = new Alarm();
final boolean areAlarmVibrationsEnabledByDefault = DataModel.getDataModel().areAlarmVibrationsEnabledByDefault();
final boolean isOccasionalAlarmDeletedByDefault = DataModel.getDataModel().isOccasionalAlarmDeletedByDefault();
alarm.year = year;
alarm.month = month;
alarm.day = day;
alarm.hour = hourOfDay;
alarm.minutes = minute;
alarm.enabled = true;
alarm.dismissAlarmWhenRingtoneEnds = false;
alarm.alarmSnoozeActions = true;
alarm.vibrate = areAlarmVibrationsEnabledByDefault;
alarm.deleteAfterUse = isOccasionalAlarmDeletedByDefault;
mAlarmUpdateHandler.asyncAddAlarm(alarm);
} else {
mSelectedAlarm.year = year;
mSelectedAlarm.month = month;
mSelectedAlarm.day = day;
mSelectedAlarm.hour = hourOfDay;
mSelectedAlarm.minutes = minute;
mAlarmUpdateHandler.asyncUpdateAlarm(mSelectedAlarm, true, false);
mSelectedAlarm = null;
}
}

public void removeDate(Alarm alarm) {
if (mSelectedAlarm == null) {
alarm.year = Calendar.getInstance().get(Calendar.YEAR);
alarm.month = Calendar.getInstance().get(Calendar.MONTH);
alarm.day = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
mAlarmUpdateHandler.asyncUpdateAlarm(alarm, true, false);
} else {
mSelectedAlarm.year = Calendar.getInstance().get(Calendar.YEAR);
mSelectedAlarm.month = Calendar.getInstance().get(Calendar.MONTH);
mSelectedAlarm.day = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
mAlarmUpdateHandler.asyncUpdateAlarm(mSelectedAlarm, true, false);
mSelectedAlarm = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
Expand All @@ -29,7 +30,9 @@
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.color.MaterialColors;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;

/**
* Abstract ViewHolder for alarm time items.
Expand Down Expand Up @@ -60,12 +63,12 @@ public AlarmItemViewHolder(View itemView) {

final MaterialCardView itemCardView = itemView.findViewById(R.id.item_card_view);
final boolean isCardBackgroundDisplayed = DataModel.getDataModel().isCardBackgroundDisplayed();
final String getDarkMode = DataModel.getDataModel().getDarkMode();
final String darkMode = DataModel.getDataModel().getDarkMode();
if (isCardBackgroundDisplayed) {
itemCardView.setCardBackgroundColor(
MaterialColors.getColor(context, com.google.android.material.R.attr.colorSurface, Color.BLACK)
);
} else if (Utils.isNight(context.getResources()) && getDarkMode.equals(KEY_AMOLED_DARK_MODE)) {
} else if (Utils.isNight(context.getResources()) && darkMode.equals(KEY_AMOLED_DARK_MODE)) {
itemCardView.setCardBackgroundColor(Color.BLACK);
} else {
itemCardView.setCardBackgroundColor(
Expand Down Expand Up @@ -155,12 +158,26 @@ private void bindUpcomingInstance(Context context, Alarm alarm) {
if (alarm.daysOfWeek.isRepeating()) {
upcomingInstanceLabel.setVisibility(View.GONE);
} else {
final String labelText;
labelText = Alarm.isTomorrow(alarm, Calendar.getInstance())
? context.getString(R.string.alarm_tomorrow)
: context.getString(R.string.alarm_today);
if (Alarm.isTomorrow(alarm, Calendar.getInstance()) && !alarm.isDateSpecified()) {
upcomingInstanceLabel.setText(context.getString(R.string.alarm_tomorrow));
} else if (alarm.isDateSpecified()) {
if (Alarm.isDateSpecifiedTomorrow(alarm.year, alarm.month, alarm.day)) {
upcomingInstanceLabel.setText(context.getString(R.string.alarm_tomorrow));
} else {
int year = alarm.year;
int month = alarm.month;
int dayOfMonth = alarm.day;
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, dayOfMonth);
String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMMd");
SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, Locale.getDefault());
String formattedDate = dateFormat.format(calendar.getTime());
upcomingInstanceLabel.setText(context.getString(R.string.alarm_scheduled_for, formattedDate));
}
} else {
upcomingInstanceLabel.setText(context.getString(R.string.alarm_today));
}
upcomingInstanceLabel.setVisibility(View.VISIBLE);
upcomingInstanceLabel.setText(labelText);
upcomingInstanceLabel.setAlpha(alarm.enabled ? CLOCK_ENABLED_ALPHA : CLOCK_DISABLED_ALPHA);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Vibrator;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

Expand All @@ -40,7 +42,10 @@
import com.best.deskclock.uidata.UiDataModel;
import com.google.android.material.chip.Chip;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;

/**
* A ViewHolder containing views for an alarm item in expanded state.
Expand All @@ -49,6 +54,12 @@ public final class ExpandedAlarmViewHolder extends AlarmItemViewHolder {
public static final int VIEW_TYPE = R.layout.alarm_time_expanded;

public final LinearLayout repeatDays;
public final View emptyView;
public final TextView daysOfWeek;
public final TextView scheduleAlarm;
public final TextView selectedDate;
public final ImageView addDate;
public final ImageView removeDate;
public final CheckBox dismissAlarmWhenRingtoneEnds;
public final CheckBox alarmSnoozeActions;
public final CheckBox vibrate;
Expand All @@ -66,6 +77,12 @@ private ExpandedAlarmViewHolder(View itemView, boolean hasVibrator) {
mHasVibrator = hasVibrator;

repeatDays = itemView.findViewById(R.id.repeat_days_alarm);
emptyView = itemView.findViewById(R.id.alarm_expanded_empty_view);
daysOfWeek = itemView.findViewById(R.id.days_of_week);
scheduleAlarm = itemView.findViewById(R.id.schedule_alarm);
selectedDate = itemView.findViewById(R.id.selected_date);
addDate = itemView.findViewById(R.id.add_date);
removeDate = itemView.findViewById(R.id.remove_date);
ringtone = itemView.findViewById(R.id.choose_ringtone);
delete = itemView.findViewById(R.id.delete);
duplicate = itemView.findViewById(R.id.duplicate);
Expand Down Expand Up @@ -100,6 +117,14 @@ private ExpandedAlarmViewHolder(View itemView, boolean hasVibrator) {
getItemHolder().collapse();
});

scheduleAlarm.setOnClickListener(v -> getAlarmTimeClickHandler().onDateClicked(getItemHolder().item));

selectedDate.setOnClickListener(v -> getAlarmTimeClickHandler().onDateClicked(getItemHolder().item));

addDate.setOnClickListener(v -> getAlarmTimeClickHandler().onDateClicked(getItemHolder().item));

removeDate.setOnClickListener(v -> getAlarmTimeClickHandler().removeDate(getItemHolder().item));

// Dismiss alarm when ringtone ends checkbox handler
dismissAlarmWhenRingtoneEnds.setOnClickListener(v ->
getAlarmTimeClickHandler().setDismissAlarmWhenRingtoneEndsEnabled(
Expand Down Expand Up @@ -153,6 +178,8 @@ protected void onBindItemView(final AlarmItemHolder itemHolder) {
final Alarm alarm = itemHolder.item;
final Context context = itemView.getContext();
bindDaysOfWeekButtons(alarm, context);
bindScheduleAlarm(alarm);
bindSelectedDate(alarm);
bindRingtone(context, alarm);
bindDismissAlarmWhenRingtoneEnds(alarm);
bindAlarmSnoozeActions(alarm);
Expand All @@ -168,9 +195,49 @@ private void bindDaysOfWeekButtons(Alarm alarm, Context context) {
if (alarm.daysOfWeek.isBitOn(weekdays.get(i))) {
dayButton.setChecked(true);
dayButton.setTextColor(context.getColor(R.color.md_theme_inverseOnSurface));
selectedDate.setVisibility(GONE);
} else {
dayButton.setChecked(false);
dayButton.setTextColor(context.getColor(R.color.md_theme_inverseSurface));
selectedDate.setVisibility(VISIBLE);
}
}
}

private void bindScheduleAlarm(Alarm alarm) {
if (alarm.daysOfWeek.isRepeating()) {
scheduleAlarm.setVisibility(GONE);
} else {
scheduleAlarm.setVisibility(VISIBLE);
}
}

private void bindSelectedDate(Alarm alarm) {
int year = alarm.year;
int month = alarm.month;
int dayOfMonth = alarm.day;
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, dayOfMonth);
String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyy/MM/d");
SimpleDateFormat dateFormat = new SimpleDateFormat(pattern, Locale.getDefault());
String formattedDate = dateFormat.format(calendar.getTime());
if (alarm.daysOfWeek.isRepeating()) {
selectedDate.setVisibility(GONE);
addDate.setVisibility(GONE);
removeDate.setVisibility(GONE);
} else {
if (alarm.isDateSpecified()) {
emptyView.setVisibility(VISIBLE);
repeatDays.setVisibility(GONE);
selectedDate.setText(formattedDate);
addDate.setVisibility(GONE);
removeDate.setVisibility(VISIBLE);
} else {
emptyView.setVisibility(GONE);
repeatDays.setVisibility(VISIBLE);
selectedDate.setVisibility(GONE);
addDate.setVisibility(VISIBLE);
removeDate.setVisibility(GONE);
}
}
}
Expand Down
Loading

0 comments on commit 00d6ef2

Please sign in to comment.