mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Garmin: Set alarms from phone
This commit is contained in:
@@ -58,7 +58,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
final Schema schema = new Schema(111, MAIN_PACKAGE + ".entities");
|
||||
final Schema schema = new Schema(112, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@@ -1242,6 +1242,8 @@ public class GBDaoGenerator {
|
||||
alarm.addBooleanProperty("unused").notNull();
|
||||
alarm.addStringProperty("title");
|
||||
alarm.addStringProperty("description");
|
||||
alarm.addIntProperty("soundCode").notNull();
|
||||
alarm.addBooleanProperty("backlight").notNull();
|
||||
alarm.addToOne(user, userId);
|
||||
alarm.addToOne(device, deviceId);
|
||||
}
|
||||
|
||||
+151
-155
@@ -23,137 +23,89 @@ import android.text.Spanned;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TimePicker;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
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.databinding.ActivityAlarmDetailsBinding;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm.ALARM_LABEL;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm.ALARM_SOUND;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
|
||||
public class AlarmDetails extends AbstractGBActivity {
|
||||
private ActivityAlarmDetailsBinding binding;
|
||||
|
||||
private Alarm alarm;
|
||||
private TimePicker timePicker;
|
||||
private CheckedTextView cbSmartWakeup;
|
||||
private CheckedTextView cbSnooze;
|
||||
private CheckedTextView cbMonday;
|
||||
private CheckedTextView cbTuesday;
|
||||
private CheckedTextView cbWednesday;
|
||||
private CheckedTextView cbThursday;
|
||||
private CheckedTextView cbFriday;
|
||||
private CheckedTextView cbSaturday;
|
||||
private CheckedTextView cbSunday;
|
||||
private EditText title;
|
||||
private EditText description;
|
||||
private EditText smartWakeupInterval;
|
||||
private GBDevice device;
|
||||
|
||||
private final Map<String, ALARM_SOUND> textToAlarmSound = new HashMap<>(ALARM_SOUND.values().length);
|
||||
private final Map<String, ALARM_LABEL> textToAlarmLabel = new HashMap<>(ALARM_LABEL.values().length);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_alarm_details);
|
||||
binding = ActivityAlarmDetailsBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
alarm = (Alarm) getIntent().getSerializableExtra(nodomain.freeyourgadget.gadgetbridge.model.Alarm.EXTRA_ALARM);
|
||||
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (device == null) {
|
||||
throw new IllegalArgumentException("No device provided to AlarmDetails");
|
||||
}
|
||||
|
||||
title = findViewById(R.id.alarm_title);
|
||||
description = findViewById(R.id.alarm_description);
|
||||
binding.cbSmartWakeup.setOnClickListener(v -> {
|
||||
((CheckedTextView) v).toggle();
|
||||
binding.cbSmartWakeupInterval.setEnabled(((CheckedTextView) v).isChecked());
|
||||
});
|
||||
binding.cbSnooze.setOnClickListener(v -> ((CheckedTextView) v).toggle());
|
||||
binding.cbMonday.setOnClickListener(v -> ((CheckedTextView) v).toggle());
|
||||
binding.cbTuesday.setOnClickListener(v -> ((CheckedTextView) v).toggle());
|
||||
binding.cbWednesday.setOnClickListener(v -> ((CheckedTextView) v).toggle());
|
||||
binding.cbThursday.setOnClickListener(v -> ((CheckedTextView) v).toggle());
|
||||
binding.cbFriday.setOnClickListener(v -> ((CheckedTextView) v).toggle());
|
||||
binding.cbSaturday.setOnClickListener(v -> ((CheckedTextView) v).toggle());
|
||||
binding.cbSunday.setOnClickListener(v -> ((CheckedTextView) v).toggle());
|
||||
|
||||
timePicker = findViewById(R.id.alarm_time_picker);
|
||||
cbSmartWakeup = findViewById(R.id.alarm_cb_smart_wakeup);
|
||||
smartWakeupInterval = findViewById(R.id.alarm_cb_smart_wakeup_interval);
|
||||
cbSnooze = findViewById(R.id.alarm_cb_snooze);
|
||||
cbMonday = findViewById(R.id.alarm_cb_monday);
|
||||
cbTuesday = findViewById(R.id.alarm_cb_tuesday);
|
||||
cbWednesday = findViewById(R.id.alarm_cb_wednesday);
|
||||
cbThursday = findViewById(R.id.alarm_cb_thursday);
|
||||
cbFriday = findViewById(R.id.alarm_cb_friday);
|
||||
cbSaturday = findViewById(R.id.alarm_cb_saturday);
|
||||
cbSunday = findViewById(R.id.alarm_cb_sunday);
|
||||
|
||||
|
||||
cbSmartWakeup.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
smartWakeupInterval.setEnabled(((CheckedTextView) v).isChecked());
|
||||
}
|
||||
});
|
||||
cbSnooze.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbMonday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbTuesday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbWednesday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbThursday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbFriday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbSaturday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbSunday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
|
||||
timePicker.setIs24HourView(DateFormat.is24HourFormat(GBApplication.getContext()));
|
||||
timePicker.setCurrentHour(alarm.getHour());
|
||||
timePicker.setCurrentMinute(alarm.getMinute());
|
||||
binding.timePicker.setIs24HourView(DateFormat.is24HourFormat(GBApplication.getContext()));
|
||||
binding.timePicker.setCurrentHour(alarm.getHour());
|
||||
binding.timePicker.setCurrentMinute(alarm.getMinute());
|
||||
|
||||
boolean smartAlarmSupported = supportsSmartWakeup(alarm.getPosition());
|
||||
boolean smartAlarmForced = forcedSmartWakeup(alarm.getPosition());
|
||||
boolean smartAlarmIntervalSupported = supportsSmartWakeupInterval(alarm.getPosition());
|
||||
|
||||
cbSmartWakeup.setChecked(alarm.getSmartWakeup() || smartAlarmForced);
|
||||
cbSmartWakeup.setVisibility(smartAlarmSupported ? View.VISIBLE : View.GONE);
|
||||
binding.cbSmartWakeup.setChecked(alarm.getSmartWakeup() || smartAlarmForced);
|
||||
binding.cbSmartWakeup.setVisibility(smartAlarmSupported ? View.VISIBLE : View.GONE);
|
||||
if (smartAlarmForced) {
|
||||
cbSmartWakeup.setEnabled(false);
|
||||
binding.cbSmartWakeup.setEnabled(false);
|
||||
// Force the text to be visible for the "interval" part
|
||||
// Enabled or not can still be seen in the checkmark
|
||||
// TODO: I'd like feedback on this
|
||||
if (GBApplication.isDarkThemeEnabled())
|
||||
cbSmartWakeup.setTextColor(getResources().getColor(android.R.color.secondary_text_dark));
|
||||
binding.cbSmartWakeup.setTextColor(getResources().getColor(android.R.color.secondary_text_dark));
|
||||
else
|
||||
cbSmartWakeup.setTextColor(getResources().getColor(android.R.color.secondary_text_light));
|
||||
binding.cbSmartWakeup.setTextColor(getResources().getColor(android.R.color.secondary_text_light));
|
||||
}
|
||||
if (smartAlarmIntervalSupported)
|
||||
cbSmartWakeup.setText(R.string.alarm_smart_wakeup_interval);
|
||||
binding.cbSmartWakeup.setText(R.string.alarm_smart_wakeup_interval);
|
||||
|
||||
smartWakeupInterval.setVisibility(smartAlarmSupported && smartAlarmIntervalSupported ? View.VISIBLE : View.GONE);
|
||||
smartWakeupInterval.setEnabled(alarm.getSmartWakeup() || smartAlarmForced);
|
||||
if (alarm.getSmartWakeupInterval() != null)
|
||||
smartWakeupInterval.setText(NumberFormat.getInstance().format(alarm.getSmartWakeupInterval()));
|
||||
smartWakeupInterval.setFilters(new InputFilter[] {
|
||||
binding.cbSmartWakeupInterval.setVisibility(smartAlarmSupported && smartAlarmIntervalSupported ? View.VISIBLE : View.GONE);
|
||||
binding.cbSmartWakeupInterval.setEnabled(alarm.getSmartWakeup() || smartAlarmForced);
|
||||
if (alarm.getSmartWakeupInterval() != null) {
|
||||
binding.cbSmartWakeupInterval.setText(NumberFormat.getInstance().format(alarm.getSmartWakeupInterval()));
|
||||
}
|
||||
binding.cbSmartWakeupInterval.setFilters(new InputFilter[]{
|
||||
new InputFilter() {
|
||||
@Override
|
||||
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
|
||||
@@ -164,8 +116,8 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
try {
|
||||
int value = Integer.parseInt(strValue);
|
||||
if (value > 255) {
|
||||
smartWakeupInterval.setText("255");
|
||||
smartWakeupInterval.setSelection(3); // Move cursor to end
|
||||
binding.cbSmartWakeupInterval.setText("255");
|
||||
binding.cbSmartWakeupInterval.setSelection(3); // Move cursor to end
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return "";
|
||||
@@ -175,87 +127,111 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
}
|
||||
});
|
||||
|
||||
cbSnooze.setChecked(alarm.getSnooze());
|
||||
int snoozeVisibility = supportsSnoozing(device) ? View.VISIBLE : View.GONE;
|
||||
cbSnooze.setVisibility(snoozeVisibility);
|
||||
binding.cbSnooze.setChecked(alarm.getSnooze());
|
||||
binding.cbSnooze.setVisibility(supportsSnoozing() ? View.VISIBLE : View.GONE);
|
||||
|
||||
title.setVisibility(supportsTitle() ? View.VISIBLE : View.GONE);
|
||||
title.setText(alarm.getTitle());
|
||||
binding.cbBacklight.setChecked(alarm.getBacklight());
|
||||
binding.cbBacklight.setVisibility(supportsAlarmBacklight() ? View.VISIBLE : View.GONE);
|
||||
binding.cbBacklight.setOnClickListener(v -> ((CheckedTextView) v).toggle());
|
||||
|
||||
binding.presetLabelLayout.setVisibility(supportsAlarmTitlePresets() ? View.VISIBLE : View.GONE);
|
||||
if (supportsAlarmTitlePresets()) {
|
||||
final List<ALARM_LABEL> alarmTitlePresets = getAlarmTitlePresets();
|
||||
String[] items = new String[alarmTitlePresets.size()];
|
||||
for (int i = 0; i < alarmTitlePresets.size(); i++) {
|
||||
items[i] = getString(alarmTitlePresets.get(i).getLabel());
|
||||
if (alarmTitlePresets.get(i).name().equals(alarm.getTitle())) {
|
||||
binding.presetLabelSpinner.setText(items[i], false);
|
||||
}
|
||||
textToAlarmLabel.put(items[i], alarmTitlePresets.get(i));
|
||||
}
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, items);
|
||||
binding.presetLabelSpinner.setAdapter(adapter);
|
||||
if (StringUtils.isEmpty(binding.presetLabelSpinner.getText().toString())) {
|
||||
binding.presetLabelSpinner.setText(items[0], false);
|
||||
}
|
||||
}
|
||||
|
||||
binding.soundModeLayout.setVisibility(supportsAlarmSounds() ? View.VISIBLE : View.GONE);
|
||||
if (supportsAlarmSounds()) {
|
||||
String[] items = new String[ALARM_SOUND.values().length - 1];
|
||||
for (int i = 0; i < items.length; i++) {
|
||||
final ALARM_SOUND alarmSound = ALARM_SOUND.values()[i + 1]; // skip unused
|
||||
items[i] = getString(alarmSound.getLabel());
|
||||
if (alarm.getSoundCode() == i) {
|
||||
binding.soundModeSpinner.setText(items[i], false);
|
||||
}
|
||||
textToAlarmSound.put(items[i], alarmSound);
|
||||
}
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, items);
|
||||
binding.soundModeSpinner.setAdapter(adapter);
|
||||
}
|
||||
|
||||
binding.title.setVisibility(supportsTitle() ? View.VISIBLE : View.GONE);
|
||||
binding.title.setText(alarm.getTitle());
|
||||
|
||||
final int titleLimit = getAlarmTitleLimit();
|
||||
if (titleLimit > 0) {
|
||||
title.setFilters(new InputFilter[]{new InputFilter.LengthFilter(titleLimit)});
|
||||
binding.title.setFilters(new InputFilter[]{new InputFilter.LengthFilter(titleLimit)});
|
||||
}
|
||||
|
||||
description.setVisibility(supportsDescription() ? View.VISIBLE : View.GONE);
|
||||
description.setText(alarm.getDescription());
|
||||
binding.description.setVisibility(supportsDescription() ? View.VISIBLE : View.GONE);
|
||||
binding.description.setText(alarm.getDescription());
|
||||
|
||||
cbMonday.setChecked(alarm.getRepetition(Alarm.ALARM_MON));
|
||||
cbTuesday.setChecked(alarm.getRepetition(Alarm.ALARM_TUE));
|
||||
cbWednesday.setChecked(alarm.getRepetition(Alarm.ALARM_WED));
|
||||
cbThursday.setChecked(alarm.getRepetition(Alarm.ALARM_THU));
|
||||
cbFriday.setChecked(alarm.getRepetition(Alarm.ALARM_FRI));
|
||||
cbSaturday.setChecked(alarm.getRepetition(Alarm.ALARM_SAT));
|
||||
cbSunday.setChecked(alarm.getRepetition(Alarm.ALARM_SUN));
|
||||
binding.cbMonday.setChecked(alarm.getRepetition(Alarm.ALARM_MON));
|
||||
binding.cbTuesday.setChecked(alarm.getRepetition(Alarm.ALARM_TUE));
|
||||
binding.cbWednesday.setChecked(alarm.getRepetition(Alarm.ALARM_WED));
|
||||
binding.cbThursday.setChecked(alarm.getRepetition(Alarm.ALARM_THU));
|
||||
binding.cbFriday.setChecked(alarm.getRepetition(Alarm.ALARM_FRI));
|
||||
binding.cbSaturday.setChecked(alarm.getRepetition(Alarm.ALARM_SAT));
|
||||
binding.cbSunday.setChecked(alarm.getRepetition(Alarm.ALARM_SUN));
|
||||
}
|
||||
|
||||
private boolean supportsSmartWakeup(int position) {
|
||||
if (device != null) {
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
return coordinator.supportsSmartWakeup(device, position);
|
||||
}
|
||||
return false;
|
||||
return device.getDeviceCoordinator().supportsSmartWakeup(device, position);
|
||||
}
|
||||
|
||||
private boolean supportsSmartWakeupInterval(int position) {
|
||||
if (device != null) {
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
return coordinator.supportsSmartWakeupInterval(device, position);
|
||||
}
|
||||
return false;
|
||||
return device.getDeviceCoordinator().supportsSmartWakeupInterval(device, position);
|
||||
}
|
||||
|
||||
/**
|
||||
* The alarm at this position *must* be a smart alarm
|
||||
*/
|
||||
private boolean forcedSmartWakeup(int position) {
|
||||
if (device != null) {
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
return coordinator.forcedSmartWakeup(device, position);
|
||||
}
|
||||
return false;
|
||||
return device.getDeviceCoordinator().forcedSmartWakeup(device, position);
|
||||
}
|
||||
|
||||
private boolean supportsTitle() {
|
||||
if (device != null) {
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
return coordinator.supportsAlarmTitle(device);
|
||||
}
|
||||
return false;
|
||||
return device.getDeviceCoordinator().supportsAlarmTitle(device);
|
||||
}
|
||||
|
||||
private int getAlarmTitleLimit() {
|
||||
if (device != null) {
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
return coordinator.getAlarmTitleLimit(device);
|
||||
}
|
||||
return -1;
|
||||
return device.getDeviceCoordinator().getAlarmTitleLimit(device);
|
||||
}
|
||||
|
||||
private boolean supportsDescription() {
|
||||
if (device != null) {
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
return coordinator.supportsAlarmDescription(device);
|
||||
}
|
||||
return false;
|
||||
return device.getDeviceCoordinator().supportsAlarmDescription(device);
|
||||
}
|
||||
|
||||
private boolean supportsSnoozing(GBDevice device) {
|
||||
if (device != null) {
|
||||
DeviceCoordinator coordinator = device.getDeviceCoordinator();
|
||||
return coordinator.supportsAlarmSnoozing(device);
|
||||
private boolean supportsSnoozing() {
|
||||
return device.getDeviceCoordinator().supportsAlarmSnoozing(device);
|
||||
}
|
||||
return false;
|
||||
|
||||
private boolean supportsAlarmSounds() {
|
||||
return device.getDeviceCoordinator().supportsAlarmSounds(device);
|
||||
}
|
||||
|
||||
private boolean supportsAlarmBacklight() {
|
||||
return device.getDeviceCoordinator().supportsAlarmBacklight(device);
|
||||
}
|
||||
|
||||
private boolean supportsAlarmTitlePresets() {
|
||||
return device.getDeviceCoordinator().supportsAlarmTitlePresets(device);
|
||||
}
|
||||
|
||||
private List<ALARM_LABEL> getAlarmTitlePresets() {
|
||||
return device.getDeviceCoordinator().getAlarmTitlePresets(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -271,20 +247,40 @@ public class AlarmDetails extends AbstractGBActivity {
|
||||
|
||||
private void updateAlarm() {
|
||||
// Set alarm as used and enabled if time has changed
|
||||
if (alarm.getUnused() && alarm.getHour() != timePicker.getCurrentHour() || alarm.getMinute() != timePicker.getCurrentMinute()) {
|
||||
if (alarm.getUnused() && alarm.getHour() != binding.timePicker.getCurrentHour() || alarm.getMinute() != binding.timePicker.getCurrentMinute()) {
|
||||
alarm.setUnused(false);
|
||||
alarm.setEnabled(true);
|
||||
}
|
||||
alarm.setSmartWakeup(supportsSmartWakeup(alarm.getPosition()) && cbSmartWakeup.isChecked());
|
||||
String interval = smartWakeupInterval.getText().toString();
|
||||
alarm.setSmartWakeupInterval(interval.equals("") ? null : Integer.parseInt(interval));
|
||||
alarm.setSnooze(supportsSnoozing(device) && cbSnooze.isChecked());
|
||||
int repetitionMask = AlarmUtils.createRepetitionMask(cbMonday.isChecked(), cbTuesday.isChecked(), cbWednesday.isChecked(), cbThursday.isChecked(), cbFriday.isChecked(), cbSaturday.isChecked(), cbSunday.isChecked());
|
||||
alarm.setSmartWakeup(supportsSmartWakeup(alarm.getPosition()) && binding.cbSmartWakeup.isChecked());
|
||||
String interval = binding.cbSmartWakeupInterval.getText().toString();
|
||||
alarm.setSmartWakeupInterval(interval.isEmpty() ? null : Integer.parseInt(interval));
|
||||
alarm.setSnooze(supportsSnoozing() && binding.cbSnooze.isChecked());
|
||||
int repetitionMask = AlarmUtils.createRepetitionMask(
|
||||
binding.cbMonday.isChecked(),
|
||||
binding.cbTuesday.isChecked(),
|
||||
binding.cbWednesday.isChecked(),
|
||||
binding.cbThursday.isChecked(),
|
||||
binding.cbFriday.isChecked(),
|
||||
binding.cbSaturday.isChecked(),
|
||||
binding.cbSunday.isChecked()
|
||||
);
|
||||
alarm.setRepetition(repetitionMask);
|
||||
alarm.setHour(timePicker.getCurrentHour());
|
||||
alarm.setMinute(timePicker.getCurrentMinute());
|
||||
alarm.setTitle(title.getText().toString());
|
||||
alarm.setDescription(description.getText().toString());
|
||||
alarm.setHour(binding.timePicker.getCurrentHour());
|
||||
alarm.setMinute(binding.timePicker.getCurrentMinute());
|
||||
alarm.setBacklight(binding.cbBacklight.isChecked());
|
||||
if (supportsAlarmTitlePresets()) {
|
||||
final ALARM_LABEL alarmLabel = textToAlarmLabel.get(binding.presetLabelSpinner.getText().toString());
|
||||
if (alarmLabel != null) {
|
||||
alarm.setTitle(alarmLabel.name());
|
||||
} else {
|
||||
alarm.setTitle("");
|
||||
}
|
||||
} else {
|
||||
alarm.setTitle(binding.title.getText().toString());
|
||||
}
|
||||
final ALARM_SOUND alarmSound = textToAlarmSound.get(binding.soundModeSpinner.getText().toString());
|
||||
alarm.setSoundCode(Objects.requireNonNullElse(alarmSound, ALARM_SOUND.UNSET).ordinal());
|
||||
alarm.setDescription(binding.description.getText().toString());
|
||||
DBHelper.store(alarm);
|
||||
}
|
||||
|
||||
|
||||
+1
-5
@@ -25,7 +25,6 @@ import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -43,12 +42,9 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
|
||||
public class ConfigureAlarms extends AbstractGBActivity {
|
||||
@@ -134,7 +130,7 @@ public class ConfigureAlarms extends AbstractGBActivity {
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
LOG.info("adding missing alarm at position " + position);
|
||||
LOG.info("adding missing alarm at position {}", position);
|
||||
alarms.add(position, AlarmUtils.createDefaultAlarm(daoSession, getGbDevice(), position));
|
||||
}
|
||||
}
|
||||
|
||||
+9
-9
@@ -38,6 +38,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
@@ -72,6 +73,7 @@ public final class DeviceSettingsUtils {
|
||||
|
||||
/**
|
||||
* Populates a list preference, or hides it if no known supported values are known.
|
||||
* @noinspection ConstantValue
|
||||
*/
|
||||
public static void populateOrHideListPreference(final CharSequence prefKey,
|
||||
final DeviceSpecificSettingsHandler handler,
|
||||
@@ -137,11 +139,10 @@ public final class DeviceSettingsUtils {
|
||||
final String possibleValue = possibleValues.get(i);
|
||||
final CharSequence knownLabel = entryNames.get(possibleValue);
|
||||
|
||||
if (knownLabel != null) {
|
||||
entries[i] = knownLabel;
|
||||
} else {
|
||||
entries[i] = handler.getContext().getString(R.string.menuitem_unknown_app, possibleValue);
|
||||
}
|
||||
entries[i] = Objects.requireNonNullElseGet(
|
||||
knownLabel,
|
||||
() -> handler.getContext().getString(R.string.menuitem_unknown_app, possibleValue)
|
||||
);
|
||||
values[i] = possibleValue;
|
||||
}
|
||||
|
||||
@@ -194,7 +195,7 @@ public final class DeviceSettingsUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static void sortListPreference(final ListPreference listPreference) {
|
||||
public static void sortListPreference(final ListPreference listPreference, final boolean keepFirst) {
|
||||
final CharSequence[] entries = listPreference.getEntries();
|
||||
final CharSequence[] entryValues = listPreference.getEntryValues();
|
||||
|
||||
@@ -211,9 +212,8 @@ public final class DeviceSettingsUtils {
|
||||
combined[i][1] = entryValues[i].toString();
|
||||
}
|
||||
|
||||
// Sort, keeping "auto" at the top
|
||||
final boolean hasAuto = "auto".contentEquals(entryValues[0]);
|
||||
Arrays.sort(combined, hasAuto ? 1 : 0, length, Comparator.comparing(o -> o[0]));
|
||||
// Sort, keeping "the first" at the top
|
||||
Arrays.sort(combined, keepFirst ? 1 : 0, length, Comparator.comparing(o -> o[0]));
|
||||
|
||||
// Reassign sorted values
|
||||
for (int i = 0; i < length; i++) {
|
||||
|
||||
+5
-3
@@ -416,13 +416,15 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
|
||||
languageListPreference.setEntries(entries);
|
||||
languageListPreference.setEntryValues(values);
|
||||
}
|
||||
|
||||
DeviceSettingsUtils.sortListPreference(languageListPreference);
|
||||
DeviceSettingsUtils.sortListPreference(
|
||||
languageListPreference,
|
||||
supportedLanguages.length > 0 && "auto".equals(supportedLanguages[0])
|
||||
);
|
||||
}
|
||||
|
||||
final ListPreference transliterationPreference = findPreference(DeviceSettingsPreferenceConst.PREF_TRANSLITERATION_LANGUAGES);
|
||||
if (transliterationPreference != null) {
|
||||
DeviceSettingsUtils.sortListPreference(transliterationPreference);
|
||||
DeviceSettingsUtils.sortListPreference(transliterationPreference, false);
|
||||
}
|
||||
|
||||
String disconnectNotificationState = prefs.getString(PREF_DISCONNECT_NOTIFICATION, PREF_DO_NOT_DISTURB_OFF);
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.database.schema;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AlarmDao;
|
||||
|
||||
public class GadgetbridgeUpdate_112 implements DBUpdateScript {
|
||||
@Override
|
||||
public void upgradeSchema(SQLiteDatabase db) {
|
||||
if (!DBHelper.existsColumn(AlarmDao.TABLENAME, AlarmDao.Properties.SoundCode.columnName, db)) {
|
||||
String ADD_COLUMN_SOUND_CODE = "ALTER TABLE " + AlarmDao.TABLENAME + " ADD COLUMN "
|
||||
+ AlarmDao.Properties.SoundCode.columnName + " INTEGER NOT NULL DEFAULT 0;";
|
||||
db.execSQL(ADD_COLUMN_SOUND_CODE);
|
||||
}
|
||||
|
||||
if (!DBHelper.existsColumn(AlarmDao.TABLENAME, AlarmDao.Properties.Backlight.columnName, db)) {
|
||||
String ADD_COLUMN_SOUND_CODE = "ALTER TABLE " + AlarmDao.TABLENAME + " ADD COLUMN "
|
||||
+ AlarmDao.Properties.Backlight.columnName + " BOOLEAN NOT NULL DEFAULT TRUE;";
|
||||
db.execSQL(ADD_COLUMN_SOUND_CODE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downgradeSchema(SQLiteDatabase db) {
|
||||
}
|
||||
}
|
||||
+20
@@ -75,6 +75,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
@@ -725,6 +726,25 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean supportsAlarmSounds(@NonNull GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmBacklight(@NonNull GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmTitlePresets(@NonNull GBDevice device) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Alarm.ALARM_LABEL> getAlarmTitlePresets(@NonNull GBDevice device) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMusicInfo(@NonNull GBDevice device) {
|
||||
return false;
|
||||
|
||||
@@ -52,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.AbstractNotificationPattern;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySummaryParser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BodyEnergySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
@@ -544,6 +545,11 @@ public interface DeviceCoordinator {
|
||||
*/
|
||||
boolean supportsAlarmDescription(@NonNull GBDevice device);
|
||||
|
||||
boolean supportsAlarmSounds(@NonNull GBDevice device);
|
||||
boolean supportsAlarmBacklight(@NonNull GBDevice device);
|
||||
boolean supportsAlarmTitlePresets(@NonNull GBDevice device);
|
||||
List<Alarm.ALARM_LABEL> getAlarmTitlePresets(@NonNull GBDevice device);
|
||||
|
||||
/**
|
||||
* Returns true if the given device supports heart rate measurements.
|
||||
* @return
|
||||
|
||||
+40
@@ -2,9 +2,14 @@ package nodomain.freeyourgadget.gadgetbridge.devices.garmin.watches;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.garmin.GarminCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
|
||||
public abstract class GarminWatchCoordinator extends GarminCoordinator {
|
||||
@Override
|
||||
@@ -12,6 +17,41 @@ public abstract class GarminWatchCoordinator extends GarminCoordinator {
|
||||
return R.drawable.ic_device_zetime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlarmSlotCount(final GBDevice device) {
|
||||
return supports(device, GarminCapability.REALTIME_SETTINGS) ? 0 : 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmSounds(@NonNull final GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmBacklight(@NonNull final GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsAlarmTitlePresets(@NonNull final GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Alarm.ALARM_LABEL> getAlarmTitlePresets(@NonNull final GBDevice device) {
|
||||
return Arrays.asList(
|
||||
Alarm.ALARM_LABEL.NONE,
|
||||
Alarm.ALARM_LABEL.WAKE_UP,
|
||||
Alarm.ALARM_LABEL.WORKOUT,
|
||||
Alarm.ALARM_LABEL.REMINDER,
|
||||
Alarm.ALARM_LABEL.APPOINTMENT,
|
||||
Alarm.ALARM_LABEL.TRAINING,
|
||||
Alarm.ALARM_LABEL.CLASS,
|
||||
Alarm.ALARM_LABEL.MEDITATE,
|
||||
Alarm.ALARM_LABEL.BEDTIME
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents(final GBDevice device) {
|
||||
return true;
|
||||
|
||||
+1
-1
@@ -255,7 +255,7 @@ public class ZeppOsSettingsCustomizer extends HuamiSettingsCustomizer {
|
||||
// Since we populate the values dynamically, we need to sort it here
|
||||
final Preference languagePref = handler.findPreference("language");
|
||||
if (languagePref != null) {
|
||||
DeviceSettingsUtils.sortListPreference((ListPreference) languagePref);
|
||||
DeviceSettingsUtils.sortListPreference((ListPreference) languagePref, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,12 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public interface Alarm extends Serializable {
|
||||
/**
|
||||
* The {@link android.os.Bundle} name for transferring parceled alarms.
|
||||
@@ -36,6 +40,51 @@ public interface Alarm extends Serializable {
|
||||
|
||||
byte ALARM_DAILY = Alarm.ALARM_MON | Alarm.ALARM_TUE | Alarm.ALARM_WED | Alarm.ALARM_THU | Alarm.ALARM_FRI | Alarm.ALARM_SAT | Alarm.ALARM_SUN;
|
||||
|
||||
enum ALARM_SOUND {
|
||||
UNSET(R.string.unset),
|
||||
OFF(R.string.off),
|
||||
TONE(R.string.pref_header_sound),
|
||||
VIBRATION(R.string.title_activity_vibration),
|
||||
TONE_AND_VIBRATION(R.string.sound_and_vibration),
|
||||
;
|
||||
|
||||
@StringRes
|
||||
private final int label;
|
||||
|
||||
ALARM_SOUND(final int label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public int getLabel() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
/// Enum name will be persisted as the title
|
||||
enum ALARM_LABEL {
|
||||
NONE(R.string.none),
|
||||
WAKE_UP(R.string.wake_up_time),
|
||||
WORKOUT(R.string.pref_header_workout),
|
||||
REMINDER(R.string.reminder),
|
||||
APPOINTMENT(R.string.alarm_label_appointment),
|
||||
TRAINING(R.string.alarm_label_training),
|
||||
CLASS(R.string.alarm_label_class),
|
||||
MEDITATE(R.string.alarm_label_meditate),
|
||||
BEDTIME(R.string.bedtime),
|
||||
;
|
||||
|
||||
@StringRes
|
||||
private final int label;
|
||||
|
||||
ALARM_LABEL(final int label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public int getLabel() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
int getPosition();
|
||||
|
||||
boolean getEnabled();
|
||||
@@ -61,4 +110,8 @@ public interface Alarm extends Serializable {
|
||||
String getTitle();
|
||||
|
||||
String getDescription();
|
||||
|
||||
int getSoundCode();
|
||||
|
||||
boolean getBacklight();
|
||||
}
|
||||
+10
@@ -237,7 +237,9 @@ public CreateFileMessage initiateUpload(byte[] fileAsByteArray, FileType.FILETYP
|
||||
private UploadRequestMessage setCreateFileStatusMessage(CreateFileStatusMessage createFileStatusMessage) {
|
||||
if (createFileStatusMessage.canProceed()) {
|
||||
LOG.info("SENDING UPLOAD FILE");
|
||||
if (currentlyUploading.directoryEntry.filetype != FileType.FILETYPE.SETTINGS) {
|
||||
updateUploadProgress(0);
|
||||
}
|
||||
return new UploadRequestMessage(createFileStatusMessage.getFileIndex(), currentlyUploading.getDataSize());
|
||||
} else {
|
||||
LOG.warn("Cannot proceed with upload");
|
||||
@@ -255,23 +257,31 @@ public CreateFileMessage initiateUpload(byte[] fileAsByteArray, FileType.FILETYP
|
||||
return currentlyUploading.take();
|
||||
} else {
|
||||
LOG.warn("Cannot proceed with upload");
|
||||
if (currentlyUploading.directoryEntry.filetype != FileType.FILETYPE.SETTINGS) {
|
||||
updateUploadProgress(-1);
|
||||
}
|
||||
this.currentlyUploading = null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private GFDIMessage processUploadProgress(FileTransferDataStatusMessage fileTransferDataStatusMessage) {
|
||||
final boolean showNotification = currentlyUploading.directoryEntry.filetype != FileType.FILETYPE.SETTINGS;
|
||||
|
||||
if (currentlyUploading.getDataSize() <= fileTransferDataStatusMessage.getDataOffset()) {
|
||||
this.currentlyUploading = null;
|
||||
LOG.info("SENDING SYNC COMPLETE!!!");
|
||||
if (showNotification) {
|
||||
updateUploadProgress(100);
|
||||
}
|
||||
|
||||
return new SystemEventMessage(SystemEventMessage.GarminSystemEventType.SYNC_COMPLETE, 0);
|
||||
} else {
|
||||
if (fileTransferDataStatusMessage.canProceed()) {
|
||||
LOG.info("SENDING NEXT CHUNK!!!");
|
||||
if (showNotification) {
|
||||
updateUploadProgress((100 * currentlyUploading.dataHolder.position()) / currentlyUploading.dataHolder.limit());
|
||||
}
|
||||
if (fileTransferDataStatusMessage.getDataOffset() != currentlyUploading.dataHolder.position())
|
||||
throw new IllegalStateException("Received file transfer status with unaligned offset");
|
||||
return currentlyUploading.take();
|
||||
|
||||
+101
@@ -16,12 +16,14 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
@@ -53,6 +55,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
@@ -77,10 +80,15 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.SupportedFileTypesDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents.WeatherRequestDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitAsyncProcessor;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GpxRouteFileConverter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.PredefinedLocalMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionAlarmLabel;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitAlarmSettings;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitDeviceSettings;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitFileId;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.ConfigurationMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.DownloadRequestMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage;
|
||||
@@ -762,6 +770,99 @@ public class GarminSupport extends AbstractBTLESingleDeviceSupport implements IC
|
||||
sendOutgoingMessage("find device", findMyWatch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetAlarms(final ArrayList<? extends Alarm> alarms) {
|
||||
final int alarmSlotCount = getCoordinator().getAlarmSlotCount(getDevice());
|
||||
|
||||
final List<RecordData> dataRecords = new ArrayList<>(1 + alarmSlotCount);
|
||||
|
||||
final int currentTime = (int) (System.currentTimeMillis() / 1000);
|
||||
|
||||
dataRecords.add(new FitFileId.Builder()
|
||||
.setType(FileType.FILETYPE.SETTINGS)
|
||||
.setManufacturer(1) // garmin
|
||||
.setProduct(65534) // connect
|
||||
.setTimeCreated((long) currentTime)
|
||||
.setSerialNumber(1L)
|
||||
.setNumber(1)
|
||||
.build());
|
||||
|
||||
final List<Number> deviceSettingsTimes = new ArrayList<>();
|
||||
final List<Number> deviceSettingsUnk5 = new ArrayList<>();
|
||||
final List<Number> deviceSettingsEnabled = new ArrayList<>();
|
||||
final List<Number> deviceSettingsRepeat = new ArrayList<>();
|
||||
|
||||
int numberEnabledAlarms = 0;
|
||||
for (Alarm alarm : alarms) {
|
||||
if (alarm.getUnused()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int soundCode = switch (Alarm.ALARM_SOUND.values()[alarm.getSoundCode()]) {
|
||||
case OFF -> 0;
|
||||
case TONE -> 1;
|
||||
case VIBRATION -> 2;
|
||||
case UNSET, TONE_AND_VIBRATION -> 3;
|
||||
};
|
||||
final FieldDefinitionAlarmLabel.Label label;
|
||||
|
||||
final String alarmTitle = alarm.getTitle();
|
||||
if (StringUtils.isBlank(alarmTitle)) {
|
||||
label = FieldDefinitionAlarmLabel.Label.NONE;
|
||||
} else {
|
||||
FieldDefinitionAlarmLabel.Label alarmLabel;
|
||||
try {
|
||||
alarmLabel = FieldDefinitionAlarmLabel.Label.valueOf(alarmTitle);
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Invalid alarm label {}", alarmTitle, e);
|
||||
alarmLabel = FieldDefinitionAlarmLabel.Label.NONE;
|
||||
}
|
||||
label = alarmLabel;
|
||||
}
|
||||
|
||||
final long repetitionCode = alarm.getRepetition() != 0 ? alarm.getRepetition() : 128L;
|
||||
|
||||
final FitAlarmSettings.Builder alarmBuilder = new FitAlarmSettings.Builder()
|
||||
.setTime(LocalTime.of(alarm.getHour(), alarm.getMinute()))
|
||||
.setRepeat(repetitionCode)
|
||||
.setEnabled(alarm.getEnabled() ? 1 : 0)
|
||||
.setSound(soundCode)
|
||||
.setBacklight(alarm.getBacklight() ? 1 : 0)
|
||||
.setSomeTimestamp((long) currentTime)
|
||||
.setUnknown7(0)
|
||||
.setLabel(label)
|
||||
.setMessageIndex(numberEnabledAlarms);
|
||||
|
||||
dataRecords.add(alarmBuilder.build());
|
||||
|
||||
deviceSettingsTimes.add(alarm.getHour() * 60 + alarm.getMinute());
|
||||
deviceSettingsUnk5.add(5);
|
||||
deviceSettingsEnabled.add(alarm.getEnabled() ? 1 : 0);
|
||||
deviceSettingsRepeat.add(repetitionCode);
|
||||
|
||||
numberEnabledAlarms++;
|
||||
}
|
||||
|
||||
if (numberEnabledAlarms > 0) {
|
||||
final FitDeviceSettings.Builder deviceSettingsBuilder = new FitDeviceSettings.Builder()
|
||||
.setAlarmsTime(deviceSettingsTimes.toArray(new Number[0]))
|
||||
.setAlarmsUnk5(deviceSettingsUnk5.toArray(new Number[0]))
|
||||
.setAlarmsEnabled(deviceSettingsEnabled.toArray(new Number[0]))
|
||||
.setAlarmsRepeat(deviceSettingsRepeat.toArray(new Number[0]));
|
||||
|
||||
dataRecords.add(deviceSettingsBuilder.build());
|
||||
}
|
||||
|
||||
final FitFile fitFile = new FitFile(dataRecords);
|
||||
communicator.sendMessage(
|
||||
"set alarms",
|
||||
fileTransferHandler.initiateUpload(
|
||||
fitFile.getOutgoingMessage(),
|
||||
FileType.FILETYPE.SETTINGS
|
||||
).getOutgoingMessage()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
|
||||
sendOutgoingMessage("set canned messages", protocolBufferHandler.setCannedMessages(cannedMessagesSpec));
|
||||
|
||||
+24
-42
@@ -2,6 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionAlarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionAlarmLabel;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionArray;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionCoordinate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionDayOfWeek;
|
||||
@@ -26,48 +27,28 @@ public class FieldDefinitionFactory {
|
||||
if (null == field) {
|
||||
return new FieldDefinition(localNumber, size, baseType, name, scale, offset);
|
||||
}
|
||||
switch (field) {
|
||||
case ALARM:
|
||||
return new FieldDefinitionAlarm(localNumber, size, baseType, name);
|
||||
case ARRAY:
|
||||
return new FieldDefinitionArray(localNumber, size, baseType, name, scale, offset);
|
||||
case DAY_OF_WEEK:
|
||||
return new FieldDefinitionDayOfWeek(localNumber, size, baseType, name);
|
||||
case EXERCISE_CATEGORY:
|
||||
return new FieldDefinitionExerciseCategory(localNumber, size, baseType, name);
|
||||
case FILE_TYPE:
|
||||
return new FieldDefinitionFileType(localNumber, size, baseType, name);
|
||||
case GOAL_SOURCE:
|
||||
return new FieldDefinitionGoalSource(localNumber, size, baseType, name);
|
||||
case GOAL_TYPE:
|
||||
return new FieldDefinitionGoalType(localNumber, size, baseType, name);
|
||||
case HRV_STATUS:
|
||||
return new FieldDefinitionHrvStatus(localNumber, size, baseType, name);
|
||||
case HR_TIME_IN_ZONE:
|
||||
return new FieldDefinitionHrTimeInZone(localNumber, size, baseType, name);
|
||||
case HR_ZONE_HIGH_BOUNDARY:
|
||||
return new FieldDefinitionHrZoneHighBoundary(localNumber, size, baseType, name);
|
||||
case MEASUREMENT_SYSTEM:
|
||||
return new FieldDefinitionMeasurementSystem(localNumber, size, baseType, name);
|
||||
case TEMPERATURE:
|
||||
return new FieldDefinitionTemperature(localNumber, size, baseType, name);
|
||||
case TIMESTAMP:
|
||||
return new FieldDefinitionTimestamp(localNumber, size, baseType, name);
|
||||
case WEATHER_CONDITION:
|
||||
return new FieldDefinitionWeatherCondition(localNumber, size, baseType, name);
|
||||
case LANGUAGE:
|
||||
return new FieldDefinitionLanguage(localNumber, size, baseType, name);
|
||||
case SLEEP_STAGE:
|
||||
return new FieldDefinitionSleepStage(localNumber, size, baseType, name);
|
||||
case WEATHER_AQI:
|
||||
return new FieldDefinitionWeatherAqi(localNumber, size, baseType, name);
|
||||
case COORDINATE:
|
||||
return new FieldDefinitionCoordinate(localNumber, size, baseType, name);
|
||||
case SWIM_STYLE:
|
||||
return new FieldDefinitionSwimStyle(localNumber, size, baseType, name);
|
||||
default:
|
||||
return new FieldDefinition(localNumber, size, baseType, name);
|
||||
}
|
||||
return switch (field) {
|
||||
case ALARM -> new FieldDefinitionAlarm(localNumber, size, baseType, name);
|
||||
case ARRAY -> new FieldDefinitionArray(localNumber, size, baseType, name, scale, offset);
|
||||
case DAY_OF_WEEK -> new FieldDefinitionDayOfWeek(localNumber, size, baseType, name);
|
||||
case EXERCISE_CATEGORY -> new FieldDefinitionExerciseCategory(localNumber, size, baseType, name);
|
||||
case ALARM_LABEL -> new FieldDefinitionAlarmLabel(localNumber, size, baseType, name);
|
||||
case FILE_TYPE -> new FieldDefinitionFileType(localNumber, size, baseType, name);
|
||||
case GOAL_SOURCE -> new FieldDefinitionGoalSource(localNumber, size, baseType, name);
|
||||
case GOAL_TYPE -> new FieldDefinitionGoalType(localNumber, size, baseType, name);
|
||||
case HRV_STATUS -> new FieldDefinitionHrvStatus(localNumber, size, baseType, name);
|
||||
case HR_TIME_IN_ZONE -> new FieldDefinitionHrTimeInZone(localNumber, size, baseType, name);
|
||||
case HR_ZONE_HIGH_BOUNDARY -> new FieldDefinitionHrZoneHighBoundary(localNumber, size, baseType, name);
|
||||
case MEASUREMENT_SYSTEM -> new FieldDefinitionMeasurementSystem(localNumber, size, baseType, name);
|
||||
case TEMPERATURE -> new FieldDefinitionTemperature(localNumber, size, baseType, name);
|
||||
case TIMESTAMP -> new FieldDefinitionTimestamp(localNumber, size, baseType, name);
|
||||
case WEATHER_CONDITION -> new FieldDefinitionWeatherCondition(localNumber, size, baseType, name);
|
||||
case LANGUAGE -> new FieldDefinitionLanguage(localNumber, size, baseType, name);
|
||||
case SLEEP_STAGE -> new FieldDefinitionSleepStage(localNumber, size, baseType, name);
|
||||
case WEATHER_AQI -> new FieldDefinitionWeatherAqi(localNumber, size, baseType, name);
|
||||
case COORDINATE -> new FieldDefinitionCoordinate(localNumber, size, baseType, name);
|
||||
case SWIM_STYLE -> new FieldDefinitionSwimStyle(localNumber, size, baseType, name);
|
||||
};
|
||||
}
|
||||
|
||||
public enum FIELD {
|
||||
@@ -75,6 +56,7 @@ public class FieldDefinitionFactory {
|
||||
ARRAY,
|
||||
DAY_OF_WEEK,
|
||||
EXERCISE_CATEGORY,
|
||||
ALARM_LABEL,
|
||||
FILE_TYPE,
|
||||
GOAL_SOURCE,
|
||||
GOAL_TYPE,
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ public class FitRecordDataBuilder {
|
||||
new RecordHeader((byte) 0x40),
|
||||
ByteOrder.BIG_ENDIAN,
|
||||
globalMessage,
|
||||
values.keySet().stream().map(globalMessage::getFieldDefinition).collect(Collectors.toList()),
|
||||
values.entrySet().stream().map(e -> globalMessage.getFieldDefinition(e.getKey(), e.getValue().length)).collect(Collectors.toList()),
|
||||
null
|
||||
),
|
||||
new RecordHeader((byte) 0x00)
|
||||
|
||||
+9
-5
@@ -31,7 +31,10 @@ public class GlobalFITMessage {
|
||||
new FieldDefinitionPrimitive(2, BaseType.UINT32, "time_offset"),
|
||||
new FieldDefinitionPrimitive(4, BaseType.ENUM, "time_mode"),
|
||||
new FieldDefinitionPrimitive(5, BaseType.SINT8, "time_zone_offset"),
|
||||
new FieldDefinitionPrimitive(8, BaseType.UINT16, "alarms_time", FieldDefinitionFactory.FIELD.ARRAY),
|
||||
new FieldDefinitionPrimitive(9, BaseType.ENUM, "alarms_unk5", FieldDefinitionFactory.FIELD.ARRAY), // [5, 5]
|
||||
new FieldDefinitionPrimitive(12, BaseType.ENUM, "backlight_mode"),
|
||||
new FieldDefinitionPrimitive(28, BaseType.ENUM, "alarms_enabled", FieldDefinitionFactory.FIELD.ARRAY), // [1,1]
|
||||
new FieldDefinitionPrimitive(36, BaseType.ENUM, "activity_tracker_enabled"),
|
||||
new FieldDefinitionPrimitive(46, BaseType.ENUM, "move_alert_enabled"),
|
||||
new FieldDefinitionPrimitive(47, BaseType.ENUM, "date_mode"),
|
||||
@@ -41,7 +44,8 @@ public class GlobalFITMessage {
|
||||
new FieldDefinitionPrimitive(58, BaseType.UINT16, "autosync_min_steps"),
|
||||
new FieldDefinitionPrimitive(59, BaseType.UINT16, "autosync_min_time"),
|
||||
new FieldDefinitionPrimitive(86, BaseType.ENUM, "ble_auto_upload_enabled"),
|
||||
new FieldDefinitionPrimitive(90, BaseType.UINT32, "auto_activity_detect")
|
||||
new FieldDefinitionPrimitive(90, BaseType.UINT32, "auto_activity_detect"),
|
||||
new FieldDefinitionPrimitive(92, BaseType.UINT32Z, "alarms_repeat", FieldDefinitionFactory.FIELD.ARRAY)
|
||||
));
|
||||
|
||||
public static GlobalFITMessage USER_PROFILE = new GlobalFITMessage(3, "USER_PROFILE", Arrays.asList(
|
||||
@@ -404,10 +408,10 @@ public class GlobalFITMessage {
|
||||
new FieldDefinitionPrimitive(1, BaseType.UINT32Z, "repeat"), // 31 weekday 96 weekend 126 all except mon 127 daily 128 once
|
||||
new FieldDefinitionPrimitive(2, BaseType.ENUM, "enabled"), // 0/1
|
||||
new FieldDefinitionPrimitive(3, BaseType.ENUM, "sound"), // 0 none 1 sound 2 vibrate 3 sound+vibrate
|
||||
new FieldDefinitionPrimitive(4, BaseType.ENUM, "unknown4"), // 1
|
||||
new FieldDefinitionPrimitive(4, BaseType.ENUM, "backlight"), // 1
|
||||
new FieldDefinitionPrimitive(5, BaseType.UINT32, "some_timestamp", FieldDefinitionFactory.FIELD.TIMESTAMP),
|
||||
new FieldDefinitionPrimitive(7, BaseType.UINT8, "unknown7"), // 0
|
||||
new FieldDefinitionPrimitive(8, BaseType.ENUM, "label"), // 0 none 2 workout 3 reminder 4 appointment 6 class 7 meditate 8 bedtime
|
||||
new FieldDefinitionPrimitive(8, BaseType.ENUM, "label", FieldDefinitionFactory.FIELD.ALARM_LABEL), // 0 none 2 workout 3 reminder 4 appointment 6 class 7 meditate 8 bedtime
|
||||
new FieldDefinitionPrimitive(254, BaseType.UINT16, "message_index")
|
||||
));
|
||||
|
||||
@@ -684,13 +688,13 @@ public class GlobalFITMessage {
|
||||
return subset;
|
||||
}
|
||||
|
||||
public FieldDefinition getFieldDefinition(final String name) {
|
||||
public FieldDefinition getFieldDefinition(final String name, final int count) {
|
||||
for (FieldDefinitionPrimitive fieldDefinitionPrimitive :
|
||||
fieldDefinitionPrimitives) {
|
||||
if (name.equals(fieldDefinitionPrimitive.name)) {
|
||||
return FieldDefinitionFactory.create(
|
||||
fieldDefinitionPrimitive.number,
|
||||
fieldDefinitionPrimitive.size,
|
||||
fieldDefinitionPrimitive.size * count,
|
||||
fieldDefinitionPrimitive.type,
|
||||
fieldDefinitionPrimitive.baseType,
|
||||
fieldDefinitionPrimitive.name,
|
||||
|
||||
+4
-2
@@ -6,6 +6,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@@ -179,7 +180,8 @@ public class RecordData {
|
||||
tsb.append(new Date(getComputedTimestamp() * 1000L));
|
||||
}
|
||||
|
||||
for (FieldData fieldData : fieldDataList) {
|
||||
fieldDataList.stream().sorted(Comparator.comparingInt(FieldData::getNumber))
|
||||
.forEach(fieldData -> {
|
||||
final String fieldName;
|
||||
if (!StringUtils.isBlank(fieldData.getName())) {
|
||||
fieldName = fieldData.getName();
|
||||
@@ -196,7 +198,7 @@ public class RecordData {
|
||||
fieldValueString = o.toString();
|
||||
}
|
||||
tsb.append(fieldName, fieldValueString);
|
||||
}
|
||||
});
|
||||
return tsb.build();
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -27,6 +27,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.GlobalFIT
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionAlarmLabel;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionExerciseCategory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalSource;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionGoalType;
|
||||
@@ -273,6 +274,7 @@ public class FitCodeGen {
|
||||
case ARRAY -> Number[].class;
|
||||
case DAY_OF_WEEK -> DayOfWeek.class;
|
||||
case EXERCISE_CATEGORY -> FieldDefinitionExerciseCategory.ExerciseCategory[].class;
|
||||
case ALARM_LABEL -> FieldDefinitionAlarmLabel.Label.class;
|
||||
case FILE_TYPE -> FileType.FILETYPE.class;
|
||||
case GOAL_SOURCE -> FieldDefinitionGoalSource.Source.class;
|
||||
case GOAL_TYPE -> FieldDefinitionGoalType.Type.class;
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FieldDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.baseTypes.BaseType;
|
||||
|
||||
public class FieldDefinitionAlarmLabel extends FieldDefinition {
|
||||
|
||||
public FieldDefinitionAlarmLabel(final int localNumber, final int size, final BaseType baseType, final String name) {
|
||||
super(localNumber, size, baseType, name, 1, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object decode(final ByteBuffer byteBuffer) {
|
||||
final Object rawObj = baseType.decode(byteBuffer, scale, offset);
|
||||
if (rawObj != null) {
|
||||
final int raw = (int) rawObj;
|
||||
return Label.fromId(raw);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(final ByteBuffer byteBuffer, final Object o) {
|
||||
if (o instanceof Label) {
|
||||
baseType.encode(byteBuffer, (((Label) o).getId()), scale, offset);
|
||||
return;
|
||||
}
|
||||
baseType.encode(byteBuffer, o, scale, offset);
|
||||
}
|
||||
|
||||
public enum Label {
|
||||
NONE(0),
|
||||
WAKE_UP(1),
|
||||
WORKOUT(2),
|
||||
REMINDER(3),
|
||||
APPOINTMENT(4),
|
||||
TRAINING(5),
|
||||
CLASS(6),
|
||||
MEDITATE(7),
|
||||
BEDTIME(8),
|
||||
;
|
||||
|
||||
private final int id;
|
||||
|
||||
Label(final int i) {
|
||||
id = i;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Label fromId(final int id) {
|
||||
for (Label label : Label.values()) {
|
||||
if (id == label.getId()) {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -8,6 +8,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitRecord
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordData;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDefinition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordHeader;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.fieldDefinitions.FieldDefinitionAlarmLabel.Label;
|
||||
|
||||
//
|
||||
// WARNING: This class was auto-generated, please avoid modifying it directly.
|
||||
@@ -44,7 +45,7 @@ public class FitAlarmSettings extends RecordData {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getUnknown4() {
|
||||
public Integer getBacklight() {
|
||||
return (Integer) getFieldByNumber(4);
|
||||
}
|
||||
|
||||
@@ -59,8 +60,8 @@ public class FitAlarmSettings extends RecordData {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getLabel() {
|
||||
return (Integer) getFieldByNumber(8);
|
||||
public Label getLabel() {
|
||||
return (Label) getFieldByNumber(8);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -93,7 +94,7 @@ public class FitAlarmSettings extends RecordData {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUnknown4(final Integer value) {
|
||||
public Builder setBacklight(final Integer value) {
|
||||
setFieldByNumber(4, value);
|
||||
return this;
|
||||
}
|
||||
@@ -108,7 +109,7 @@ public class FitAlarmSettings extends RecordData {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLabel(final Integer value) {
|
||||
public Builder setLabel(final Label value) {
|
||||
setFieldByNumber(8, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
+68
@@ -46,11 +46,47 @@ public class FitDeviceSettings extends RecordData {
|
||||
return (Integer) getFieldByNumber(5);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Number[] getAlarmsTime() {
|
||||
final Object[] objectsArray = (Object[]) getFieldByNumber(8);
|
||||
if (objectsArray == null)
|
||||
return null;
|
||||
final Number[] ret = new Number[objectsArray.length];
|
||||
for (int i = 0; i < objectsArray.length; i++) {
|
||||
ret[i] = (Number) objectsArray[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Number[] getAlarmsUnk5() {
|
||||
final Object[] objectsArray = (Object[]) getFieldByNumber(9);
|
||||
if (objectsArray == null)
|
||||
return null;
|
||||
final Number[] ret = new Number[objectsArray.length];
|
||||
for (int i = 0; i < objectsArray.length; i++) {
|
||||
ret[i] = (Number) objectsArray[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getBacklightMode() {
|
||||
return (Integer) getFieldByNumber(12);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Number[] getAlarmsEnabled() {
|
||||
final Object[] objectsArray = (Object[]) getFieldByNumber(28);
|
||||
if (objectsArray == null)
|
||||
return null;
|
||||
final Number[] ret = new Number[objectsArray.length];
|
||||
for (int i = 0; i < objectsArray.length; i++) {
|
||||
ret[i] = (Number) objectsArray[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getActivityTrackerEnabled() {
|
||||
return (Integer) getFieldByNumber(36);
|
||||
@@ -101,6 +137,18 @@ public class FitDeviceSettings extends RecordData {
|
||||
return (Long) getFieldByNumber(90);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Number[] getAlarmsRepeat() {
|
||||
final Object[] objectsArray = (Object[]) getFieldByNumber(92);
|
||||
if (objectsArray == null)
|
||||
return null;
|
||||
final Number[] ret = new Number[objectsArray.length];
|
||||
for (int i = 0; i < objectsArray.length; i++) {
|
||||
ret[i] = (Number) objectsArray[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static class Builder extends FitRecordDataBuilder {
|
||||
public Builder() {
|
||||
super(2);
|
||||
@@ -131,11 +179,26 @@ public class FitDeviceSettings extends RecordData {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAlarmsTime(final Number[] value) {
|
||||
setFieldByNumber(8, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAlarmsUnk5(final Number[] value) {
|
||||
setFieldByNumber(9, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setBacklightMode(final Integer value) {
|
||||
setFieldByNumber(12, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAlarmsEnabled(final Number[] value) {
|
||||
setFieldByNumber(28, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setActivityTrackerEnabled(final Integer value) {
|
||||
setFieldByNumber(36, value);
|
||||
return this;
|
||||
@@ -186,6 +249,11 @@ public class FitDeviceSettings extends RecordData {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAlarmsRepeat(final Number[] value) {
|
||||
setFieldByNumber(92, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FitDeviceSettings build() {
|
||||
return (FitDeviceSettings) super.build();
|
||||
|
||||
+1
-1
@@ -1004,7 +1004,7 @@ public class HuaweiSupportProvider {
|
||||
title = context.getString(R.string.alarm_smart_wakeup);
|
||||
description = context.getString(R.string.huawei_alarm_smart_description);
|
||||
}
|
||||
return new Alarm(device.getId(), user.getId(), position, false, smartWakeup, null, false, 0, 6, 30, true, title, description);
|
||||
return new Alarm(device.getId(), user.getId(), position, false, smartWakeup, null, false, 0, 6, 30, true, title, description, 0, true);
|
||||
}
|
||||
|
||||
private void getAlarms() {
|
||||
|
||||
+6
-2
@@ -78,7 +78,9 @@ public class GetEventAlarmList extends Request {
|
||||
eventAlarm.startMinute,
|
||||
false,
|
||||
eventAlarm.name,
|
||||
""
|
||||
"",
|
||||
0,
|
||||
true
|
||||
));
|
||||
usedBitmap |= 1 << eventAlarm.index;
|
||||
}
|
||||
@@ -99,7 +101,9 @@ public class GetEventAlarmList extends Request {
|
||||
0,
|
||||
true,
|
||||
"",
|
||||
""
|
||||
"",
|
||||
0,
|
||||
true
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -69,7 +69,9 @@ public class GetSmartAlarmList extends Request {
|
||||
smartAlarm.startMinute,
|
||||
false,
|
||||
"Smart alarm",
|
||||
""
|
||||
"",
|
||||
0,
|
||||
true
|
||||
)
|
||||
});
|
||||
} else {
|
||||
@@ -88,7 +90,9 @@ public class GetSmartAlarmList extends Request {
|
||||
0,
|
||||
true,
|
||||
"Smart alarm",
|
||||
""
|
||||
"",
|
||||
0,
|
||||
true
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class AlarmUtils {
|
||||
*/
|
||||
public static nodomain.freeyourgadget.gadgetbridge.model.Alarm createSingleShot(int index, boolean smartWakeup, boolean snooze, Calendar calendar) {
|
||||
// TODO: add interval setting?
|
||||
return new Alarm(-1, -1, index, true, smartWakeup, null, snooze, Alarm.ALARM_ONCE, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), false, GBApplication.getContext().getString(R.string.quick_alarm), GBApplication.getContext().getString(R.string.quick_alarm_description));
|
||||
return new Alarm(-1, -1, index, true, smartWakeup, null, snooze, Alarm.ALARM_ONCE, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), false, GBApplication.getContext().getString(R.string.quick_alarm), GBApplication.getContext().getString(R.string.quick_alarm_description), 0, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +80,7 @@ public class AlarmUtils {
|
||||
public static Alarm createDefaultAlarm(DaoSession daoSession, GBDevice gbDevice, int position) {
|
||||
Device device = DBHelper.getDevice(gbDevice, daoSession);
|
||||
User user = DBHelper.getUser(daoSession);
|
||||
return new Alarm(device.getId(), user.getId(), position, false, false, null, false, 0, 6, 30, false, null, null);
|
||||
return new Alarm(device.getId(), user.getId(), position, false, false, null, false, 0, 6, 30, false, null, null, 0, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +160,7 @@ public class AlarmUtils {
|
||||
int hour = Integer.parseInt(tokens[4]);
|
||||
int minute = Integer.parseInt(tokens[5]);
|
||||
|
||||
return new Alarm(device.getId(), user.getId(), index, enabled, smartWakeup, null, false, repetition, hour, minute, false, null, null);
|
||||
return new Alarm(device.getId(), user.getId(), index, enabled, smartWakeup, null, false, repetition, hour, minute, false, null, null, 0, true);
|
||||
}
|
||||
|
||||
private static Comparator<Alarm> createComparator() {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.AlarmDetails">
|
||||
|
||||
@@ -23,8 +24,25 @@
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/preset_label_layout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:hint="@string/world_clock_label"
|
||||
app:endIconMode="dropdown_menu">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/preset_label_spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:focusable="false" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/alarm_title"
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
@@ -33,7 +51,7 @@
|
||||
android:maxLength="32" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/alarm_description"
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
@@ -44,19 +62,46 @@
|
||||
android:singleLine="false" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_snooze"
|
||||
android:id="@+id/cb_snooze"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_snooze"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_smart_wakeup"
|
||||
android:layout_width="wrap_content"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/sound_mode_layout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:hint="@string/pref_header_sound_vibration"
|
||||
app:endIconMode="dropdown_menu">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/sound_mode_spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:focusable="false" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/cb_backlight"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/watchface_setting_button_toggle_backlight"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/cb_smart_wakeup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
@@ -64,7 +109,7 @@
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/alarm_cb_smart_wakeup_interval"
|
||||
android:id="@+id/cb_smart_wakeup_interval"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
@@ -72,7 +117,7 @@
|
||||
android:hint="@string/alarm_smart_wakeup_interval_default" />
|
||||
|
||||
<TimePicker
|
||||
android:id="@+id/alarm_time_picker"
|
||||
android:id="@+id/time_picker"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
@@ -87,7 +132,7 @@
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_monday"
|
||||
android:id="@+id/cb_monday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
@@ -95,10 +140,11 @@
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_mon_short"
|
||||
android:textAlignment="gravity"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_tuesday"
|
||||
android:id="@+id/cb_tuesday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
@@ -106,10 +152,11 @@
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_tue_short"
|
||||
android:textAlignment="gravity"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_wednesday"
|
||||
android:id="@+id/cb_wednesday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
@@ -117,10 +164,11 @@
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_wed_short"
|
||||
android:textAlignment="gravity"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_thursday"
|
||||
android:id="@+id/cb_thursday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
@@ -128,10 +176,11 @@
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_thu_short"
|
||||
android:textAlignment="gravity"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_friday"
|
||||
android:id="@+id/cb_friday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
@@ -139,10 +188,11 @@
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_fri_short"
|
||||
android:textAlignment="gravity"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_saturday"
|
||||
android:id="@+id/cb_saturday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
@@ -150,10 +200,11 @@
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_sat_short"
|
||||
android:textAlignment="gravity"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_sunday"
|
||||
android:id="@+id/cb_sunday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
@@ -161,6 +212,7 @@
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_sun_short"
|
||||
android:textAlignment="gravity"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -216,6 +216,7 @@
|
||||
<string name="pref_header_generic">Generic</string>
|
||||
<string name="pref_header_health">Health</string>
|
||||
<string name="pref_header_sound_vibration">Sound & Vibration</string>
|
||||
<string name="sound_and_vibration">Sound + Vibration</string>
|
||||
<string name="pref_header_offline_voice">Offline Voice</string>
|
||||
<string name="pref_header_sound">Sound</string>
|
||||
<string name="pref_header_time">Time</string>
|
||||
@@ -3407,6 +3408,7 @@
|
||||
<string name="error_deleting_device">Error deleting device: %s</string>
|
||||
<string name="controlcenter_folder_name">Folder name:</string>
|
||||
<string name="controlcenter_add_new_folder">Add new folder</string>
|
||||
<string name="unset">Unset</string>
|
||||
<string name="controlcenter_unset_folder">Unset folder</string>
|
||||
<string name="controlcenter_set_folder_title">Set or create new folder</string>
|
||||
<string name="auto_reconnect_ble_title">Auto reconnect to device</string>
|
||||
@@ -3660,6 +3662,11 @@
|
||||
<string name="menuitem_alerts">Alerts</string>
|
||||
<string name="menuitem_focus">Focus</string>
|
||||
<string name="bedtime">Bedtime</string>
|
||||
<string name="reminder">Reminder</string>
|
||||
<string name="alarm_label_appointment">Appointment</string>
|
||||
<string name="alarm_label_training">Training</string>
|
||||
<string name="alarm_label_class">Class</string>
|
||||
<string name="alarm_label_meditate">Meditate</string>
|
||||
<string name="wake_up_time">Wake Up</string>
|
||||
<string name="pref_sleep_mode_schedule_title">Sleep Mode Schedule</string>
|
||||
<string name="pref_sleep_mode_schedule_summary">Send a reminder and enter sleep mode at bedtime. At the scheduled wake-up time, the wake-up alarm will sound.</string>
|
||||
|
||||
Reference in New Issue
Block a user