mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-27 12:26:48 +01:00
Mi Band 5: Add support for World Clocks
This commit is contained in:
parent
d973f50560
commit
5c6edea233
@ -43,7 +43,7 @@ public class GBDaoGenerator {
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
final Schema schema = new Schema(37, MAIN_PACKAGE + ".entities");
|
||||
final Schema schema = new Schema(38, MAIN_PACKAGE + ".entities");
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
@ -87,6 +87,7 @@ public class GBDaoGenerator {
|
||||
addCalendarSyncState(schema, device);
|
||||
addAlarms(schema, user, device);
|
||||
addReminders(schema, user, device);
|
||||
addWorldClocks(schema, user, device);
|
||||
|
||||
Entity notificationFilter = addNotificationFilters(schema);
|
||||
|
||||
@ -561,6 +562,24 @@ public class GBDaoGenerator {
|
||||
reminder.addToOne(device, deviceId);
|
||||
}
|
||||
|
||||
private static void addWorldClocks(Schema schema, Entity user, Entity device) {
|
||||
Entity worldClock = addEntity(schema, "WorldClock");
|
||||
worldClock.implementsInterface("nodomain.freeyourgadget.gadgetbridge.model.WorldClock");
|
||||
Property deviceId = worldClock.addLongProperty("deviceId").notNull().getProperty();
|
||||
Property userId = worldClock.addLongProperty("userId").notNull().getProperty();
|
||||
Property worldClockId = worldClock.addStringProperty("worldClockId").notNull().primaryKey().getProperty();
|
||||
Index indexUnique = new Index();
|
||||
indexUnique.addProperty(deviceId);
|
||||
indexUnique.addProperty(userId);
|
||||
indexUnique.addProperty(worldClockId);
|
||||
indexUnique.makeUnique();
|
||||
worldClock.addIndex(indexUnique);
|
||||
worldClock.addStringProperty("label").notNull();
|
||||
worldClock.addStringProperty("timeZoneId").notNull();
|
||||
worldClock.addToOne(user, userId);
|
||||
worldClock.addToOne(device, deviceId);
|
||||
}
|
||||
|
||||
private static void addNotificationFilterEntry(Schema schema, Entity notificationFilterEntity) {
|
||||
Entity notificatonFilterEntry = addEntity(schema, "NotificationFilterEntry");
|
||||
notificatonFilterEntry.addIdProperty().autoincrement();
|
||||
|
@ -238,6 +238,10 @@ dependencies {
|
||||
implementation 'com.google.protobuf:protobuf-lite:3.0.1'
|
||||
implementation "androidx.multidex:multidex:2.0.1"
|
||||
implementation 'com.android.volley:volley:1.2.1'
|
||||
|
||||
// JSR-310 timezones backport for Android, since we're still on java 7
|
||||
implementation 'com.jakewharton.threetenabp:threetenabp:1.4.0'
|
||||
testImplementation 'org.threeten:threetenbp:1.6.0'
|
||||
}
|
||||
|
||||
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
||||
|
@ -498,6 +498,10 @@
|
||||
android:name=".activities.ConfigureReminders"
|
||||
android:label="@string/title_activity_set_reminders"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.ConfigureWorldClocks"
|
||||
android:label="@string/pref_world_clocks_title"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.devicesettings.DeviceSettingsActivity"
|
||||
android:label="@string/title_activity_device_specific_settings"
|
||||
@ -513,6 +517,12 @@
|
||||
android:parentActivityName=".activities.ConfigureReminders"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".activities.WorldClockDetails"
|
||||
android:label="@string/title_activity_world_clock_details"
|
||||
android:parentActivityName=".activities.ConfigureWorldClocks"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name=".activities.VibrationActivity"
|
||||
android:label="@string/title_activity_vibration"
|
||||
|
@ -100,6 +100,8 @@ import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_ID_ERROR
|
||||
|
||||
import androidx.multidex.MultiDex;
|
||||
|
||||
import com.jakewharton.threetenabp.AndroidThreeTen;
|
||||
|
||||
/**
|
||||
* Main Application class that initializes and provides access to certain things like
|
||||
* logging and DB access.
|
||||
@ -191,6 +193,9 @@ public class GBApplication extends Application {
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the timezones library
|
||||
AndroidThreeTen.init(this);
|
||||
|
||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs = new Prefs(sharedPrefs);
|
||||
gbPrefs = new GBPrefs(prefs);
|
||||
|
@ -0,0 +1,218 @@
|
||||
/* Copyright (C) 2022 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.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
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.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.GBWorldClockListAdapter;
|
||||
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.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WorldClock;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
|
||||
public class ConfigureWorldClocks extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ConfigureWorldClocks.class);
|
||||
|
||||
private static final int REQ_CONFIGURE_WORLD_CLOCK = 1;
|
||||
|
||||
private GBWorldClockListAdapter mGBWorldClockListAdapter;
|
||||
private GBDevice gbDevice;
|
||||
|
||||
private BroadcastReceiver timeTickBroadcastReceiver;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_configure_world_clocks);
|
||||
|
||||
gbDevice = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
|
||||
mGBWorldClockListAdapter = new GBWorldClockListAdapter(this);
|
||||
|
||||
final RecyclerView worldClocksRecyclerView = findViewById(R.id.world_clock_list);
|
||||
worldClocksRecyclerView.setHasFixedSize(true);
|
||||
worldClocksRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
worldClocksRecyclerView.setAdapter(mGBWorldClockListAdapter);
|
||||
updateWorldClocksFromDB();
|
||||
|
||||
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);
|
||||
|
||||
int deviceSlots = coordinator.getWorldClocksSlotCount();
|
||||
|
||||
if (mGBWorldClockListAdapter.getItemCount() >= deviceSlots) {
|
||||
// No more free slots
|
||||
new AlertDialog.Builder(v.getContext())
|
||||
.setTitle(R.string.world_clock_no_free_slots_title)
|
||||
.setMessage(getBaseContext().getString(R.string.world_clock_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 WorldClock worldClock;
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
final Device device = DBHelper.getDevice(gbDevice, daoSession);
|
||||
final User user = DBHelper.getUser(daoSession);
|
||||
worldClock = createDefaultWorldClock(device, user);
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error accessing database", e);
|
||||
return;
|
||||
}
|
||||
|
||||
configureWorldClock(worldClock);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
timeTickBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(final Context context, Intent intent) {
|
||||
if (Intent.ACTION_TIME_TICK.equals(intent.getAction())) {
|
||||
// Refresh the UI, to update the current time in each timezone
|
||||
mGBWorldClockListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
registerReceiver(timeTickBroadcastReceiver, new IntentFilter(Intent.ACTION_TIME_TICK));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
if (timeTickBroadcastReceiver != null) {
|
||||
unregisterReceiver(timeTickBroadcastReceiver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// Refresh to update the current time on each clock
|
||||
mGBWorldClockListAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (requestCode == REQ_CONFIGURE_WORLD_CLOCK && resultCode == 1) {
|
||||
updateWorldClocksFromDB();
|
||||
sendWorldClocksToDevice();
|
||||
}
|
||||
}
|
||||
|
||||
private WorldClock createDefaultWorldClock(@NonNull Device device, @NonNull User user) {
|
||||
final WorldClock worldClock = new WorldClock();
|
||||
final String timezone = TimeZone.getDefault().getID();
|
||||
worldClock.setTimeZoneId(timezone);
|
||||
final String[] timezoneParts = timezone.split("/");
|
||||
worldClock.setLabel(timezoneParts[timezoneParts.length - 1]);
|
||||
|
||||
worldClock.setDeviceId(device.getId());
|
||||
worldClock.setUserId(user.getId());
|
||||
worldClock.setWorldClockId(UUID.randomUUID().toString());
|
||||
|
||||
return worldClock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the available worldClocks from the database and updates the view afterwards.
|
||||
*/
|
||||
private void updateWorldClocksFromDB() {
|
||||
final List<WorldClock> worldClocks = DBHelper.getWorldClocks(gbDevice);
|
||||
|
||||
mGBWorldClockListAdapter.setWorldClockList(worldClocks);
|
||||
mGBWorldClockListAdapter.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 configureWorldClock(final WorldClock worldClock) {
|
||||
final Intent startIntent = new Intent(getApplicationContext(), WorldClockDetails.class);
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, gbDevice);
|
||||
startIntent.putExtra(WorldClock.EXTRA_WORLD_CLOCK, worldClock);
|
||||
startActivityForResult(startIntent, REQ_CONFIGURE_WORLD_CLOCK);
|
||||
}
|
||||
|
||||
public void deleteWorldClock(final WorldClock worldClock) {
|
||||
DBHelper.delete(worldClock);
|
||||
updateWorldClocksFromDB();
|
||||
sendWorldClocksToDevice();
|
||||
}
|
||||
|
||||
private void sendWorldClocksToDevice() {
|
||||
if (gbDevice.isInitialized()) {
|
||||
GBApplication.deviceService().onSetWorldClocks(mGBWorldClockListAdapter.getWorldClockList());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
/* Copyright (C) 2022 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.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.TimeZone;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WorldClock;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class WorldClockDetails extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(WorldClockDetails.class);
|
||||
|
||||
private WorldClock worldClock;
|
||||
private GBDevice device;
|
||||
|
||||
ArrayAdapter<String> timezoneAdapter;
|
||||
|
||||
TextView worldClockTimezone;
|
||||
EditText worldClockLabel;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_world_clock_details);
|
||||
|
||||
worldClock = (WorldClock) getIntent().getSerializableExtra(WorldClock.EXTRA_WORLD_CLOCK);
|
||||
|
||||
if (worldClock == null) {
|
||||
GB.toast("No worldClock provided to WorldClockDetails Activity", Toast.LENGTH_LONG, GB.ERROR);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
worldClockTimezone = findViewById(R.id.world_clock_timezone);
|
||||
worldClockLabel = findViewById(R.id.world_clock_label);
|
||||
|
||||
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
|
||||
final String[] timezoneIDs = TimeZone.getAvailableIDs();
|
||||
timezoneAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, timezoneIDs);
|
||||
|
||||
final View cardTimezone = findViewById(R.id.card_timezone);
|
||||
cardTimezone.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
new AlertDialog.Builder(WorldClockDetails.this).setAdapter(timezoneAdapter, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
worldClock.setTimeZoneId(timezoneIDs[i]);
|
||||
updateUiFromWorldClock();
|
||||
}
|
||||
}).create().show();
|
||||
}
|
||||
});
|
||||
|
||||
worldClockLabel.setFilters(new InputFilter[]{new InputFilter.LengthFilter(coordinator.getWorldClocksLabelLength())});
|
||||
worldClockLabel.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) {
|
||||
worldClock.setLabel(s.toString());
|
||||
}
|
||||
});
|
||||
|
||||
final FloatingActionButton fab = findViewById(R.id.fab_save);
|
||||
fab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
updateWorldClock();
|
||||
WorldClockDetails.this.setResult(1);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
updateUiFromWorldClock();
|
||||
}
|
||||
|
||||
@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 updateWorldClock() {
|
||||
DBHelper.store(worldClock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle state) {
|
||||
super.onSaveInstanceState(state);
|
||||
state.putSerializable("worldClock", worldClock);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
worldClock = (WorldClock) savedInstanceState.getSerializable("worldClock");
|
||||
updateUiFromWorldClock();
|
||||
}
|
||||
|
||||
public void updateUiFromWorldClock() {
|
||||
final String oldTimezone = worldClockTimezone.getText().toString();
|
||||
|
||||
worldClockTimezone.setText(worldClock.getTimeZoneId());
|
||||
|
||||
// Check if the label was still the default (the timezone city name)
|
||||
// If so, and if the user changed the timezone, update the label to match the new city name
|
||||
if (!oldTimezone.equals(worldClock.getTimeZoneId())) {
|
||||
final String[] oldTimezoneParts = oldTimezone.split("/");
|
||||
final String[] newTimezoneParts = worldClock.getTimeZoneId().split("/");
|
||||
final String newLabel = newTimezoneParts[newTimezoneParts.length - 1];
|
||||
final String oldLabel = oldTimezoneParts[oldTimezoneParts.length - 1];
|
||||
final String userLabel = worldClockLabel.getText().toString();
|
||||
|
||||
if (userLabel.equals(oldLabel)) {
|
||||
// The label was still the original, so let's override it with the new city
|
||||
worldClock.setLabel(newLabel);
|
||||
}
|
||||
}
|
||||
|
||||
worldClockLabel.setText(worldClock.getLabel());
|
||||
}
|
||||
}
|
@ -48,20 +48,7 @@ public class DeviceSettingsActivity extends AbstractGBActivity implements
|
||||
if (savedInstanceState == null) {
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(DeviceSpecificSettingsFragment.FRAGMENT_TAG);
|
||||
if (fragment == null) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
int[] supportedSettings = coordinator.getSupportedDeviceSpecificSettings(device);
|
||||
String[] supportedLanguages = coordinator.getSupportedLanguageSettings(device);
|
||||
|
||||
if (supportedLanguages != null) {
|
||||
supportedSettings = ArrayUtils.insert(0, supportedSettings, R.xml.devicesettings_language_generic);
|
||||
}
|
||||
if (coordinator.supportsActivityTracking()) {
|
||||
supportedSettings = ArrayUtils.addAll(supportedSettings, R.xml.devicesettings_chartstabs);
|
||||
supportedSettings = ArrayUtils.addAll(supportedSettings, R.xml.devicesettings_device_card_activity_card_preferences);
|
||||
}
|
||||
|
||||
final DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer = coordinator.getDeviceSpecificSettingsCustomizer(device);
|
||||
fragment = DeviceSpecificSettingsFragment.newInstance(device.getAddress(), supportedSettings, supportedLanguages, deviceSpecificSettingsCustomizer);
|
||||
fragment = DeviceSpecificSettingsFragment.newInstance(device);
|
||||
}
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
@ -73,21 +60,7 @@ public class DeviceSettingsActivity extends AbstractGBActivity implements
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen preferenceScreen) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
int[] supportedSettings = coordinator.getSupportedDeviceSpecificSettings(device);
|
||||
String[] supportedLanguages = coordinator.getSupportedLanguageSettings(device);
|
||||
|
||||
if (supportedLanguages != null) {
|
||||
supportedSettings = ArrayUtils.insert(0, supportedSettings, R.xml.devicesettings_language_generic);
|
||||
}
|
||||
|
||||
if (coordinator.supportsActivityTracking()) {
|
||||
supportedSettings = ArrayUtils.addAll(supportedSettings, R.xml.devicesettings_chartstabs);
|
||||
supportedSettings = ArrayUtils.addAll(supportedSettings, R.xml.devicesettings_device_card_activity_card_preferences);
|
||||
}
|
||||
|
||||
final DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer = coordinator.getDeviceSpecificSettingsCustomizer(device);
|
||||
PreferenceFragmentCompat fragment = DeviceSpecificSettingsFragment.newInstance(device.getAddress(), supportedSettings, supportedLanguages, deviceSpecificSettingsCustomizer);
|
||||
final PreferenceFragmentCompat fragment = DeviceSpecificSettingsFragment.newInstance(device);
|
||||
Bundle args = fragment.getArguments();
|
||||
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
|
||||
fragment.setArguments(args);
|
||||
|
@ -112,6 +112,8 @@ public class DeviceSettingsPreferenceConst {
|
||||
public static final String PREF_KEY_VIBRATION = "key_vibration";
|
||||
public static final String PREF_FAKE_RING_DURATION = "fake_ring_duration";
|
||||
|
||||
public static final String PREF_WORLD_CLOCKS = "pref_world_clocks";
|
||||
|
||||
public static final String PREF_ANTILOST_ENABLED = "pref_antilost_enabled";
|
||||
public static final String PREF_HYDRATION_SWITCH = "pref_hydration_switch";
|
||||
public static final String PREF_HYDRATION_PERIOD = "pref_hydration_period";
|
||||
|
@ -20,6 +20,7 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@ -47,11 +48,17 @@ import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment;
|
||||
@ -89,6 +96,8 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
||||
|
||||
private DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer;
|
||||
|
||||
private GBDevice device;
|
||||
|
||||
private void setSettingsFileSuffix(String settingsFileSuffix, @NonNull int[] supportedSettings, String[] supportedLanguages) {
|
||||
Bundle args = new Bundle();
|
||||
args.putString("settingsFileSuffix", settingsFileSuffix);
|
||||
@ -103,6 +112,12 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
||||
setArguments(args);
|
||||
}
|
||||
|
||||
private void setDevice(final GBDevice device) {
|
||||
final Bundle args = getArguments() != null ? getArguments() : new Bundle();
|
||||
args.putParcelable("device", device);
|
||||
setArguments(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
Bundle arguments = getArguments();
|
||||
@ -113,6 +128,7 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
||||
int[] supportedSettings = arguments.getIntArray("supportedSettings");
|
||||
String[] supportedLanguages = arguments.getStringArray("supportedLanguages");
|
||||
this.deviceSpecificSettingsCustomizer = arguments.getParcelable("deviceSpecificSettingsCustomizer");
|
||||
this.device = arguments.getParcelable("device");
|
||||
|
||||
if (settingsFileSuffix == null || supportedSettings == null) {
|
||||
return;
|
||||
@ -587,6 +603,19 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
||||
});
|
||||
}
|
||||
|
||||
final Preference worldClocks = findPreference(PREF_WORLD_CLOCKS);
|
||||
if (worldClocks != null) {
|
||||
worldClocks.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
final Intent intent = new Intent(getContext(), ConfigureWorldClocks.class);
|
||||
intent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final Preference cannedMessagesDismissCall = findPreference("canned_messages_dismisscall_send");
|
||||
if (cannedMessagesDismissCall != null) {
|
||||
cannedMessagesDismissCall.setOnPreferenceClickListener(new androidx.preference.Preference.OnPreferenceClickListener() {
|
||||
@ -704,13 +733,28 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
||||
}
|
||||
}
|
||||
|
||||
static DeviceSpecificSettingsFragment newInstance(String settingsFileSuffix,
|
||||
@NonNull int[] supportedSettings,
|
||||
String[] supportedLanguages,
|
||||
DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer) {
|
||||
static DeviceSpecificSettingsFragment newInstance(GBDevice device) {
|
||||
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
int[] supportedSettings = coordinator.getSupportedDeviceSpecificSettings(device);
|
||||
String[] supportedLanguages = coordinator.getSupportedLanguageSettings(device);
|
||||
|
||||
if (supportedLanguages != null) {
|
||||
supportedSettings = ArrayUtils.insert(0, supportedSettings, R.xml.devicesettings_language_generic);
|
||||
}
|
||||
|
||||
if (coordinator.supportsActivityTracking()) {
|
||||
supportedSettings = ArrayUtils.addAll(supportedSettings, R.xml.devicesettings_chartstabs);
|
||||
supportedSettings = ArrayUtils.addAll(supportedSettings, R.xml.devicesettings_device_card_activity_card_preferences);
|
||||
}
|
||||
|
||||
final DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer = coordinator.getDeviceSpecificSettingsCustomizer(device);
|
||||
|
||||
final String settingsFileSuffix = device.getAddress();
|
||||
|
||||
final DeviceSpecificSettingsFragment fragment = new DeviceSpecificSettingsFragment();
|
||||
fragment.setSettingsFileSuffix(settingsFileSuffix, supportedSettings, supportedLanguages);
|
||||
fragment.setDeviceSpecificSettingsCustomizer(deviceSpecificSettingsCustomizer);
|
||||
fragment.setDevice(device);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
/* Copyright (C) 2022 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.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WorldClock;
|
||||
|
||||
/**
|
||||
* Adapter for displaying WorldClock instances.
|
||||
*/
|
||||
public class GBWorldClockListAdapter extends RecyclerView.Adapter<GBWorldClockListAdapter.ViewHolder> {
|
||||
|
||||
private final Context mContext;
|
||||
private ArrayList<WorldClock> worldClockList;
|
||||
|
||||
public GBWorldClockListAdapter(final Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
public void setWorldClockList(final List<WorldClock> worldClocks) {
|
||||
this.worldClockList = new ArrayList<>(worldClocks);
|
||||
}
|
||||
|
||||
public ArrayList<WorldClock> getWorldClockList() {
|
||||
return worldClockList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public GBWorldClockListAdapter.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_world_clock, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
|
||||
final WorldClock worldClock = worldClockList.get(position);
|
||||
|
||||
holder.container.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
((ConfigureWorldClocks) mContext).configureWorldClock(worldClock);
|
||||
}
|
||||
});
|
||||
|
||||
holder.container.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
new AlertDialog.Builder(v.getContext())
|
||||
.setTitle(v.getContext().getString(R.string.world_clock_delete_confirm_title, worldClock.getLabel()))
|
||||
.setMessage(R.string.world_clock_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) {
|
||||
((ConfigureWorldClocks) mContext).deleteWorldClock(worldClock);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
holder.worldClockLabel.setText(worldClock.getLabel());
|
||||
holder.worldClockTimezone.setText(worldClock.getTimeZoneId());
|
||||
|
||||
final DateFormat df = new SimpleDateFormat("HH:mm", GBApplication.getLanguage());
|
||||
df.setTimeZone(TimeZone.getTimeZone(worldClock.getTimeZoneId()));
|
||||
holder.worldClockCurrentTime.setText(df.format(new Date()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return worldClockList.size();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
final CardView container;
|
||||
|
||||
final TextView worldClockTimezone;
|
||||
final TextView worldClockLabel;
|
||||
final TextView worldClockCurrentTime;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
|
||||
container = view.findViewById(R.id.card_view);
|
||||
|
||||
worldClockTimezone = view.findViewById(R.id.world_clock_item_timezone);
|
||||
worldClockLabel = view.findViewById(R.id.world_clock_item_label);
|
||||
worldClockCurrentTime = view.findViewById(R.id.world_clock_current_time);
|
||||
}
|
||||
}
|
||||
}
|
@ -62,6 +62,8 @@ import nodomain.freeyourgadget.gadgetbridge.entities.TagDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.UserAttributes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.UserDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WorldClock;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.WorldClockDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ValidByDate;
|
||||
@ -658,6 +660,28 @@ public class DBHelper {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List<WorldClock> getWorldClocks(@NonNull GBDevice gbDevice) {
|
||||
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 WorldClockDao worldClockDao = daoSession.getWorldClockDao();
|
||||
final Long deviceId = dbDevice.getId();
|
||||
final QueryBuilder<WorldClock> qb = worldClockDao.queryBuilder();
|
||||
qb.where(
|
||||
WorldClockDao.Properties.UserId.eq(user.getId()),
|
||||
WorldClockDao.Properties.DeviceId.eq(deviceId)).orderAsc(WorldClockDao.Properties.WorldClockId);
|
||||
return qb.build().list();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error reading world clocks from db", e);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static void store(final Reminder reminder) {
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
@ -667,6 +691,15 @@ public class DBHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static void store(final WorldClock worldClock) {
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
daoSession.insertOrReplace(worldClock);
|
||||
} 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();
|
||||
@ -676,6 +709,15 @@ public class DBHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static void delete(final WorldClock worldClock) {
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
final DaoSession daoSession = db.getDaoSession();
|
||||
daoSession.delete(worldClock);
|
||||
} catch (final Exception e) {
|
||||
LOG.error("Error acquiring database", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearSession() {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
|
@ -232,6 +232,16 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldClocksSlotCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldClocksLabelLength() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRgbLedColor() {
|
||||
return false;
|
||||
|
@ -331,6 +331,16 @@ public interface DeviceCoordinator {
|
||||
*/
|
||||
int getReminderSlotCount();
|
||||
|
||||
/**
|
||||
* Indicates the maximum number of slots available for world clocks in the device.
|
||||
*/
|
||||
int getWorldClocksSlotCount();
|
||||
|
||||
/**
|
||||
* Indicates the maximum label length for a world clock in the device.
|
||||
*/
|
||||
int getWorldClocksLabelLength();
|
||||
|
||||
/**
|
||||
* Indicates whether the device has an led which supports custom colors
|
||||
*/
|
||||
|
@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||
|
||||
/**
|
||||
* Specifies all events that Gadgetbridge intends to send to the gadget device.
|
||||
@ -49,6 +50,8 @@ public interface EventHandler {
|
||||
|
||||
void onSetReminders(ArrayList<? extends Reminder> reminders);
|
||||
|
||||
void onSetWorldClocks(ArrayList<? extends WorldClock> clocks);
|
||||
|
||||
void onSetCallState(CallSpec callSpec);
|
||||
|
||||
void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec);
|
||||
|
@ -85,6 +85,16 @@ public class MiBand5Coordinator extends HuamiCoordinator {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldClocksSlotCount() {
|
||||
return 20; // as enforced by Mi Fit
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWorldClocksLabelLength() {
|
||||
return 30; // at least
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
return new int[]{
|
||||
@ -93,6 +103,7 @@ public class MiBand5Coordinator extends HuamiCoordinator {
|
||||
R.xml.devicesettings_custom_emoji_font,
|
||||
R.xml.devicesettings_timeformat,
|
||||
R.xml.devicesettings_dateformat,
|
||||
R.xml.devicesettings_world_clocks,
|
||||
R.xml.devicesettings_nightmode,
|
||||
R.xml.devicesettings_liftwrist_display_sensitivity,
|
||||
R.xml.devicesettings_swipeunlock,
|
||||
|
@ -43,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.RtlUtils;
|
||||
|
||||
@ -230,6 +231,13 @@ public class GBDeviceService implements DeviceService {
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
|
||||
Intent intent = createIntent().setAction(ACTION_SET_WORLD_CLOCKS)
|
||||
.putExtra(EXTRA_WORLD_CLOCKS, clocks);
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||
Intent intent = createIntent().setAction(ACTION_SETMUSICINFO)
|
||||
|
@ -56,6 +56,7 @@ public interface DeviceService extends EventHandler {
|
||||
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
|
||||
String ACTION_SAVE_ALARMS = PREFIX + ".action.save_alarms";
|
||||
String ACTION_SET_REMINDERS = PREFIX + ".action.set_reminders";
|
||||
String ACTION_SET_WORLD_CLOCKS = PREFIX + ".action.set_world_clocks";
|
||||
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
|
||||
String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
|
||||
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
|
||||
@ -110,6 +111,7 @@ public interface DeviceService extends EventHandler {
|
||||
String EXTRA_CONFIG = "config";
|
||||
String EXTRA_ALARMS = "alarms";
|
||||
String EXTRA_REMINDERS = "reminders";
|
||||
String EXTRA_WORLD_CLOCKS = "world_clocks";
|
||||
String EXTRA_CONNECT_FIRST_TIME = "connect_first_time";
|
||||
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
|
||||
String EXTRA_INTERVAL_SECONDS = "interval_seconds";
|
||||
|
@ -0,0 +1,30 @@
|
||||
/* Copyright (C) 2022 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;
|
||||
|
||||
public interface WorldClock extends Serializable {
|
||||
/**
|
||||
* The {@link android.os.Bundle} name for transferring parceled world clocks.
|
||||
*/
|
||||
String EXTRA_WORLD_CLOCK = "world_clock";
|
||||
|
||||
String getWorldClockId();
|
||||
String getLabel();
|
||||
String getTimeZoneId();
|
||||
}
|
@ -75,6 +75,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.receivers.AutoConnectIntervalReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBAutoFetchReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
@ -120,6 +121,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SE
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_HEARTRATE_MEASUREMENT_INTERVAL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_LED_COLOR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_REMINDERS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_WORLD_CLOCKS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION;
|
||||
@ -178,6 +180,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_RES
|
||||
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_WEATHER;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WORLD_CLOCKS;
|
||||
|
||||
public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
|
||||
@ -586,6 +589,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
ArrayList<? extends Reminder> reminders = (ArrayList<? extends Reminder>) intent.getSerializableExtra(EXTRA_REMINDERS);
|
||||
mDeviceSupport.onSetReminders(reminders);
|
||||
break;
|
||||
case ACTION_SET_WORLD_CLOCKS:
|
||||
ArrayList<? extends WorldClock> clocks = (ArrayList<? extends WorldClock>) intent.getSerializableExtra(EXTRA_WORLD_CLOCKS);
|
||||
mDeviceSupport.onSetWorldClocks(clocks);
|
||||
break;
|
||||
case ACTION_ENABLE_REALTIME_STEPS: {
|
||||
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
|
||||
mDeviceSupport.onEnableRealtimeSteps(enable);
|
||||
|
@ -39,6 +39,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||
|
||||
/**
|
||||
* Wraps another device support instance and supports busy-checking and throttling of events.
|
||||
@ -318,6 +319,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
delegate.onSetReminders(reminders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
|
||||
if (checkBusy("set world clocks")) {
|
||||
return;
|
||||
}
|
||||
delegate.onSetWorldClocks(clocks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeSteps(boolean enable) {
|
||||
if (checkBusy("enable realtime steps: " + enable)) {
|
||||
|
@ -38,6 +38,7 @@ import java.util.UUID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
|
||||
@ -378,6 +379,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
|
||||
|
||||
|
@ -37,10 +37,16 @@ import net.e175.klaus.solarpositioning.SPA;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.threeten.bp.Instant;
|
||||
import org.threeten.bp.ZoneId;
|
||||
import org.threeten.bp.zone.ZoneOffsetTransition;
|
||||
import org.threeten.bp.zone.ZoneRules;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
@ -52,6 +58,7 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SimpleTimeZone;
|
||||
import java.util.TimeZone;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
@ -112,6 +119,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
|
||||
@ -925,6 +933,120 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
writeToChunked(builder, 2, buf.array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
|
||||
final TransactionBuilder builder;
|
||||
try {
|
||||
builder = performInitialized("onSetWorldClocks");
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Unable to send world clocks to device", e);
|
||||
return;
|
||||
}
|
||||
|
||||
sendWorldClocks(builder, clocks);
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
|
||||
private void setWorldClocks(final TransactionBuilder builder) {
|
||||
final List<? extends WorldClock> clocks = DBHelper.getWorldClocks(gbDevice);
|
||||
sendWorldClocks(builder, clocks);
|
||||
}
|
||||
|
||||
private void sendWorldClocks(final TransactionBuilder builder, final List<? extends WorldClock> clocks) {
|
||||
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
|
||||
if (coordinator.getWorldClocksSlotCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
baos.write(0x03);
|
||||
|
||||
if (clocks.size() != 0) {
|
||||
int i = clocks.size();
|
||||
for (final WorldClock clock : clocks) {
|
||||
baos.write(i--);
|
||||
baos.write(encodeWorldClock(clock));
|
||||
}
|
||||
} else {
|
||||
baos.write(0);
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Unable to send world clocks to device", e);
|
||||
return;
|
||||
}
|
||||
|
||||
writeToChunked2021(builder, (short) 0x0008, getNextHandle(), baos.toByteArray(), false, false);
|
||||
}
|
||||
|
||||
public byte[] encodeWorldClock(final WorldClock clock) {
|
||||
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
|
||||
|
||||
try {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
final TimeZone timezone = TimeZone.getTimeZone(clock.getTimeZoneId());
|
||||
final ZoneId zoneId = ZoneId.of(clock.getTimeZoneId());
|
||||
|
||||
// Usually the 3-letter city code (eg. LIS for Lisbon), but doesn't seem to be used in the UI
|
||||
baos.write(" ".getBytes(StandardCharsets.UTF_8));
|
||||
baos.write(0x00);
|
||||
|
||||
// Some other string? Seems to be empty
|
||||
baos.write(0x00);
|
||||
|
||||
// The city name / label that shows up on the band
|
||||
baos.write(StringUtils.truncate(clock.getLabel(), coordinator.getWorldClocksLabelLength()).getBytes(StandardCharsets.UTF_8));
|
||||
baos.write(0x00);
|
||||
|
||||
// The raw offset from UTC, in number of 15-minute blocks
|
||||
baos.write((int) (timezone.getRawOffset() / (1000L * 60L * 15L)));
|
||||
|
||||
// Daylight savings
|
||||
final boolean useDaylightTime = timezone.useDaylightTime();
|
||||
final boolean inDaylightTime = timezone.inDaylightTime(new Date());
|
||||
byte daylightByte = 0;
|
||||
// The daylight savings offset, either currently (the previous transition) or future (the next transition), in minutes
|
||||
byte daylightOffsetMinutes = 0;
|
||||
|
||||
final ZoneRules zoneRules = zoneId.getRules();
|
||||
if (useDaylightTime) {
|
||||
final ZoneOffsetTransition transition;
|
||||
if (inDaylightTime) {
|
||||
daylightByte = 0x01;
|
||||
transition = zoneRules.previousTransition(Instant.now());
|
||||
} else {
|
||||
daylightByte = 0x02;
|
||||
transition = zoneRules.nextTransition(Instant.now());
|
||||
}
|
||||
daylightOffsetMinutes = (byte) transition.getDuration().toMinutes();
|
||||
}
|
||||
|
||||
baos.write(daylightByte);
|
||||
baos.write(daylightOffsetMinutes);
|
||||
|
||||
// The timestamp of the next daylight savings transition, if any
|
||||
final ZoneOffsetTransition nextTransition = zoneRules.nextTransition(Instant.now());
|
||||
long nextTransitionTs = 0;
|
||||
if (nextTransition != null) {
|
||||
nextTransitionTs = nextTransition
|
||||
.getDateTimeBefore()
|
||||
.atZone(zoneId)
|
||||
.toEpochSecond();
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
baos.write((byte) ((nextTransitionTs >> (i * 8)) & 0xff));
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException("This should never happen", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) {
|
||||
@ -3351,6 +3473,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
||||
setExposeHRThridParty(builder);
|
||||
setHeartrateMeasurementInterval(builder, getHeartRateMeasurementInterval());
|
||||
sendReminders(builder);
|
||||
setWorldClocks(builder);
|
||||
requestAlarms(builder);
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
|
||||
|
||||
/**
|
||||
@ -276,4 +277,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
|
||||
byte[] bytes = gbDeviceProtocol.encodeReminders(reminders);
|
||||
sendToDevice(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
|
||||
byte[] bytes = gbDeviceProtocol.encodeWorldClocks(clocks);
|
||||
sendToDevice(bytes);
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||
|
||||
public abstract class GBDeviceProtocol {
|
||||
|
||||
@ -151,6 +152,10 @@ public abstract class GBDeviceProtocol {
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] encodeWorldClocks(ArrayList<? extends WorldClock> clocks) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] encodeFmFrequency(float frequency) {
|
||||
return null;
|
||||
}
|
||||
|
27
app/src/main/res/layout/activity_configure_world_clocks.xml
Normal file
27
app/src/main/res/layout/activity_configure_world_clocks.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureWorldClocks">
|
||||
|
||||
<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/world_clock_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>
|
97
app/src/main/res/layout/activity_world_clock_details.xml
Normal file
97
app/src/main/res/layout/activity_world_clock_details.xml
Normal file
@ -0,0 +1,97 @@
|
||||
<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_timezone"
|
||||
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_timezone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/world_clock_timezone"
|
||||
android:textAppearance="?android:attr/textAppearance"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/world_clock_timezone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="?"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/label_timezone" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/card_label"
|
||||
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_timezone"
|
||||
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/world_clock_label"
|
||||
android:textAppearance="?android:attr/textAppearance"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/world_clock_label"
|
||||
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>
|
@ -73,15 +73,15 @@
|
||||
android:id="@+id/device_image"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@id/device_item_infos_box"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
android:clickable="true"
|
||||
android:longClickable="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
card_view:srcCompat="@drawable/ic_device_pebble"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="2dp"
|
||||
android:focusable="true" />
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/candidate_item_device_image"
|
||||
android:focusable="true"
|
||||
android:longClickable="true"
|
||||
card_view:srcCompat="@drawable/ic_device_pebble" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_name"
|
||||
|
64
app/src/main/res/layout/item_world_clock.xml
Normal file
64
app/src/main/res/layout/item_world_clock.xml
Normal file
@ -0,0 +1,64 @@
|
||||
<?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/world_clock_item_timezone"
|
||||
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="Middle-earth/Gondor"
|
||||
android:textAppearance="?android:attr/textAppearance" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/world_clock_item_label"
|
||||
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" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/world_clock_current_time"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="00:00"
|
||||
card_view:tint="@color/secondarytext" />
|
||||
|
||||
</RelativeLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -486,6 +486,8 @@
|
||||
<string name="title_activity_charts">Activity and Sleep</string>
|
||||
<string name="title_activity_set_alarm">Configure alarms</string>
|
||||
<string name="title_activity_set_reminders">Configure reminders</string>
|
||||
<string name="pref_world_clocks_title">World Clocks</string>
|
||||
<string name="pref_world_clocks_summary">Configure clocks for other timezones</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>
|
||||
@ -506,8 +508,15 @@
|
||||
<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="world_clock_delete_confirm_title">Delete \'%1$s\'</string>
|
||||
<string name="world_clock_delete_confirm_description">Are you sure you want to delete the world clock?</string>
|
||||
<string name="world_clock_no_free_slots_title">No free slots</string>
|
||||
<string name="world_clock_no_free_slots_description">The device has no free slots for world clocks (total slots: %1$s)</string>
|
||||
<string name="world_clock_timezone">Time Zone</string>
|
||||
<string name="world_clock_label">Label</string>
|
||||
<string name="title_activity_alarm_details">Alarm details</string>
|
||||
<string name="title_activity_reminder_details">Reminder details</string>
|
||||
<string name="title_activity_world_clock_details">World Clock details</string>
|
||||
<string name="alarm_sun_short">Sun</string>
|
||||
<string name="alarm_mon_short">Mon</string>
|
||||
<string name="alarm_tue_short">Tue</string>
|
||||
|
8
app/src/main/res/xml/devicesettings_world_clocks.xml
Normal file
8
app/src/main/res/xml/devicesettings_world_clocks.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Preference
|
||||
android:icon="@drawable/ic_access_time"
|
||||
android:key="pref_world_clocks"
|
||||
android:summary="@string/pref_world_clocks_summary"
|
||||
android:title="@string/pref_world_clocks_title" />
|
||||
</androidx.preference.PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user