diff --git a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
index 7f9432366..b6f5fdef6 100644
--- a/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
+++ b/GBDaoGenerator/src/nodomain/freeyourgadget/gadgetbridge/daogen/GBDaoGenerator.java
@@ -43,7 +43,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception {
- Schema schema = new Schema(36, MAIN_PACKAGE + ".entities");
+ final Schema schema = new Schema(37, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
@@ -86,6 +86,7 @@ public class GBDaoGenerator {
addHybridHRActivitySample(schema, user, device);
addCalendarSyncState(schema, device);
addAlarms(schema, user, device);
+ addReminders(schema, user, device);
Entity notificationFilter = addNotificationFilters(schema);
@@ -541,6 +542,25 @@ public class GBDaoGenerator {
alarm.addToOne(device, deviceId);
}
+ private static void addReminders(Schema schema, Entity user, Entity device) {
+ Entity reminder = addEntity(schema, "Reminder");
+ reminder.implementsInterface("nodomain.freeyourgadget.gadgetbridge.model.Reminder");
+ Property deviceId = reminder.addLongProperty("deviceId").notNull().getProperty();
+ Property userId = reminder.addLongProperty("userId").notNull().getProperty();
+ Property reminderId = reminder.addStringProperty("reminderId").notNull().primaryKey().getProperty();
+ Index indexUnique = new Index();
+ indexUnique.addProperty(deviceId);
+ indexUnique.addProperty(userId);
+ indexUnique.addProperty(reminderId);
+ indexUnique.makeUnique();
+ reminder.addIndex(indexUnique);
+ reminder.addStringProperty("message").notNull();
+ reminder.addDateProperty("date").notNull();
+ reminder.addIntProperty("repetition").notNull();
+ reminder.addToOne(user, userId);
+ reminder.addToOne(device, deviceId);
+ }
+
private static void addNotificationFilterEntry(Schema schema, Entity notificationFilterEntity) {
Entity notificatonFilterEntry = addEntity(schema, "NotificationFilterEntry");
notificatonFilterEntry.addIdProperty().autoincrement();
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cab98550f..4e7b3156d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -485,6 +485,10 @@
android:name=".activities.ConfigureAlarms"
android:label="@string/title_activity_set_alarm"
android:parentActivityName=".activities.ControlCenterv2" />
+
+
. */
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Calendar;
+import java.util.List;
+import java.util.Locale;
+import java.util.UUID;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
+import nodomain.freeyourgadget.gadgetbridge.adapter.GBReminderListAdapter;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
+import nodomain.freeyourgadget.gadgetbridge.entities.Device;
+import nodomain.freeyourgadget.gadgetbridge.entities.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.entities.User;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
+
+
+public class ConfigureReminders extends AbstractGBActivity {
+ private static final Logger LOG = LoggerFactory.getLogger(ConfigureReminders.class);
+
+ private static final int REQ_CONFIGURE_REMINDER = 1;
+
+ private GBReminderListAdapter mGBReminderListAdapter;
+ private GBDevice gbDevice;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_configure_reminders);
+
+ gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+
+ mGBReminderListAdapter = new GBReminderListAdapter(this);
+
+ final RecyclerView remindersRecyclerView = findViewById(R.id.reminder_list);
+ remindersRecyclerView.setHasFixedSize(true);
+ remindersRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+ remindersRecyclerView.setAdapter(mGBReminderListAdapter);
+ updateRemindersFromDB();
+
+ final FloatingActionButton fab = findViewById(R.id.fab);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
+
+ final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+ int reservedSlots = prefs.getInt(DeviceSettingsPreferenceConst.PREF_RESERVER_REMINDERS_CALENDAR, 9);
+
+ int deviceSlots = coordinator.getReminderSlotCount() - reservedSlots;
+
+ if (mGBReminderListAdapter.getItemCount() >= deviceSlots) {
+ // No more free slots
+ new AlertDialog.Builder(v.getContext())
+ .setTitle(R.string.reminder_no_free_slots_title)
+ .setMessage(getBaseContext().getString(R.string.reminder_no_free_slots_description, String.format(Locale.getDefault(), "%d", deviceSlots)))
+ .setIcon(R.drawable.ic_warning)
+ .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(final DialogInterface dialog, final int whichButton) {
+ }
+ })
+ .show();
+ return;
+ }
+
+ final Reminder reminder;
+ try (DBHandler db = GBApplication.acquireDB()) {
+ final DaoSession daoSession = db.getDaoSession();
+ final Device device = DBHelper.getDevice(gbDevice, daoSession);
+ final User user = DBHelper.getUser(daoSession);
+ reminder = createDefaultReminder(device, user);
+ } catch (final Exception e) {
+ LOG.error("Error accessing database", e);
+ return;
+ }
+
+ configureReminder(reminder);
+ }
+ });
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == REQ_CONFIGURE_REMINDER && resultCode == 1) {
+ updateRemindersFromDB();
+ sendRemindersToDevice();
+ }
+ }
+
+ private Reminder createDefaultReminder(@NonNull Device device, @NonNull User user) {
+ final Reminder reminder = new Reminder();
+ reminder.setRepetition(Reminder.ONCE);
+ reminder.setDate(Calendar.getInstance().getTime());
+ reminder.setMessage("");
+ reminder.setDeviceId(device.getId());
+ reminder.setUserId(user.getId());
+ reminder.setReminderId(UUID.randomUUID().toString());
+
+ return reminder;
+ }
+
+ /**
+ * Reads the available reminders from the database and updates the view afterwards.
+ */
+ private void updateRemindersFromDB() {
+ final List reminders = DBHelper.getReminders(gbDevice);
+
+ mGBReminderListAdapter.setReminderList(reminders);
+ mGBReminderListAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ // back button
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ public void configureReminder(final Reminder reminder) {
+ final Intent startIntent = new Intent(getApplicationContext(), ReminderDetails.class);
+ startIntent.putExtra(GBDevice.EXTRA_DEVICE, gbDevice);
+ startIntent.putExtra(Reminder.EXTRA_REMINDER, reminder);
+ startActivityForResult(startIntent, REQ_CONFIGURE_REMINDER);
+ }
+
+ public void deleteReminder(final Reminder reminder) {
+ DBHelper.delete(reminder);
+ updateRemindersFromDB();
+ sendRemindersToDevice();
+ }
+
+ private void sendRemindersToDevice() {
+ if (gbDevice.isInitialized()) {
+ GBApplication.deviceService().onSetReminders(mGBReminderListAdapter.getReminderList());
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ReminderDetails.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ReminderDetails.java
new file mode 100644
index 000000000..c0a675f97
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ReminderDetails.java
@@ -0,0 +1,222 @@
+/* Copyright (C) 2019 José Rebelo
+
+ 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;
+
+import android.app.AlertDialog;
+import android.app.DatePickerDialog;
+import android.app.TimePickerDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.text.format.DateFormat;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TimePicker;
+import android.widget.Toast;
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
+import nodomain.freeyourgadget.gadgetbridge.entities.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class ReminderDetails extends AbstractGBActivity implements TimePickerDialog.OnTimeSetListener, DatePickerDialog.OnDateSetListener {
+ private static final Logger LOG = LoggerFactory.getLogger(ReminderDetails.class);
+
+ private Reminder reminder;
+ private GBDevice device;
+
+ ArrayAdapter repeatAdapter;
+
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
+ SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
+
+ TextView reminderRepeat;
+ TextView reminderDate;
+ TextView reminderTime;
+ EditText reminderText;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_reminder_details);
+
+ reminder = (Reminder) getIntent().getSerializableExtra(Reminder.EXTRA_REMINDER);
+
+ if (reminder == null) {
+ GB.toast("No reminder provided to ReminderDetails Activity", Toast.LENGTH_LONG, GB.ERROR);
+ finish();
+ return;
+ }
+
+ reminderRepeat = findViewById(R.id.reminder_repeat);
+ reminderDate = findViewById(R.id.reminder_date);
+ reminderTime = findViewById(R.id.reminder_time);
+ reminderText = findViewById(R.id.reminder_message);
+
+ device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
+ final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
+
+ final String[] repeatStrings = getResources().getStringArray(R.array.reminder_repeat);
+ repeatAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, repeatStrings);
+
+ final View cardRepeat = findViewById(R.id.card_repeat);
+ cardRepeat.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ new AlertDialog.Builder(ReminderDetails.this).setAdapter(repeatAdapter, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ reminder.setRepetition(i);
+ updateUiFromReminder();
+ }
+ }).create().show();
+ }
+ });
+
+ final View cardDate = findViewById(R.id.card_date);
+ cardDate.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ final Calendar date = new GregorianCalendar();
+ date.setTime(reminder.getDate());
+ new DatePickerDialog(
+ ReminderDetails.this,
+ ReminderDetails.this,
+ date.get(Calendar.YEAR),
+ date.get(Calendar.MONTH),
+ date.get(Calendar.DAY_OF_MONTH)
+ ).show();
+ }
+ });
+
+ final View cardTime = findViewById(R.id.card_time);
+ cardTime.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ new TimePickerDialog(
+ ReminderDetails.this,
+ ReminderDetails.this,
+ reminder.getDate().getHours(),
+ reminder.getDate().getMinutes(),
+ DateFormat.is24HourFormat(GBApplication.getContext())
+ ).show();
+ }
+ });
+
+ reminderText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(coordinator.getMaximumReminderMessageLength())});
+ reminderText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(final CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(final CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ reminder.setMessage(s.toString());
+ }
+ });
+
+ final FloatingActionButton fab = findViewById(R.id.fab_save);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ updateReminder();
+ ReminderDetails.this.setResult(1);
+ finish();
+ }
+ });
+
+ updateUiFromReminder();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ // back button
+ // TODO confirm when exiting without saving
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void updateReminder() {
+ DBHelper.store(reminder);
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle state) {
+ super.onSaveInstanceState(state);
+ state.putSerializable("reminder", reminder);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ reminder = (Reminder) savedInstanceState.getSerializable("reminder");
+ updateUiFromReminder();
+ }
+
+ @Override
+ public void onTimeSet(TimePicker timePicker, int hour, int minute) {
+ reminder.getDate().setHours(hour);
+ reminder.getDate().setMinutes(minute);
+ updateUiFromReminder();
+ }
+
+ @Override
+ public void onDateSet(DatePicker datePicker, int year, int month, int dayOfMonth) {
+ final Calendar date = new GregorianCalendar(year, month, dayOfMonth);
+ reminder.setDate(new Date(date.getTimeInMillis()));
+ updateUiFromReminder();
+ }
+
+ public void updateUiFromReminder() {
+ reminderRepeat.setText(repeatAdapter.getItem(reminder.getRepetition()));
+ reminderText.setText(reminder.getMessage());
+
+ if (reminder.getDate() != null) {
+ reminderDate.setText(dateFormat.format(reminder.getDate()));
+ reminderTime.setText(timeFormat.format(reminder.getDate()));
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
index 4b4d0235e..72eb79718 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSettingsPreferenceConst.java
@@ -25,6 +25,7 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_NOTIFICATION_ENABLE = "notification_enable";
public static final String PREF_SCREEN_ORIENTATION = "screen_orientation";
public static final String PREF_RESERVER_ALARMS_CALENDAR = "reserve_alarms_calendar";
+ public static final String PREF_RESERVER_REMINDERS_CALENDAR = "reserve_reminders_calendar";
public static final String PREF_ALLOW_HIGH_MTU = "allow_high_mtu";
public static final String PREF_SYNC_CALENDAR = "sync_calendar";
public static final String PREF_USE_CUSTOM_DEVICEICON = "use_custom_deviceicon";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
index c36f4ca78..a58047b94 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/devicesettings/DeviceSpecificSettingsFragment.java
@@ -738,6 +738,7 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
setInputTypeFor(MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS, InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_SIGNED);
setInputTypeFor(MakibesHR3Constants.PREF_FIND_PHONE_DURATION, InputType.TYPE_CLASS_NUMBER);
setInputTypeFor(DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR, InputType.TYPE_CLASS_NUMBER);
+ setInputTypeFor(DeviceSettingsPreferenceConst.PREF_RESERVER_REMINDERS_CALENDAR, InputType.TYPE_CLASS_NUMBER);
String deviceActionsFellSleepSelection = prefs.getString(PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION, PREF_DEVICE_ACTION_SELECTION_OFF);
final Preference deviceActionsFellSleep = findPreference(PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
index 55736a72d..84af915e6 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapterv2.java
@@ -75,6 +75,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.BatteryInfoActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateDialog;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
@@ -320,6 +321,21 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter 0 ? View.VISIBLE : View.GONE);
+ holder.setRemindersView.setOnClickListener(new View.OnClickListener()
+
+ {
+ @Override
+ public void onClick(View v) {
+ Intent startIntent;
+ startIntent = new Intent(context, ConfigureReminders.class);
+ startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
+ context.startActivity(startIntent);
+ }
+ }
+ );
+
//show graphs
holder.showActivityGraphs.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
holder.showActivityGraphs.setOnClickListener(new View.OnClickListener()
@@ -743,6 +759,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter. */
+package nodomain.freeyourgadget.gadgetbridge.adapter;
+
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.cardview.widget.CardView;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders;
+import nodomain.freeyourgadget.gadgetbridge.entities.Reminder;
+
+/**
+ * Adapter for displaying Reminder instances.
+ */
+public class GBReminderListAdapter extends RecyclerView.Adapter {
+
+ private final Context mContext;
+ private ArrayList reminderList;
+
+ public GBReminderListAdapter(Context context) {
+ this.mContext = context;
+ }
+
+ public void setReminderList(List reminders) {
+ this.reminderList = new ArrayList<>(reminders);
+ }
+
+ public ArrayList getReminderList() {
+ return reminderList;
+ }
+
+ @NonNull
+ @Override
+ public GBReminderListAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_reminder, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
+ final Reminder reminder = reminderList.get(position);
+
+ holder.container.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ((ConfigureReminders) mContext).configureReminder(reminder);
+ }
+ });
+
+ holder.container.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ new AlertDialog.Builder(v.getContext())
+ .setTitle(R.string.reminder_delete_confirm_title)
+ .setMessage(R.string.reminder_delete_confirm_description)
+ .setIcon(R.drawable.ic_warning)
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(final DialogInterface dialog, final int whichButton) {
+ ((ConfigureReminders) mContext).deleteReminder(reminder);
+ }
+ })
+ .setNegativeButton(android.R.string.no, null)
+ .show();
+
+ return true;
+ }
+ });
+
+ holder.reminderMessage.setText(reminder.getMessage());
+
+ final Date time = reminder.getDate();
+ final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());
+
+ int stringResId = 0;
+
+ switch (reminder.getRepetition()) {
+ case Reminder.ONCE:
+ stringResId = R.string.reminder_time_once;
+ break;
+ case Reminder.EVERY_DAY:
+ stringResId = R.string.reminder_time_every_day;
+ break;
+ case Reminder.EVERY_WEEK:
+ stringResId = R.string.reminder_time_every_week;
+ break;
+ case Reminder.EVERY_MONTH:
+ stringResId = R.string.reminder_time_every_month;
+ break;
+ case Reminder.EVERY_YEAR:
+ stringResId = R.string.reminder_time_every_year;
+ break;
+ }
+
+ final String reminderTimeText = mContext.getString(stringResId, format.format(time));
+ holder.reminderTime.setText(reminderTimeText);
+ }
+
+ @Override
+ public int getItemCount() {
+ return reminderList.size();
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ final CardView container;
+
+ final TextView reminderTime;
+ final TextView reminderMessage;
+
+ ViewHolder(View view) {
+ super(view);
+
+ container = view.findViewById(R.id.card_view);
+
+ reminderTime = view.findViewById(R.id.reminder_item_time);
+ reminderMessage = view.findViewById(R.id.reminder_item_message);
+ }
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
index 056fffc95..7e97e9862 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java
@@ -55,6 +55,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
+import nodomain.freeyourgadget.gadgetbridge.entities.Reminder;
+import nodomain.freeyourgadget.gadgetbridge.entities.ReminderDao;
import nodomain.freeyourgadget.gadgetbridge.entities.Tag;
import nodomain.freeyourgadget.gadgetbridge.entities.TagDao;
import nodomain.freeyourgadget.gadgetbridge.entities.User;
@@ -620,6 +622,60 @@ public class DBHelper {
}
}
+ /**
+ * Returns all user-configurable reminders for the given user and device. The list is sorted by
+ * {@link Reminder#getDate}. Calendar events that may also be modeled as reminders are not stored
+ * in the database and hence not returned by this method.
+ * @param gbDevice the device for which the alarms shall be loaded
+ * @return the list of reminders for the given device
+ */
+ @NonNull
+ public static List getReminders(@NonNull GBDevice gbDevice) {
+ final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
+ final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+
+ int reservedSlots = prefs.getInt(DeviceSettingsPreferenceConst.PREF_RESERVER_REMINDERS_CALENDAR, 9);
+
+ final int reminderSlots = coordinator.getReminderSlotCount();
+
+ try (DBHandler db = GBApplication.acquireDB()) {
+ final DaoSession daoSession = db.getDaoSession();
+ final User user = getUser(daoSession);
+ final Device dbDevice = DBHelper.findDevice(gbDevice, daoSession);
+ if (dbDevice != null) {
+ final ReminderDao reminderDao = daoSession.getReminderDao();
+ final Long deviceId = dbDevice.getId();
+ final QueryBuilder qb = reminderDao.queryBuilder();
+ qb.where(
+ ReminderDao.Properties.UserId.eq(user.getId()),
+ ReminderDao.Properties.DeviceId.eq(deviceId)).orderAsc(ReminderDao.Properties.Date).limit(reminderSlots - reservedSlots);
+ return qb.build().list();
+ }
+ } catch (final Exception e) {
+ LOG.error("Error reading reminders from db", e);
+ }
+
+ return Collections.emptyList();
+ }
+
+ public static void store(final Reminder reminder) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ final DaoSession daoSession = db.getDaoSession();
+ daoSession.insertOrReplace(reminder);
+ } catch (final Exception e) {
+ LOG.error("Error acquiring database", e);
+ }
+ }
+
+ public static void delete(final Reminder reminder) {
+ try (DBHandler db = GBApplication.acquireDB()) {
+ final DaoSession daoSession = db.getDaoSession();
+ daoSession.delete(reminder);
+ } catch (final Exception e) {
+ LOG.error("Error acquiring database", e);
+ }
+ }
+
public static void clearSession() {
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
index 3cf0b1e27..e4e8bd43f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java
@@ -222,6 +222,16 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return false;
}
+ @Override
+ public int getMaximumReminderMessageLength() {
+ return 0;
+ }
+
+ @Override
+ public int getReminderSlotCount() {
+ return 0;
+ }
+
@Override
public boolean supportsRgbLedColor() {
return false;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
index 8dda292c0..d934deadc 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java
@@ -321,6 +321,16 @@ public interface DeviceCoordinator {
*/
boolean supportsMusicInfo();
+ /**
+ * Indicates the maximum reminder message length.
+ */
+ int getMaximumReminderMessageLength();
+
+ /**
+ * Indicates the maximum number of reminder slots available in the device.
+ */
+ int getReminderSlotCount();
+
/**
* Indicates whether the device has an led which supports custom colors
*/
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
index e20493c11..9668d6acb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
@@ -30,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
/**
@@ -46,6 +47,8 @@ public interface EventHandler {
void onSetAlarms(ArrayList extends Alarm> alarms);
+ void onSetReminders(ArrayList extends Reminder> reminders);
+
void onSetCallState(CallSpec callSpec);
void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Coordinator.java
index 050938da5..4b9ac8447 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Coordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/huami/miband3/MiBand3Coordinator.java
@@ -84,6 +84,15 @@ public class MiBand3Coordinator extends HuamiCoordinator {
return true;
}
+ @Override
+ public int getMaximumReminderMessageLength() {
+ return 16;
+ }
+
+ @Override
+ public int getReminderSlotCount() {
+ return 22; // At least, Mi Fit still allows more
+ }
public static String getNightMode(String deviceAddress) {
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(deviceAddress));
@@ -111,6 +120,7 @@ public class MiBand3Coordinator extends HuamiCoordinator {
R.xml.devicesettings_liftwrist_display,
R.xml.devicesettings_swipeunlock,
R.xml.devicesettings_sync_calendar,
+ R.xml.devicesettings_reserve_reminders_calendar,
R.xml.devicesettings_expose_hr_thirdparty,
R.xml.devicesettings_bt_connected_advertisement,
R.xml.devicesettings_device_actions,
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
index 9773147c9..c16f94df1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
@@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.RtlUtils;
@@ -225,6 +226,13 @@ public class GBDeviceService implements DeviceService {
invokeService(intent);
}
+ @Override
+ public void onSetReminders(ArrayList extends Reminder> reminders) {
+ Intent intent = createIntent().setAction(ACTION_SET_REMINDERS)
+ .putExtra(EXTRA_REMINDERS, reminders);
+ invokeService(intent);
+ }
+
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
Intent intent = createIntent().setAction(ACTION_SETMUSICINFO)
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
index 95da94ec9..62f71e9d8 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
@@ -55,6 +55,7 @@ public interface DeviceService extends EventHandler {
String ACTION_SET_CONSTANT_VIBRATION = PREFIX + ".action.set_constant_vibration";
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
String ACTION_SAVE_ALARMS = PREFIX + ".action.save_alarms";
+ String ACTION_SET_REMINDERS = PREFIX + ".action.set_reminders";
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
@@ -107,6 +108,7 @@ public interface DeviceService extends EventHandler {
String EXTRA_URI = "uri";
String EXTRA_CONFIG = "config";
String EXTRA_ALARMS = "alarms";
+ String EXTRA_REMINDERS = "reminders";
String EXTRA_CONNECT_FIRST_TIME = "connect_first_time";
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
String EXTRA_INTERVAL_SECONDS = "interval_seconds";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Reminder.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Reminder.java
new file mode 100644
index 000000000..278f26cde
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/Reminder.java
@@ -0,0 +1,38 @@
+/* Copyright (C) 2019 José Rebelo
+
+ 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.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+public interface Reminder extends Serializable {
+ /**
+ * The {@link android.os.Bundle} name for transferring parceled reminders.
+ */
+ String EXTRA_REMINDER = "reminder";
+
+ int ONCE = 0;
+ int EVERY_DAY = 1;
+ int EVERY_WEEK = 2;
+ int EVERY_MONTH = 3;
+ int EVERY_YEAR = 4;
+
+ String getReminderId();
+ String getMessage();
+ Date getDate();
+ int getRepetition();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
index c7e0f1013..148ebb465 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
@@ -73,6 +73,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.AutoConnectIntervalReceiver;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBAutoFetchReceiver;
@@ -117,6 +118,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SE
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_FM_FREQUENCY;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_LED_COLOR;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_REMINDERS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION;
@@ -170,6 +172,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOT
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TITLE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TYPE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_RECORDED_DATA_TYPES;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_REMINDERS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_RESET_FLAGS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_VIBRATION_INTENSITY;
@@ -578,6 +581,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
ArrayList extends Alarm> alarms = (ArrayList extends Alarm>) intent.getSerializableExtra(EXTRA_ALARMS);
mDeviceSupport.onSetAlarms(alarms);
break;
+ case ACTION_SET_REMINDERS:
+ ArrayList extends Reminder> reminders = (ArrayList extends Reminder>) intent.getSerializableExtra(EXTRA_REMINDERS);
+ mDeviceSupport.onSetReminders(reminders);
+ break;
case ACTION_ENABLE_REALTIME_STEPS: {
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableRealtimeSteps(enable);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
index b1b5769f6..bd60f8342 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
@@ -37,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
/**
@@ -309,6 +310,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
delegate.onSetAlarms(alarms);
}
+ @Override
+ public void onSetReminders(ArrayList extends Reminder> reminders) {
+ if (checkBusy("set reminders")) {
+ return;
+ }
+ delegate.onSetReminders(reminders);
+ }
+
@Override
public void onEnableRealtimeSteps(boolean enable) {
if (checkBusy("enable realtime steps: " + enable)) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
index fdf0d14ad..a51d887d9 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
@@ -36,6 +36,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
@@ -366,6 +367,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
}
+ @Override
+ public void onSetReminders(ArrayList extends Reminder> reminders) {
+
+ }
+
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java
index 6147e9b2a..8246b6493 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/huami/HuamiSupport.java
@@ -107,6 +107,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
@@ -150,6 +151,7 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_DATEFORMAT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR;
+import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_RESERVER_REMINDERS_CALENDAR;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SOUNDS;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT;
@@ -778,6 +780,117 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
}
}
+ @Override
+ public void onSetReminders(ArrayList extends Reminder> reminders) {
+ final TransactionBuilder builder;
+ try {
+ builder = performInitialized("onSetReminders");
+ } catch (final IOException e) {
+ LOG.error("Unable to send reminders to device", e);
+ return;
+ }
+
+ sendReminders(builder, reminders);
+
+ builder.queue(getQueue());
+ }
+
+ private void sendReminders(final TransactionBuilder builder) {
+ final List extends Reminder> reminders = DBHelper.getReminders(gbDevice);
+ sendReminders(builder, reminders);
+ }
+
+ private void sendReminders(final TransactionBuilder builder, final List extends Reminder> reminders) {
+ final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
+
+ final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+ int reservedSlots = prefs.getInt(PREF_RESERVER_REMINDERS_CALENDAR, 9);
+ LOG.info("On Set Reminders. Reminders: {}, Reserved slots: {}", reminders.size(), reservedSlots);
+
+ // Send the reminders, skipping the reserved slots for calendar events
+ for (int i = 0; i < reminders.size(); i++) {
+ LOG.debug("Sending reminder at position {}", i + reservedSlots);
+
+ sendReminderToDevice(builder, i + reservedSlots, reminders.get(i));
+ }
+
+ // Delete the remaining slots, skipping the sent reminders and reserved slots
+ for (int i = reminders.size() + reservedSlots; i < coordinator.getReminderSlotCount(); i++) {
+ LOG.debug("Deleting reminder at position {}", i);
+
+ sendReminderToDevice(builder, i, null);
+ }
+ }
+
+ private void sendReminderToDevice(final TransactionBuilder builder, int position, final Reminder reminder) {
+ if (characteristicChunked == null) {
+ LOG.warn("characteristicChunked is null, not sending reminder");
+ return;
+ }
+
+ final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
+
+ if (position + 1 > coordinator.getReminderSlotCount()) {
+ LOG.error("Reminder for position {} is over the limit of {} reminders", position, coordinator.getReminderSlotCount());
+ return;
+ }
+
+ if (reminder == null) {
+ // Delete reminder
+ writeToChunked(builder, 3, new byte[]{(byte) (position & 0xFF), 0, 0, 0, 0, 0});
+
+ return;
+ }
+
+ final ByteBuffer buf = ByteBuffer.allocate(14 + reminder.getMessage().getBytes().length);
+ buf.order(ByteOrder.LITTLE_ENDIAN);
+
+ buf.put((byte) 0x0B);
+ buf.put((byte) (position & 0xFF));
+
+ switch(reminder.getRepetition()) {
+ case Reminder.ONCE:
+ buf.put(new byte[]{0x09, 0x00});
+ break;
+ case Reminder.EVERY_DAY:
+ buf.put(new byte[]{(byte) 0xE9, 0x0F});
+ break;
+ case Reminder.EVERY_WEEK:
+ buf.put(new byte[]{0x09, 0x01});
+ break;
+ case Reminder.EVERY_MONTH:
+ buf.put(new byte[]{0x09, 0x10});
+ break;
+ case Reminder.EVERY_YEAR:
+ buf.put(new byte[]{0x09, 0x20});
+ break;
+ default:
+ LOG.warn("Unknown repetition for reminder in position {}, defaulting to once", position);
+ buf.put(new byte[]{0x09, 0x00});
+ }
+
+ buf.put(new byte[]{0x00, 0x00}); // unknown
+
+ final Calendar cal = Calendar.getInstance();
+ cal.setTime(reminder.getDate());
+
+ buf.put(BLETypeConversions.shortCalendarToRawBytes(cal));
+ buf.put((byte) 0x00);
+
+ if (reminder.getMessage().getBytes().length > coordinator.getMaximumReminderMessageLength()) {
+ LOG.warn("The reminder message length {} is longer than {}, will be truncated",
+ reminder.getMessage().getBytes().length,
+ coordinator.getMaximumReminderMessageLength()
+ );
+ buf.put(Arrays.copyOf(reminder.getMessage().getBytes(), coordinator.getMaximumReminderMessageLength()));
+ } else {
+ buf.put(reminder.getMessage().getBytes());
+ }
+ buf.put((byte) 0x00);
+
+ writeToChunked(builder, 2, buf.array());
+ }
+
@Override
public void onNotification(NotificationSpec notificationSpec) {
if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) {
@@ -1903,19 +2016,21 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
return this;
}
+ final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
+ int availableSlots = prefs.getInt(PREF_RESERVER_REMINDERS_CALENDAR, 9);
+
CalendarEvents upcomingEvents = new CalendarEvents();
List calendarEvents = upcomingEvents.getCalendarEventList(getContext());
Calendar calendar = Calendar.getInstance();
int iteration = 0;
- int iterationMax = 8;
for (CalendarEvents.CalendarEvent calendarEvent : calendarEvents) {
if (calendarEvent.isAllDay()) {
continue;
}
- if (iteration > iterationMax) { // limit ?
+ if (iteration >= availableSlots) {
break;
}
@@ -1948,7 +2063,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
}
// Continue by deleting the events
- for(;iteration < iterationMax; iteration++){
+ for(;iteration < availableSlots; iteration++){
int length = 1 + 1 + 4 + 6 + 6 + 1 + 0 + 1;
ByteBuffer buf = ByteBuffer.allocate(length);
@@ -3014,6 +3129,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
setDisconnectNotification(builder);
setExposeHRThridParty(builder);
setHeartrateMeasurementInterval(builder, getHeartRateMeasurementInterval());
+ sendReminders(builder);
requestAlarms(builder);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
index 3c23c834f..d880f8aae 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
@@ -17,6 +17,7 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.service.serial;
+import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
@@ -28,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
@@ -262,4 +264,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
byte[] bytes = gbDeviceProtocol.encodeLedColor(color);
sendToDevice(bytes);
}
+
+ @Override
+ public void onSetReminders(ArrayList extends Reminder> reminders) {
+ byte[] bytes = gbDeviceProtocol.encodeReminders(reminders);
+ sendToDevice(bytes);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
index 7ca8d5cc2..ccf3adb52 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
@@ -17,6 +17,7 @@
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.service.serial;
+import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
@@ -24,6 +25,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
public abstract class GBDeviceProtocol {
@@ -141,6 +143,10 @@ public abstract class GBDeviceProtocol {
return null;
}
+ public byte[] encodeReminders(ArrayList extends Reminder> reminders) {
+ return null;
+ }
+
public byte[] encodeFmFrequency(float frequency) {
return null;
}
diff --git a/app/src/main/res/drawable/ic_device_set_reminders.xml b/app/src/main/res/drawable/ic_device_set_reminders.xml
new file mode 100644
index 000000000..7009a6763
--- /dev/null
+++ b/app/src/main/res/drawable/ic_device_set_reminders.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml
new file mode 100644
index 000000000..a7a81a25d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_save.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_configure_reminders.xml b/app/src/main/res/layout/activity_configure_reminders.xml
new file mode 100644
index 000000000..fbbc4cf8f
--- /dev/null
+++ b/app/src/main/res/layout/activity_configure_reminders.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_reminder_details.xml b/app/src/main/res/layout/activity_reminder_details.xml
new file mode 100644
index 000000000..294fef19e
--- /dev/null
+++ b/app/src/main/res/layout/activity_reminder_details.xml
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/device_itemv2.xml b/app/src/main/res/layout/device_itemv2.xml
index 7dd574733..2dcbefaa0 100644
--- a/app/src/main/res/layout/device_itemv2.xml
+++ b/app/src/main/res/layout/device_itemv2.xml
@@ -370,7 +370,7 @@
card_view:tint="@color/secondarytext" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 4b01fd8a4..53bf80961 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -222,8 +222,23 @@
Cancele para parar a vibração.
Atividade e sono
Configurar alarmes
+ Configurar Lembretes
Configurar alarmes
+ Configurar lembretes
Detalhes do alarme
+ Detalhes do lembrete
+ %1$s, uma vez
+ %1$s, todos os dias
+ %1$s, todas as semanas
+ %1$s, todos os meses
+ %1$s, todos os anos
+ Uma vez
+ Todos os dias
+ Todas as semanas
+ Todos os meses
+ Todos os anos
+ Apagar lembrete
+ De certeza que pretende apagar o lembrete?
Dom
Seg
Ter
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 29d8ab387..6e696d523 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -1490,6 +1490,14 @@
- 1800
+
+ - @string/reminder_once
+ - @string/reminder_every_day
+ - @string/reminder_every_week
+ - @string/reminder_every_month
+ - @string/reminder_every_year
+
+
- @string/filter_mode_none
- @string/filter_mode_whitelist
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8900ce1d6..bd2b0372b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -451,8 +451,29 @@
Cancel to stop vibration.
Activity and Sleep
Configure alarms
+ Configure reminders
Configure alarms
+ Configure reminders
+ Repeat
+ Date
+ Time
+ Message
+ %1$s, once
+ %1$s, every day
+ %1$s, every week
+ %1$s, every month
+ %1$s, every year
+ Once
+ Every day
+ Every week
+ Every month
+ Every year
+ Delete reminder
+ Are you sure you want to delete the reminder?
+ No free slots
+ The device has no free slots for reminders (total slots: %1$s)
Alarm details
+ Reminder details
Sun
Mon
Tue
@@ -577,6 +598,7 @@
Incompatible firmware
This firmware is not compatible with the device
Alarms to reserve for upcoming events
+ Reminders to reserve for upcoming events
Use heart rate sensor to improve sleep detection
Device time offset in hours (for detecting sleep of shift workers)
Find phone
diff --git a/app/src/main/res/xml/devicesettings_reserve_reminders_calendar.xml b/app/src/main/res/xml/devicesettings_reserve_reminders_calendar.xml
new file mode 100644
index 000000000..e7473ed64
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_reserve_reminders_calendar.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+