Mi Band 3: Add support for Reminders

This commit is contained in:
José Rebelo
2021-12-11 22:39:18 +01:00
committed by Gitea
parent 1e715b4914
commit 710b6f6699
32 changed files with 1241 additions and 5 deletions
@@ -43,7 +43,7 @@ public class GBDaoGenerator {
public static void main(String[] args) throws Exception { 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 userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes); Entity user = addUserInfo(schema, userAttributes);
@@ -86,6 +86,7 @@ public class GBDaoGenerator {
addHybridHRActivitySample(schema, user, device); addHybridHRActivitySample(schema, user, device);
addCalendarSyncState(schema, device); addCalendarSyncState(schema, device);
addAlarms(schema, user, device); addAlarms(schema, user, device);
addReminders(schema, user, device);
Entity notificationFilter = addNotificationFilters(schema); Entity notificationFilter = addNotificationFilters(schema);
@@ -541,6 +542,25 @@ public class GBDaoGenerator {
alarm.addToOne(device, deviceId); 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) { private static void addNotificationFilterEntry(Schema schema, Entity notificationFilterEntity) {
Entity notificatonFilterEntry = addEntity(schema, "NotificationFilterEntry"); Entity notificatonFilterEntry = addEntity(schema, "NotificationFilterEntry");
notificatonFilterEntry.addIdProperty().autoincrement(); notificatonFilterEntry.addIdProperty().autoincrement();
+10
View File
@@ -485,6 +485,10 @@
android:name=".activities.ConfigureAlarms" android:name=".activities.ConfigureAlarms"
android:label="@string/title_activity_set_alarm" android:label="@string/title_activity_set_alarm"
android:parentActivityName=".activities.ControlCenterv2" /> android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".activities.ConfigureReminders"
android:label="@string/title_activity_set_reminders"
android:parentActivityName=".activities.ControlCenterv2" />
<activity <activity
android:name=".activities.devicesettings.DeviceSettingsActivity" android:name=".activities.devicesettings.DeviceSettingsActivity"
android:label="@string/title_activity_device_specific_settings" android:label="@string/title_activity_device_specific_settings"
@@ -494,6 +498,12 @@
android:label="@string/title_activity_alarm_details" android:label="@string/title_activity_alarm_details"
android:parentActivityName=".activities.ConfigureAlarms" android:parentActivityName=".activities.ConfigureAlarms"
android:screenOrientation="portrait" /> 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 <activity
android:name=".activities.VibrationActivity" android:name=".activities.VibrationActivity"
android:label="@string/title_activity_vibration" android:label="@string/title_activity_vibration"
@@ -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());
}
}
}
@@ -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()));
}
}
}
@@ -25,6 +25,7 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_NOTIFICATION_ENABLE = "notification_enable"; public static final String PREF_NOTIFICATION_ENABLE = "notification_enable";
public static final String PREF_SCREEN_ORIENTATION = "screen_orientation"; 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_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_ALLOW_HIGH_MTU = "allow_high_mtu";
public static final String PREF_SYNC_CALENDAR = "sync_calendar"; public static final String PREF_SYNC_CALENDAR = "sync_calendar";
public static final String PREF_USE_CUSTOM_DEVICEICON = "use_custom_deviceicon"; public static final String PREF_USE_CUSTOM_DEVICEICON = "use_custom_deviceicon";
@@ -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(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(MakibesHR3Constants.PREF_FIND_PHONE_DURATION, InputType.TYPE_CLASS_NUMBER);
setInputTypeFor(DeviceSettingsPreferenceConst.PREF_RESERVER_ALARMS_CALENDAR, 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); 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); final Preference deviceActionsFellSleep = findPreference(PREF_DEVICE_ACTION_FELL_SLEEP_SELECTION);
@@ -75,6 +75,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity; import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.BatteryInfoActivity; import nodomain.freeyourgadget.gadgetbridge.activities.BatteryInfoActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms; import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureReminders;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2; import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateDialog; import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateDialog;
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity; 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 //show graphs
holder.showActivityGraphs.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE); holder.showActivityGraphs.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
holder.showActivityGraphs.setOnClickListener(new View.OnClickListener() holder.showActivityGraphs.setOnClickListener(new View.OnClickListener()
@@ -743,6 +759,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
ImageView takeScreenshotView; ImageView takeScreenshotView;
ImageView manageAppsView; ImageView manageAppsView;
ImageView setAlarmsView; ImageView setAlarmsView;
ImageView setRemindersView;
ImageView showActivityGraphs; ImageView showActivityGraphs;
ImageView showActivityTracks; ImageView showActivityTracks;
ImageView calibrateDevice; ImageView calibrateDevice;
@@ -797,6 +814,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
takeScreenshotView = view.findViewById(R.id.device_action_take_screenshot); takeScreenshotView = view.findViewById(R.id.device_action_take_screenshot);
manageAppsView = view.findViewById(R.id.device_action_manage_apps); manageAppsView = view.findViewById(R.id.device_action_manage_apps);
setAlarmsView = view.findViewById(R.id.device_action_set_alarms); 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); showActivityGraphs = view.findViewById(R.id.device_action_show_activity_graphs);
showActivityTracks = view.findViewById(R.id.device_action_show_activity_tracks); showActivityTracks = view.findViewById(R.id.device_action_show_activity_tracks);
deviceInfoView = view.findViewById(R.id.device_info_image); deviceInfoView = view.findViewById(R.id.device_info_image);
@@ -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);
}
}
}
@@ -55,6 +55,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes; import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao; import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao; 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.Tag;
import nodomain.freeyourgadget.gadgetbridge.entities.TagDao; import nodomain.freeyourgadget.gadgetbridge.entities.TagDao;
import nodomain.freeyourgadget.gadgetbridge.entities.User; 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() { public static void clearSession() {
try (DBHandler dbHandler = GBApplication.acquireDB()) { try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession(); DaoSession session = dbHandler.getDaoSession();
@@ -222,6 +222,16 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
return false; return false;
} }
@Override
public int getMaximumReminderMessageLength() {
return 0;
}
@Override
public int getReminderSlotCount() {
return 0;
}
@Override @Override
public boolean supportsRgbLedColor() { public boolean supportsRgbLedColor() {
return false; return false;
@@ -321,6 +321,16 @@ public interface DeviceCoordinator {
*/ */
boolean supportsMusicInfo(); 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 * Indicates whether the device has an led which supports custom colors
*/ */
@@ -30,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
/** /**
@@ -46,6 +47,8 @@ public interface EventHandler {
void onSetAlarms(ArrayList<? extends Alarm> alarms); void onSetAlarms(ArrayList<? extends Alarm> alarms);
void onSetReminders(ArrayList<? extends Reminder> reminders);
void onSetCallState(CallSpec callSpec); void onSetCallState(CallSpec callSpec);
void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec); void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec);
@@ -84,6 +84,15 @@ public class MiBand3Coordinator extends HuamiCoordinator {
return true; 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) { public static String getNightMode(String deviceAddress) {
Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(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_liftwrist_display,
R.xml.devicesettings_swipeunlock, R.xml.devicesettings_swipeunlock,
R.xml.devicesettings_sync_calendar, R.xml.devicesettings_sync_calendar,
R.xml.devicesettings_reserve_reminders_calendar,
R.xml.devicesettings_expose_hr_thirdparty, R.xml.devicesettings_expose_hr_thirdparty,
R.xml.devicesettings_bt_connected_advertisement, R.xml.devicesettings_bt_connected_advertisement,
R.xml.devicesettings_device_actions, R.xml.devicesettings_device_actions,
@@ -41,6 +41,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
import nodomain.freeyourgadget.gadgetbridge.util.RtlUtils; import nodomain.freeyourgadget.gadgetbridge.util.RtlUtils;
@@ -225,6 +226,13 @@ public class GBDeviceService implements DeviceService {
invokeService(intent); invokeService(intent);
} }
@Override
public void onSetReminders(ArrayList<? extends Reminder> reminders) {
Intent intent = createIntent().setAction(ACTION_SET_REMINDERS)
.putExtra(EXTRA_REMINDERS, reminders);
invokeService(intent);
}
@Override @Override
public void onSetMusicInfo(MusicSpec musicSpec) { public void onSetMusicInfo(MusicSpec musicSpec) {
Intent intent = createIntent().setAction(ACTION_SETMUSICINFO) Intent intent = createIntent().setAction(ACTION_SETMUSICINFO)
@@ -55,6 +55,7 @@ public interface DeviceService extends EventHandler {
String ACTION_SET_CONSTANT_VIBRATION = PREFIX + ".action.set_constant_vibration"; String ACTION_SET_CONSTANT_VIBRATION = PREFIX + ".action.set_constant_vibration";
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms"; String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
String ACTION_SAVE_ALARMS = PREFIX + ".action.save_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_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples"; String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement"; 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_URI = "uri";
String EXTRA_CONFIG = "config"; String EXTRA_CONFIG = "config";
String EXTRA_ALARMS = "alarms"; String EXTRA_ALARMS = "alarms";
String EXTRA_REMINDERS = "reminders";
String EXTRA_CONNECT_FIRST_TIME = "connect_first_time"; String EXTRA_CONNECT_FIRST_TIME = "connect_first_time";
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps"; String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
String EXTRA_INTERVAL_SECONDS = "interval_seconds"; String EXTRA_INTERVAL_SECONDS = "interval_seconds";
@@ -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();
}
@@ -73,6 +73,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.AutoConnectIntervalReceiver; import nodomain.freeyourgadget.gadgetbridge.service.receivers.AutoConnectIntervalReceiver;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBAutoFetchReceiver; 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_FM_FREQUENCY;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL; 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_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_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION; 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_TITLE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TYPE; 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_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_RESET_FLAGS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_VIBRATION_INTENSITY; 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); ArrayList<? extends Alarm> alarms = (ArrayList<? extends Alarm>) intent.getSerializableExtra(EXTRA_ALARMS);
mDeviceSupport.onSetAlarms(alarms); mDeviceSupport.onSetAlarms(alarms);
break; 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: { case ACTION_ENABLE_REALTIME_STEPS: {
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false); boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableRealtimeSteps(enable); mDeviceSupport.onEnableRealtimeSteps(enable);
@@ -37,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
/** /**
@@ -309,6 +310,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
delegate.onSetAlarms(alarms); delegate.onSetAlarms(alarms);
} }
@Override
public void onSetReminders(ArrayList<? extends Reminder> reminders) {
if (checkBusy("set reminders")) {
return;
}
delegate.onSetReminders(reminders);
}
@Override @Override
public void onEnableRealtimeSteps(boolean enable) { public void onEnableRealtimeSteps(boolean enable) {
if (checkBusy("enable realtime steps: " + enable)) { if (checkBusy("enable realtime steps: " + enable)) {
@@ -36,6 +36,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.Logging; import nodomain.freeyourgadget.gadgetbridge.Logging;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile; 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 @Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
@@ -107,6 +107,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.Weather; import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; 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_DATEFORMAT;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_LANGUAGE; 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_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_SOUNDS;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR; import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_TIMEFORMAT; 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 @Override
public void onNotification(NotificationSpec notificationSpec) { public void onNotification(NotificationSpec notificationSpec) {
if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) { if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) {
@@ -1903,19 +2016,21 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
return this; return this;
} }
final Prefs prefs = new Prefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()));
int availableSlots = prefs.getInt(PREF_RESERVER_REMINDERS_CALENDAR, 9);
CalendarEvents upcomingEvents = new CalendarEvents(); CalendarEvents upcomingEvents = new CalendarEvents();
List<CalendarEvents.CalendarEvent> calendarEvents = upcomingEvents.getCalendarEventList(getContext()); List<CalendarEvents.CalendarEvent> calendarEvents = upcomingEvents.getCalendarEventList(getContext());
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
int iteration = 0; int iteration = 0;
int iterationMax = 8;
for (CalendarEvents.CalendarEvent calendarEvent : calendarEvents) { for (CalendarEvents.CalendarEvent calendarEvent : calendarEvents) {
if (calendarEvent.isAllDay()) { if (calendarEvent.isAllDay()) {
continue; continue;
} }
if (iteration > iterationMax) { // limit ? if (iteration >= availableSlots) {
break; break;
} }
@@ -1948,7 +2063,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
} }
// Continue by deleting the events // Continue by deleting the events
for(;iteration < iterationMax; iteration++){ for(;iteration < availableSlots; iteration++){
int length = 1 + 1 + 4 + 6 + 6 + 1 + 0 + 1; int length = 1 + 1 + 4 + 6 + 6 + 1 + 0 + 1;
ByteBuffer buf = ByteBuffer.allocate(length); ByteBuffer buf = ByteBuffer.allocate(length);
@@ -3014,6 +3129,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
setDisconnectNotification(builder); setDisconnectNotification(builder);
setExposeHRThridParty(builder); setExposeHRThridParty(builder);
setHeartrateMeasurementInterval(builder, getHeartRateMeasurementInterval()); setHeartrateMeasurementInterval(builder, getHeartRateMeasurementInterval());
sendReminders(builder);
requestAlarms(builder); requestAlarms(builder);
} }
@@ -17,6 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.serial; package nodomain.freeyourgadget.gadgetbridge.service.serial;
import java.util.ArrayList;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; 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.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
@@ -262,4 +264,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
byte[] bytes = gbDeviceProtocol.encodeLedColor(color); byte[] bytes = gbDeviceProtocol.encodeLedColor(color);
sendToDevice(bytes); sendToDevice(bytes);
} }
@Override
public void onSetReminders(ArrayList<? extends Reminder> reminders) {
byte[] bytes = gbDeviceProtocol.encodeReminders(reminders);
sendToDevice(bytes);
}
} }
@@ -17,6 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.serial; package nodomain.freeyourgadget.gadgetbridge.service.serial;
import java.util.ArrayList;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; 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.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
public abstract class GBDeviceProtocol { public abstract class GBDeviceProtocol {
@@ -141,6 +143,10 @@ public abstract class GBDeviceProtocol {
return null; return null;
} }
public byte[] encodeReminders(ArrayList<? extends Reminder> reminders) {
return null;
}
public byte[] encodeFmFrequency(float frequency) { public byte[] encodeFmFrequency(float frequency) {
return null; return null;
} }
@@ -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>
+9
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>
@@ -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>
@@ -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>
+17 -1
View File
@@ -370,7 +370,7 @@
card_view:tint="@color/secondarytext" /> card_view:tint="@color/secondarytext" />
<ImageView <ImageView
android:id="@+id/device_action_show_activity_graphs" android:id="@+id/device_action_set_reminders"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_below="@id/device_image" android:layout_below="@id/device_image"
@@ -378,6 +378,22 @@
android:layout_toEndOf="@id/device_action_set_alarms" android:layout_toEndOf="@id/device_action_set_alarms"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:clickable="true" 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:contentDescription="@string/controlcenter_start_activitymonitor"
android:focusable="true" android:focusable="true"
android:padding="3dp" android:padding="3dp"
+51
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>
+15
View File
@@ -222,8 +222,23 @@
<string name="control_center_cancel_to_stop_vibration">Cancele para parar a vibração.</string> <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_charts">Atividade e sono</string>
<string name="title_activity_set_alarm">Configurar alarmes</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_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_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_sun_short">Dom</string>
<string name="alarm_mon_short">Seg</string> <string name="alarm_mon_short">Seg</string>
<string name="alarm_tue_short">Ter</string> <string name="alarm_tue_short">Ter</string>
+8
View File
@@ -1490,6 +1490,14 @@
<item>1800</item> <item>1800</item>
</string-array> </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"> <string-array name="notification_filter_modes_entries">
<item>@string/filter_mode_none</item> <item>@string/filter_mode_none</item>
<item>@string/filter_mode_whitelist</item> <item>@string/filter_mode_whitelist</item>
+22
View File
@@ -451,8 +451,29 @@
<string name="control_center_cancel_to_stop_vibration">Cancel to stop vibration.</string> <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_charts">Activity and Sleep</string>
<string name="title_activity_set_alarm">Configure alarms</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_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_alarm_details">Alarm details</string>
<string name="title_activity_reminder_details">Reminder details</string>
<string name="alarm_sun_short">Sun</string> <string name="alarm_sun_short">Sun</string>
<string name="alarm_mon_short">Mon</string> <string name="alarm_mon_short">Mon</string>
<string name="alarm_tue_short">Tue</string> <string name="alarm_tue_short">Tue</string>
@@ -577,6 +598,7 @@
<string name="miband_fwinstaller_incompatible_version">Incompatible firmware</string> <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="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_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_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="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> <string name="prefs_find_phone">Find phone</string>
@@ -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>