Mi Band 3: Add support for Reminders

This commit is contained in:
José Rebelo 2021-12-04 15:55:09 +00:00 committed by Gitea
parent 1e715b4914
commit 710b6f6699
32 changed files with 1241 additions and 5 deletions

View File

@ -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();

View File

@ -485,6 +485,10 @@
android:name=".activities.ConfigureAlarms"
android:label="@string/title_activity_set_alarm"
android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".activities.ConfigureReminders"
android:label="@string/title_activity_set_reminders"
android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".activities.devicesettings.DeviceSettingsActivity"
android:label="@string/title_activity_device_specific_settings"
@ -494,6 +498,12 @@
android:label="@string/title_activity_alarm_details"
android:parentActivityName=".activities.ConfigureAlarms"
android:screenOrientation="portrait" />
<activity
android:name=".activities.ReminderDetails"
android:label="@string/title_activity_reminder_details"
android:parentActivityName=".activities.ConfigureReminders"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".activities.VibrationActivity"
android:label="@string/title_activity_vibration"

View File

@ -0,0 +1,182 @@
/* 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 <http://www.gnu.org/licenses/>. */
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<Reminder> 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());
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. */
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<String> 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()));
}
}
}

View File

@ -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";

View File

@ -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);

View File

@ -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<GBDeviceAdapterv2.Vi
}
);
//set reminders
holder.setRemindersView.setVisibility(coordinator.getReminderSlotCount() > 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<GBDeviceAdapterv2.Vi
ImageView takeScreenshotView;
ImageView manageAppsView;
ImageView setAlarmsView;
ImageView setRemindersView;
ImageView showActivityGraphs;
ImageView showActivityTracks;
ImageView calibrateDevice;
@ -797,6 +814,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
takeScreenshotView = view.findViewById(R.id.device_action_take_screenshot);
manageAppsView = view.findViewById(R.id.device_action_manage_apps);
setAlarmsView = view.findViewById(R.id.device_action_set_alarms);
setRemindersView = view.findViewById(R.id.device_action_set_reminders);
showActivityGraphs = view.findViewById(R.id.device_action_show_activity_graphs);
showActivityTracks = view.findViewById(R.id.device_action_show_activity_tracks);
deviceInfoView = view.findViewById(R.id.device_info_image);

View File

@ -0,0 +1,148 @@
/* 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 <http://www.gnu.org/licenses/>. */
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<GBReminderListAdapter.ViewHolder> {
private final Context mContext;
private ArrayList<Reminder> reminderList;
public GBReminderListAdapter(Context context) {
this.mContext = context;
}
public void setReminderList(List<Reminder> reminders) {
this.reminderList = new ArrayList<>(reminders);
}
public ArrayList<Reminder> 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);
}
}
}

View File

@ -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<Reminder> 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<Reminder> 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();

View File

@ -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;

View File

@ -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
*/

View File

@ -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);

View File

@ -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,

View File

@ -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)

View File

@ -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";

View File

@ -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 <http://www.gnu.org/licenses/>. */
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();
}

View File

@ -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);

View File

@ -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)) {

View File

@ -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) {

View File

@ -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.CalendarEvent> 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);
}

View File

@ -17,6 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
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);
}
}

View File

@ -17,6 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */
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;
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
</vector>

View File

@ -0,0 +1,27 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:fitsSystemWindows="true"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:divider="@null"
android:id="@+id/reminder_list" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_gravity="bottom|end"
app:srcCompat="@drawable/ic_add"
android:layout_margin="16dp" />
</RelativeLayout>

View File

