Refactored alarm management classes
This commit is contained in:
parent
b798a8e2b0
commit
49b7d80185
@ -172,6 +172,10 @@ public abstract class Alarm implements JsonSerializable {
|
||||
return ringsIn() <= hours * 3600000;
|
||||
}
|
||||
|
||||
public int intId() {
|
||||
return (int) id();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public JSONObject toJsonObject() {
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
package com.philliphsu.clock2;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.media.RingtoneManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.design.widget.Snackbar;
|
||||
@ -21,7 +18,6 @@ import android.widget.TextView;
|
||||
|
||||
import com.philliphsu.clock2.alarms.AlarmsFragment;
|
||||
import com.philliphsu.clock2.editalarm.EditAlarmActivity;
|
||||
import com.philliphsu.clock2.ringtone.RingtoneActivity;
|
||||
|
||||
public class MainActivity extends BaseActivity implements AlarmsFragment.OnAlarmInteractionListener {
|
||||
private static final String TAG = "MainActivity";
|
||||
@ -61,22 +57,6 @@ public class MainActivity extends BaseActivity implements AlarmsFragment.OnAlarm
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startEditAlarmActivity(-1);
|
||||
/*
|
||||
scheduleAlarm();
|
||||
Snackbar.make(view, "Alarm set for 1 minute from now", Snackbar.LENGTH_INDEFINITE)
|
||||
.setAction("Dismiss", new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
|
||||
PendingIntent pi = alarmIntent(true);
|
||||
am.cancel(pi);
|
||||
pi.cancel();
|
||||
Intent intent = new Intent(MainActivity.this, UpcomingAlarmReceiver.class)
|
||||
.setAction(UpcomingAlarmReceiver.ACTION_CANCEL_NOTIFICATION);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
}).show();
|
||||
*/
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -208,46 +188,4 @@ public class MainActivity extends BaseActivity implements AlarmsFragment.OnAlarm
|
||||
intent.putExtra(EditAlarmActivity.EXTRA_ALARM_ID, alarmId);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void scheduleAlarm() {
|
||||
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
|
||||
// If there is already an alarm for this Intent scheduled (with the equality of two
|
||||
// intents being defined by filterEquals(Intent)), then it will be removed and replaced
|
||||
// by this one. For most of our uses, the relevant criteria for equality will be the
|
||||
// action, the data, and the class (component). Although not documented, the request code
|
||||
// of a PendingIntent is also considered to determine equality of two intents.
|
||||
|
||||
// WAKEUP alarm types wake the CPU up, but NOT the screen. If that is what you want, you need
|
||||
// to handle that yourself by using a wakelock, etc..
|
||||
// We use a WAKEUP alarm to send the upcoming alarm notification so it goes off even if the
|
||||
// device is asleep. Otherwise, it will not go off until the device is turned back on.
|
||||
// todo: use alarm's ring time - (number of hours to be notified in advance, converted to millis)
|
||||
am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), notifyUpcomingAlarmIntent());
|
||||
// todo: get alarm's ring time
|
||||
am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 10000, alarmIntent(false));
|
||||
}
|
||||
|
||||
private static int alarmCount;
|
||||
|
||||
private PendingIntent alarmIntent(boolean retrievePrevious) {
|
||||
// TODO: Use appropriate subclass instead
|
||||
Intent intent = new Intent(this, RingtoneActivity.class)
|
||||
.setData(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM));
|
||||
// TODO: Pass in the id of the alarm to the intent. Alternatively, if the upcoming alarm note
|
||||
// only needs to show the alarm's ring time, just pass in the alarm's ringsAt().
|
||||
// TODO: Use unique request codes per alarm.
|
||||
// If a PendingIntent with this request code already exists, then we are likely modifying
|
||||
// an alarm, so we should cancel the existing intent.
|
||||
int requestCode = retrievePrevious ? alarmCount - 1 : alarmCount++;
|
||||
int flag = retrievePrevious
|
||||
? PendingIntent.FLAG_NO_CREATE
|
||||
: PendingIntent.FLAG_CANCEL_CURRENT;
|
||||
return PendingIntent.getActivity(this, requestCode, intent, flag);
|
||||
}
|
||||
|
||||
private PendingIntent notifyUpcomingAlarmIntent() {
|
||||
Intent intent = new Intent(this, UpcomingAlarmReceiver.class);
|
||||
// TODO: Use unique request codes per alarm.
|
||||
return PendingIntent.getBroadcast(this, alarmCount, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,38 +6,72 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.philliphsu.clock2.model.AlarmsRepository;
|
||||
|
||||
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
|
||||
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
|
||||
|
||||
public class UpcomingAlarmReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = "UpcomingAlarmReceiver";
|
||||
|
||||
public static final String ACTION_CANCEL_NOTIFICATION
|
||||
= "com.philliphsu.clock2.action.CANCEL_NOTIFICATION";
|
||||
public static final String ACTION_SHOW_SNOOZING
|
||||
= "com.philliphsu.clock2.action.CANCEL_NOTIFICATION";
|
||||
|
||||
private static int count = -1;
|
||||
= "com.philliphsu.clock2.action.SHOW_SNOOZING";
|
||||
public static final String EXTRA_ALARM_ID
|
||||
= "com.philliphsu.clock2.extra.ALARM_ID";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
long id = intent.getLongExtra(EXTRA_ALARM_ID, -1);
|
||||
if (id < 0) {
|
||||
Log.e(TAG, "No alarm id received");
|
||||
}
|
||||
Alarm alarm = checkNotNull(AlarmsRepository.getInstance(context).getItem(id));
|
||||
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (ACTION_CANCEL_NOTIFICATION.equals(intent.getAction())) {
|
||||
nm.cancel(count);
|
||||
} else if (ACTION_SHOW_SNOOZING.equals(intent.getAction())) {
|
||||
if (intent.getAction() != null) {
|
||||
// TODO: Verify that no java/project configuration is needed for strings to work in switch
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_CANCEL_NOTIFICATION:
|
||||
nm.cancel(getClass().getName(), alarm.intId());
|
||||
break;
|
||||
case ACTION_SHOW_SNOOZING:
|
||||
if (!alarm.isSnoozed()) {
|
||||
throw new IllegalStateException("Can't show snoozing notif. if alarm not snoozed!");
|
||||
}
|
||||
String title = alarm.label().isEmpty()
|
||||
? context.getString(R.string.alarm)
|
||||
: alarm.label();
|
||||
String text = context.getString(R.string.title_snoozing_until,
|
||||
formatTime(context, alarm.snoozingUntil()));
|
||||
Notification note = new NotificationCompat.Builder(context)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setContentTitle("Snoozing")
|
||||
.setContentText("New ring time here")
|
||||
.setSmallIcon(R.mipmap.ic_launcher) // TODO: alarm icon
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setOngoing(true)
|
||||
.build();
|
||||
// todo actions
|
||||
nm.notify(count, note);
|
||||
nm.notify(getClass().getName(), alarm.intId(), note);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// No intent action required for default behavior
|
||||
String text = formatTime(context, alarm.ringsAt());
|
||||
if (!alarm.label().isEmpty()) {
|
||||
text = alarm.label() + ", " + text;
|
||||
}
|
||||
Notification note = new NotificationCompat.Builder(context)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setContentTitle("Upcoming alarm")
|
||||
.setContentText("Ring time here")
|
||||
.setContentTitle(context.getString(R.string.upcoming_alarm))
|
||||
.setContentText(text)
|
||||
.setOngoing(true)
|
||||
.build();
|
||||
// todo actions
|
||||
nm.notify(++count, note);
|
||||
nm.notify(getClass().getName(), alarm.intId(), note);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
package com.philliphsu.clock2.editalarm;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
import com.philliphsu.clock2.UpcomingAlarmReceiver;
|
||||
import com.philliphsu.clock2.ringtone.RingtoneActivity;
|
||||
|
||||
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
|
||||
import static android.app.PendingIntent.FLAG_NO_CREATE;
|
||||
import static android.app.PendingIntent.getActivity;
|
||||
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 6/3/2016.
|
||||
*
|
||||
* Utilities for scheduling and unscheduling alarms with the {@link AlarmManager}, as well as
|
||||
* managing the upcoming alarm notification.
|
||||
*
|
||||
* TODO: Adapt this to Timers too...
|
||||
*/
|
||||
public final class AlarmUtils {
|
||||
|
||||
private AlarmUtils() {}
|
||||
|
||||
public static void scheduleAlarm(Context context, Alarm alarm) {
|
||||
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
// If there is already an alarm for this Intent scheduled (with the equality of two
|
||||
// intents being defined by filterEquals(Intent)), then it will be removed and replaced
|
||||
// by this one. For most of our uses, the relevant criteria for equality will be the
|
||||
// action, the data, and the class (component). Although not documented, the request code
|
||||
// of a PendingIntent is also considered to determine equality of two intents.
|
||||
|
||||
// WAKEUP alarm types wake the CPU up, but NOT the screen. If that is what you want, you need
|
||||
// to handle that yourself by using a wakelock, etc..
|
||||
// We use a WAKEUP alarm to send the upcoming alarm notification so it goes off even if the
|
||||
// device is asleep. Otherwise, it will not go off until the device is turned back on.
|
||||
// todo: read shared prefs for number of hours to be notified in advance
|
||||
am.set(AlarmManager.RTC_WAKEUP, alarm.ringsAt() - 2*3600000, notifyUpcomingAlarmIntent(context, alarm, false));
|
||||
am.setExact(AlarmManager.RTC_WAKEUP, alarm.ringsAt(), alarmIntent(context, alarm, false));
|
||||
}
|
||||
|
||||
public static void unscheduleAlarm(Context c, Alarm a) {
|
||||
AlarmManager am = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
PendingIntent pi = alarmIntent(c, a, true);
|
||||
am.cancel(pi);
|
||||
pi.cancel();
|
||||
|
||||
pi = notifyUpcomingAlarmIntent(c, a, true);
|
||||
am.cancel(pi);
|
||||
pi.cancel();
|
||||
|
||||
removeUpcomingAlarmNotification(c, a);
|
||||
}
|
||||
|
||||
public static void removeUpcomingAlarmNotification(Context c, Alarm a) {
|
||||
Intent intent = new Intent(c, UpcomingAlarmReceiver.class)
|
||||
.setAction(UpcomingAlarmReceiver.ACTION_CANCEL_NOTIFICATION)
|
||||
.putExtra(UpcomingAlarmReceiver.EXTRA_ALARM_ID, a.id());
|
||||
c.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private static PendingIntent alarmIntent(Context context, Alarm alarm, boolean retrievePrevious) {
|
||||
// TODO: Use appropriate subclass instead
|
||||
Intent intent = new Intent(context, RingtoneActivity.class)
|
||||
.putExtra(RingtoneActivity.EXTRA_ITEM_ID, alarm.id());
|
||||
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
|
||||
PendingIntent pi = getActivity(context, alarm.intId(), intent, flag);
|
||||
if (retrievePrevious) {
|
||||
checkNotNull(pi);
|
||||
}
|
||||
return pi;
|
||||
}
|
||||
|
||||
private static PendingIntent notifyUpcomingAlarmIntent(Context context, Alarm alarm, boolean retrievePrevious) {
|
||||
Intent intent = new Intent(context, UpcomingAlarmReceiver.class)
|
||||
.putExtra(UpcomingAlarmReceiver.EXTRA_ALARM_ID, alarm.id());
|
||||
int flag = retrievePrevious ? FLAG_NO_CREATE : FLAG_CANCEL_CURRENT;
|
||||
PendingIntent pi = PendingIntent.getBroadcast(context, alarm.intId(), intent, flag);
|
||||
if (retrievePrevious) {
|
||||
checkNotNull(pi);
|
||||
}
|
||||
return pi;
|
||||
}
|
||||
}
|
||||
@ -31,6 +31,11 @@ public class EditAlarmPresenter implements EditAlarmContract.Presenter {
|
||||
|
||||
@Override
|
||||
public void loadAlarm(long alarmId) {
|
||||
// Can't load alarm in ctor because showDetails() calls
|
||||
// showTime(), which calls setTime() on the numpad, which
|
||||
// fires onNumberInput() events, which routes to the presenter,
|
||||
// which would not be initialized yet because we still haven't
|
||||
// returned from the ctor.
|
||||
mAlarm = alarmId > -1 ? mRepository.getItem(alarmId) : null;
|
||||
showDetails();
|
||||
}
|
||||
|
||||
@ -4,14 +4,16 @@ import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.UpcomingAlarmReceiver;
|
||||
import com.philliphsu.clock2.editalarm.AlarmUtils;
|
||||
import com.philliphsu.clock2.model.AlarmsRepository;
|
||||
|
||||
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
|
||||
|
||||
@ -24,19 +26,27 @@ import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
|
||||
*/
|
||||
public class RingtoneActivity extends AppCompatActivity {
|
||||
|
||||
// Shared with RingtoneService
|
||||
public static final String EXTRA_ITEM_ID = "com.philliphsu.clock2.ringtone.extra.ITEM_ID";
|
||||
|
||||
private Alarm mAlarm;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_ringtone);
|
||||
long id = getIntent().getLongExtra(EXTRA_ITEM_ID, -1);
|
||||
if (id < 0) {
|
||||
throw new IllegalStateException("Cannot start RingtoneActivity without item's id");
|
||||
}
|
||||
mAlarm = checkNotNull(AlarmsRepository.getInstance(this).getItem(id));
|
||||
|
||||
// Play the ringtone
|
||||
Uri ringtone = checkNotNull(getIntent().getData());
|
||||
Intent intent = new Intent(this, RingtoneService.class).setData(ringtone);
|
||||
Intent intent = new Intent(this, RingtoneService.class)
|
||||
.putExtra(EXTRA_ITEM_ID, mAlarm.id());
|
||||
startService(intent);
|
||||
// Cancel the upcoming alarm notification
|
||||
Intent intent2 = new Intent(this, UpcomingAlarmReceiver.class)
|
||||
.setAction(UpcomingAlarmReceiver.ACTION_CANCEL_NOTIFICATION);
|
||||
sendBroadcast(intent2);
|
||||
|
||||
AlarmUtils.removeUpcomingAlarmNotification(this, mAlarm);
|
||||
|
||||
Button snooze = (Button) findViewById(R.id.btn_snooze);
|
||||
snooze.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
@ -14,8 +14,11 @@ import android.os.IBinder;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.philliphsu.clock2.Alarm;
|
||||
import com.philliphsu.clock2.R;
|
||||
import com.philliphsu.clock2.model.AlarmsRepository;
|
||||
|
||||
import static com.philliphsu.clock2.util.DateFormatUtils.formatTime;
|
||||
import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
@ -26,12 +29,16 @@ import static com.philliphsu.clock2.util.Preconditions.checkNotNull;
|
||||
* navigate away from the Activity without making an action. But if they do accidentally navigate away,
|
||||
* they have plenty of time to make the desired action via the notification.
|
||||
*/
|
||||
public class RingtoneService extends Service {
|
||||
public class RingtoneService extends Service { // TODO: abstract this, make subclasses
|
||||
private static final String TAG = "RingtoneService";
|
||||
|
||||
private AudioManager mAudioManager;
|
||||
private Ringtone mRingtone;
|
||||
private Alarm mAlarm;
|
||||
private String mNormalRingTime;
|
||||
private boolean mAutoSilenced = false;
|
||||
// TODO: Using Handler for this is ill-suited? Alarm ringing could outlast the
|
||||
// application's life. Use AlarmManager API instead.
|
||||
private final Handler mSilenceHandler = new Handler();
|
||||
private final Runnable mSilenceRunnable = new Runnable() {
|
||||
@Override
|
||||
@ -50,16 +57,21 @@ public class RingtoneService extends Service {
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (mAudioManager == null && mRingtone == null) {
|
||||
Uri ringtone = checkNotNull(intent.getData());
|
||||
long id = intent.getLongExtra(RingtoneActivity.EXTRA_ITEM_ID, -1);
|
||||
mAlarm = checkNotNull(AlarmsRepository.getInstance(this).getItem(id));
|
||||
// TODO: The below call requires a notification, and there is no way to provide one suitable
|
||||
// for both Alarms and Timers. Consider making this class abstract, and have subclasses
|
||||
// implement an abstract method that calls startForeground(). You would then call that
|
||||
// method here instead.
|
||||
String title = mAlarm.label().isEmpty()
|
||||
? getString(R.string.alarm)
|
||||
: mAlarm.label();
|
||||
mNormalRingTime = formatTime(this, System.currentTimeMillis()); // now
|
||||
Notification note = new NotificationCompat.Builder(this)
|
||||
// Required contents
|
||||
.setSmallIcon(R.mipmap.ic_launcher) // TODO: alarm icon
|
||||
.setContentTitle("Foreground RingtoneService")
|
||||
.setContentText("Ringtone is playing in the foreground.")
|
||||
.setContentTitle(title)
|
||||
.setContentText(mNormalRingTime)
|
||||
.build();
|
||||
startForeground(R.id.ringtone_service_notification, note); // TOneverDO: Pass 0 as the first argument
|
||||
|
||||
@ -72,6 +84,7 @@ public class RingtoneService extends Service {
|
||||
// Request permanent focus, as ringing could last several minutes
|
||||
AudioManager.AUDIOFOCUS_GAIN);
|
||||
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
|
||||
Uri ringtone = Uri.parse(mAlarm.ringtone());
|
||||
mRingtone = RingtoneManager.getRingtone(this, ringtone);
|
||||
// Deprecated, but the alternative AudioAttributes requires API 21
|
||||
mRingtone.setStreamType(AudioManager.STREAM_ALARM);
|
||||
@ -94,11 +107,15 @@ public class RingtoneService extends Service {
|
||||
// TODO: You should probably do this in the appropriate subclass.
|
||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
Notification note = new NotificationCompat.Builder(this)
|
||||
.setContentTitle("Missed alarm")
|
||||
.setContentText("Regular alarm time here")
|
||||
.setContentTitle(getString(R.string.missed_alarm))
|
||||
.setContentText(mNormalRingTime)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
//.setShowWhen(true) // TODO: Is it shown by default?
|
||||
.build();
|
||||
nm.notify("tag", 0, note);
|
||||
// A tag with the name of the subclass is used in addition to the item's id to prevent
|
||||
// conflicting notifications for items of different class types. Items of any class type
|
||||
// have ids starting from 0.
|
||||
nm.notify(getClass().getName(), mAlarm.intId(), note);
|
||||
}
|
||||
stopForeground(true);
|
||||
}
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
package com.philliphsu.clock2.util;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static android.text.format.DateFormat.getTimeFormat;
|
||||
|
||||
/**
|
||||
* Created by Phillip Hsu on 6/3/2016.
|
||||
*/
|
||||
public final class DateFormatUtils {
|
||||
|
||||
private DateFormatUtils() {}
|
||||
|
||||
public static String formatTime(Context context, long millis) {
|
||||
return getTimeFormat(context).format(new Date(millis));
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
<string name="dummy_button">Dummy Button</string>
|
||||
<string name="dummy_content">DUMMY\nCONTENT</string>
|
||||
|
||||
<!-- ================================= EDIT ALARM ACTIVITY ==================================-->
|
||||
<string name="title_activity_edit_alarm">EditAlarmActivity</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="delete">Delete</string>
|
||||
@ -15,9 +16,18 @@
|
||||
<string name="title_snoozing_until">Snoozing until %1$s</string>
|
||||
<string name="dismiss_now">Dismiss now</string>
|
||||
<string name="done_snoozing">Done snoozing</string>
|
||||
<!-- ======================================================================================= -->
|
||||
|
||||
<!-- ==================================== NOTIFICATIONS ==================================== -->
|
||||
<string name="upcoming_alarm">Upcoming alarm</string>
|
||||
<string name="alarm">Alarm</string>
|
||||
<string name="missed_alarm">Missed alarm</string>
|
||||
<!-- ======================================================================================= -->
|
||||
|
||||
<!-- ==================================== MAIN ACTIVITY ==================================== -->
|
||||
<string name="snackbar_item_deleted">%1$s deleted</string>
|
||||
<string name="snackbar_undo_item_deleted">Undo</string>
|
||||
<!-- ======================================================================================= -->
|
||||
|
||||
<string name="sun">Sun</string>
|
||||
<string name="mon">Mon</string>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user