@ -0,0 +1,175 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ReminderDetails">
<androidx.cardview.widget.CardView
android:id="@+id/card_repeat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:foreground="?android:attr/selectableItemBackground"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="4dp"
card_view:contentPadding="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<TextView
android:id="@+id/label_repeat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reminder_repeat"
android:textAppearance="?android:attr/textAppearance"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/reminder_repeat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Once"
android:textAppearance="?android:attr/textAppearanceLarge"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/label_repeat" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:foreground="?android:attr/selectableItemBackground"
app:layout_constraintEnd_toStartOf="@id/card_time"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card_repeat"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="4dp"
card_view:contentPadding="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<TextView
android:id="@+id/label_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reminder_date"
android:textAppearance="?android:attr/textAppearance"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/reminder_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="2019-08-17"
android:textAppearance="?android:attr/textAppearanceLarge"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/label_date" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_time"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:foreground="?android:attr/selectableItemBackground"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/card_date"
app:layout_constraintTop_toBottomOf="@id/card_repeat"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="4dp"
card_view:contentPadding="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<TextView
android:id="@+id/label_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reminder_time"
android:textAppearance="?android:attr/textAppearance"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/reminder_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="16:35"
android:textAppearance="?android:attr/textAppearanceLarge"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/label_time" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/card_view4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:foreground="?android:attr/selectableItemBackground"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/card_time"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="4dp"
card_view:contentPadding="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<TextView
android:id="@+id/label_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reminder_message"
android:textAppearance="?android:attr/textAppearance"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/reminder_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/label_message" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:srcCompat="@drawable/ic_save" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -370,7 +370,7 @@
card_view:tint="@color/secondarytext" />
<ImageView
android:id="@+id/device_action_show_activity_graphs"
android:id="@+id/device_action_set_reminders"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_below="@id/device_image"
@ -378,6 +378,22 @@
android:layout_toEndOf="@id/device_action_set_alarms"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:contentDescription="@string/controlcenter_start_configure_reminders"
android:focusable="true"
android:padding="4dp"
android:scaleType="fitXY"
card_view:srcCompat="@drawable/ic_device_set_reminders"
android:tint="@color/secondarytext" />
<ImageView
android:id="@+id/device_action_show_activity_graphs"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_below="@id/device_image"
android:layout_margin="3dp"
android:layout_toEndOf="@id/device_action_set_reminders"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:contentDescription="@string/controlcenter_start_activitymonitor"
android:focusable="true"
android:padding="3dp"

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:foreground="?android:attr/selectableItemBackground"
card_view:cardCornerRadius="4dp"
card_view:cardElevation="4dp"
card_view:contentPadding="4dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<TextView
android:id="@+id/reminder_item_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="3dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="8dp"
android:text="Every ?, at ??:??"
android:textAppearance="?android:attr/textAppearance" />
<TextView
android:id="@+id/reminder_item_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="3dp"
android:layout_marginTop="25dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="8dp"
android:text="?"
android:textAppearance="?android:attr/textAppearanceLarge" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -222,8 +222,23 @@
<string name="control_center_cancel_to_stop_vibration">Cancele para parar a vibração.</string>
<string name="title_activity_charts">Atividade e sono</string>
<string name="title_activity_set_alarm">Configurar alarmes</string>
<string name="title_activity_set_reminders">Configurar Lembretes</string>
<string name="controlcenter_start_configure_alarms">Configurar alarmes</string>
<string name="controlcenter_start_configure_reminders">Configurar lembretes</string>
<string name="title_activity_alarm_details">Detalhes do alarme</string>
<string name="title_activity_reminder_details">Detalhes do lembrete</string>
<string name="reminder_time_once">%1$s, uma vez</string>
<string name="reminder_time_every_day">%1$s, todos os dias</string>
<string name="reminder_time_every_week">%1$s, todas as semanas</string>
<string name="reminder_time_every_month">%1$s, todos os meses</string>
<string name="reminder_time_every_year">%1$s, todos os anos</string>
<string name="reminder_once">Uma vez</string>
<string name="reminder_every_day">Todos os dias</string>
<string name="reminder_every_week">Todas as semanas</string>
<string name="reminder_every_month">Todos os meses</string>
<string name="reminder_every_year">Todos os anos</string>
<string name="reminder_delete_confirm_title">Apagar lembrete</string>
<string name="reminder_delete_confirm_description">De certeza que pretende apagar o lembrete?</string>
<string name="alarm_sun_short">Dom</string>
<string name="alarm_mon_short">Seg</string>
<string name="alarm_tue_short">Ter</string>

View File

@ -1490,6 +1490,14 @@
<item>1800</item>
</string-array>
<string-array name="reminder_repeat">
<item>@string/reminder_once</item>
<item>@string/reminder_every_day</item>
<item>@string/reminder_every_week</item>
<item>@string/reminder_every_month</item>
<item>@string/reminder_every_year</item>
</string-array>
<string-array name="notification_filter_modes_entries">
<item>@string/filter_mode_none</item>
<item>@string/filter_mode_whitelist</item>

View File

@ -451,8 +451,29 @@
<string name="control_center_cancel_to_stop_vibration">Cancel to stop vibration.</string>
<string name="title_activity_charts">Activity and Sleep</string>
<string name="title_activity_set_alarm">Configure alarms</string>
<string name="title_activity_set_reminders">Configure reminders</string>
<string name="controlcenter_start_configure_alarms">Configure alarms</string>
<string name="controlcenter_start_configure_reminders">Configure reminders</string>
<string name="reminder_repeat">Repeat</string>
<string name="reminder_date">Date</string>
<string name="reminder_time">Time</string>
<string name="reminder_message">Message</string>
<string name="reminder_time_once">%1$s, once</string>
<string name="reminder_time_every_day">%1$s, every day</string>
<string name="reminder_time_every_week">%1$s, every week</string>
<string name="reminder_time_every_month">%1$s, every month</string>
<string name="reminder_time_every_year">%1$s, every year</string>
<string name="reminder_once">Once</string>
<string name="reminder_every_day">Every day</string>
<string name="reminder_every_week">Every week</string>
<string name="reminder_every_month">Every month</string>
<string name="reminder_every_year">Every year</string>
<string name="reminder_delete_confirm_title">Delete reminder</string>
<string name="reminder_delete_confirm_description">Are you sure you want to delete the reminder?</string>
<string name="reminder_no_free_slots_title">No free slots</string>
<string name="reminder_no_free_slots_description">The device has no free slots for reminders (total slots: %1$s)</string>
<string name="title_activity_alarm_details">Alarm details</string>
<string name="title_activity_reminder_details">Reminder details</string>
<string name="alarm_sun_short">Sun</string>
<string name="alarm_mon_short">Mon</string>
<string name="alarm_tue_short">Tue</string>
@ -577,6 +598,7 @@
<string name="miband_fwinstaller_incompatible_version">Incompatible firmware</string>
<string name="fwinstaller_firmware_not_compatible_to_device">This firmware is not compatible with the device</string>
<string name="miband_prefs_reserve_alarm_calendar">Alarms to reserve for upcoming events</string>
<string name="miband_prefs_reserve_reminder_calendar">Reminders to reserve for upcoming events</string>
<string name="miband_prefs_hr_sleep_detection">Use heart rate sensor to improve sleep detection</string>
<string name="miband_prefs_device_time_offset_hours">Device time offset in hours (for detecting sleep of shift workers)</string>
<string name="prefs_find_phone">Find phone</string>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:icon="@drawable/ic_access_alarms"
android:defaultValue="9"
android:digits="0123"
android:inputType="number"
android:key="reserve_reminders_calendar"
android:maxLength="1"
android:title="@string/miband_prefs_reserve_reminder_calendar" />
</androidx.preference.PreferenceScreen>