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 {
|
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 userAttributes = addUserAttributes(schema);
|
||||||
Entity user = addUserInfo(schema, userAttributes);
|
Entity user = addUserInfo(schema, userAttributes);
|
||||||
@ -87,6 +87,7 @@ public class GBDaoGenerator {
|
|||||||
addCalendarSyncState(schema, device);
|
addCalendarSyncState(schema, device);
|
||||||
addAlarms(schema, user, device);
|
addAlarms(schema, user, device);
|
||||||
addReminders(schema, user, device);
|
addReminders(schema, user, device);
|
||||||
|
addWorldClocks(schema, user, device);
|
||||||
|
|
||||||
Entity notificationFilter = addNotificationFilters(schema);
|
Entity notificationFilter = addNotificationFilters(schema);
|
||||||
|
|
||||||
@ -561,6 +562,24 @@ public class GBDaoGenerator {
|
|||||||
reminder.addToOne(device, deviceId);
|
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) {
|
private static void addNotificationFilterEntry(Schema schema, Entity notificationFilterEntity) {
|
||||||
Entity notificatonFilterEntry = addEntity(schema, "NotificationFilterEntry");
|
Entity notificatonFilterEntry = addEntity(schema, "NotificationFilterEntry");
|
||||||
notificatonFilterEntry.addIdProperty().autoincrement();
|
notificatonFilterEntry.addIdProperty().autoincrement();
|
||||||
|
@ -238,6 +238,10 @@ dependencies {
|
|||||||
implementation 'com.google.protobuf:protobuf-lite:3.0.1'
|
implementation 'com.google.protobuf:protobuf-lite:3.0.1'
|
||||||
implementation "androidx.multidex:multidex:2.0.1"
|
implementation "androidx.multidex:multidex:2.0.1"
|
||||||
implementation 'com.android.volley:volley:1.2.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")
|
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
||||||
|
@ -498,6 +498,10 @@
|
|||||||
android:name=".activities.ConfigureReminders"
|
android:name=".activities.ConfigureReminders"
|
||||||
android:label="@string/title_activity_set_reminders"
|
android:label="@string/title_activity_set_reminders"
|
||||||
android:parentActivityName=".activities.ControlCenterv2" />
|
android:parentActivityName=".activities.ControlCenterv2" />
|
||||||
|
<activity
|
||||||
|
android:name=".activities.ConfigureWorldClocks"
|
||||||
|
android:label="@string/pref_world_clocks_title"
|
||||||
|
android:parentActivityName=".activities.ControlCenterv2" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.devicesettings.DeviceSettingsActivity"
|
android:name=".activities.devicesettings.DeviceSettingsActivity"
|
||||||
android:label="@string/title_activity_device_specific_settings"
|
android:label="@string/title_activity_device_specific_settings"
|
||||||
@ -513,6 +517,12 @@
|
|||||||
android:parentActivityName=".activities.ConfigureReminders"
|
android:parentActivityName=".activities.ConfigureReminders"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:windowSoftInputMode="adjustResize" />
|
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
|
<activity
|
||||||
android:name=".activities.VibrationActivity"
|
android:name=".activities.VibrationActivity"
|
||||||
android:label="@string/title_activity_vibration"
|
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 androidx.multidex.MultiDex;
|
||||||
|
|
||||||
|
import com.jakewharton.threetenabp.AndroidThreeTen;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main Application class that initializes and provides access to certain things like
|
* Main Application class that initializes and provides access to certain things like
|
||||||
* logging and DB access.
|
* logging and DB access.
|
||||||
@ -191,6 +193,9 @@ public class GBApplication extends Application {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize the timezones library
|
||||||
|
AndroidThreeTen.init(this);
|
||||||
|
|
||||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
prefs = new Prefs(sharedPrefs);
|
prefs = new Prefs(sharedPrefs);
|
||||||
gbPrefs = new GBPrefs(prefs);
|
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) {
|
if (savedInstanceState == null) {
|
||||||
Fragment fragment = getSupportFragmentManager().findFragmentByTag(DeviceSpecificSettingsFragment.FRAGMENT_TAG);
|
Fragment fragment = getSupportFragmentManager().findFragmentByTag(DeviceSpecificSettingsFragment.FRAGMENT_TAG);
|
||||||
if (fragment == null) {
|
if (fragment == null) {
|
||||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
fragment = DeviceSpecificSettingsFragment.newInstance(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);
|
|
||||||
}
|
}
|
||||||
getSupportFragmentManager()
|
getSupportFragmentManager()
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
@ -73,21 +60,7 @@ public class DeviceSettingsActivity extends AbstractGBActivity implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen preferenceScreen) {
|
public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen preferenceScreen) {
|
||||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
final PreferenceFragmentCompat fragment = DeviceSpecificSettingsFragment.newInstance(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);
|
|
||||||
Bundle args = fragment.getArguments();
|
Bundle args = fragment.getArguments();
|
||||||
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
|
args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, preferenceScreen.getKey());
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
|
@ -112,6 +112,8 @@ public class DeviceSettingsPreferenceConst {
|
|||||||
public static final String PREF_KEY_VIBRATION = "key_vibration";
|
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_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_ANTILOST_ENABLED = "pref_antilost_enabled";
|
||||||
public static final String PREF_HYDRATION_SWITCH = "pref_hydration_switch";
|
public static final String PREF_HYDRATION_SWITCH = "pref_hydration_switch";
|
||||||
public static final String PREF_HYDRATION_PERIOD = "pref_hydration_period";
|
public static final String PREF_HYDRATION_PERIOD = "pref_hydration_period";
|
||||||
|
@ -20,6 +20,7 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -47,11 +48,17 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
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.DeviceManager;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
|
import nodomain.freeyourgadget.gadgetbridge.devices.makibeshr3.MakibesHR3Constants;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
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.model.CannedMessagesSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
|
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment;
|
import nodomain.freeyourgadget.gadgetbridge.util.XTimePreferenceFragment;
|
||||||
@ -89,6 +96,8 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
|||||||
|
|
||||||
private DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer;
|
private DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer;
|
||||||
|
|
||||||
|
private GBDevice device;
|
||||||
|
|
||||||
private void setSettingsFileSuffix(String settingsFileSuffix, @NonNull int[] supportedSettings, String[] supportedLanguages) {
|
private void setSettingsFileSuffix(String settingsFileSuffix, @NonNull int[] supportedSettings, String[] supportedLanguages) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putString("settingsFileSuffix", settingsFileSuffix);
|
args.putString("settingsFileSuffix", settingsFileSuffix);
|
||||||
@ -103,6 +112,12 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
|||||||
setArguments(args);
|
setArguments(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setDevice(final GBDevice device) {
|
||||||
|
final Bundle args = getArguments() != null ? getArguments() : new Bundle();
|
||||||
|
args.putParcelable("device", device);
|
||||||
|
setArguments(args);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||||
Bundle arguments = getArguments();
|
Bundle arguments = getArguments();
|
||||||
@ -113,6 +128,7 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
|||||||
int[] supportedSettings = arguments.getIntArray("supportedSettings");
|
int[] supportedSettings = arguments.getIntArray("supportedSettings");
|
||||||
String[] supportedLanguages = arguments.getStringArray("supportedLanguages");
|
String[] supportedLanguages = arguments.getStringArray("supportedLanguages");
|
||||||
this.deviceSpecificSettingsCustomizer = arguments.getParcelable("deviceSpecificSettingsCustomizer");
|
this.deviceSpecificSettingsCustomizer = arguments.getParcelable("deviceSpecificSettingsCustomizer");
|
||||||
|
this.device = arguments.getParcelable("device");
|
||||||
|
|
||||||
if (settingsFileSuffix == null || supportedSettings == null) {
|
if (settingsFileSuffix == null || supportedSettings == null) {
|
||||||
return;
|
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");
|
final Preference cannedMessagesDismissCall = findPreference("canned_messages_dismisscall_send");
|
||||||
if (cannedMessagesDismissCall != null) {
|
if (cannedMessagesDismissCall != null) {
|
||||||
cannedMessagesDismissCall.setOnPreferenceClickListener(new androidx.preference.Preference.OnPreferenceClickListener() {
|
cannedMessagesDismissCall.setOnPreferenceClickListener(new androidx.preference.Preference.OnPreferenceClickListener() {
|
||||||
@ -704,13 +733,28 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static DeviceSpecificSettingsFragment newInstance(String settingsFileSuffix,
|
static DeviceSpecificSettingsFragment newInstance(GBDevice device) {
|
||||||
@NonNull int[] supportedSettings,
|
final DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||||
String[] supportedLanguages,
|
int[] supportedSettings = coordinator.getSupportedDeviceSpecificSettings(device);
|
||||||
DeviceSpecificSettingsCustomizer deviceSpecificSettingsCustomizer) {
|
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();
|
final DeviceSpecificSettingsFragment fragment = new DeviceSpecificSettingsFragment();
|
||||||
fragment.setSettingsFileSuffix(settingsFileSuffix, supportedSettings, supportedLanguages);
|
fragment.setSettingsFileSuffix(settingsFileSuffix, supportedSettings, supportedLanguages);
|
||||||
fragment.setDeviceSpecificSettingsCustomizer(deviceSpecificSettingsCustomizer);
|
fragment.setDeviceSpecificSettingsCustomizer(deviceSpecificSettingsCustomizer);
|
||||||
|
fragment.setDevice(device);
|
||||||
|
|
||||||
return fragment;
|
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.User;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.UserAttributes;
|
import nodomain.freeyourgadget.gadgetbridge.entities.UserAttributes;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.entities.UserDao;
|
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.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ValidByDate;
|
import nodomain.freeyourgadget.gadgetbridge.model.ValidByDate;
|
||||||
@ -658,6 +660,28 @@ public class DBHelper {
|
|||||||
return Collections.emptyList();
|
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) {
|
public static void store(final Reminder reminder) {
|
||||||
try (DBHandler db = GBApplication.acquireDB()) {
|
try (DBHandler db = GBApplication.acquireDB()) {
|
||||||
final DaoSession daoSession = db.getDaoSession();
|
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) {
|
public static void delete(final Reminder reminder) {
|
||||||
try (DBHandler db = GBApplication.acquireDB()) {
|
try (DBHandler db = GBApplication.acquireDB()) {
|
||||||
final DaoSession daoSession = db.getDaoSession();
|
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() {
|
public static void clearSession() {
|
||||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||||
DaoSession session = dbHandler.getDaoSession();
|
DaoSession session = dbHandler.getDaoSession();
|
||||||
|
@ -232,6 +232,16 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWorldClocksSlotCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWorldClocksLabelLength() {
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsRgbLedColor() {
|
public boolean supportsRgbLedColor() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -331,6 +331,16 @@ public interface DeviceCoordinator {
|
|||||||
*/
|
*/
|
||||||
int getReminderSlotCount();
|
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
|
* 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.NotificationSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies all events that Gadgetbridge intends to send to the gadget device.
|
* 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 onSetReminders(ArrayList<? extends Reminder> reminders);
|
||||||
|
|
||||||
|
void onSetWorldClocks(ArrayList<? extends WorldClock> clocks);
|
||||||
|
|
||||||
void onSetCallState(CallSpec callSpec);
|
void onSetCallState(CallSpec callSpec);
|
||||||
|
|
||||||
void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec);
|
void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec);
|
||||||
|
@ -85,6 +85,16 @@ public class MiBand5Coordinator extends HuamiCoordinator {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWorldClocksSlotCount() {
|
||||||
|
return 20; // as enforced by Mi Fit
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWorldClocksLabelLength() {
|
||||||
|
return 30; // at least
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||||
return new int[]{
|
return new int[]{
|
||||||
@ -93,6 +103,7 @@ public class MiBand5Coordinator extends HuamiCoordinator {
|
|||||||
R.xml.devicesettings_custom_emoji_font,
|
R.xml.devicesettings_custom_emoji_font,
|
||||||
R.xml.devicesettings_timeformat,
|
R.xml.devicesettings_timeformat,
|
||||||
R.xml.devicesettings_dateformat,
|
R.xml.devicesettings_dateformat,
|
||||||
|
R.xml.devicesettings_world_clocks,
|
||||||
R.xml.devicesettings_nightmode,
|
R.xml.devicesettings_nightmode,
|
||||||
R.xml.devicesettings_liftwrist_display_sensitivity,
|
R.xml.devicesettings_liftwrist_display_sensitivity,
|
||||||
R.xml.devicesettings_swipeunlock,
|
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.NotificationSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
|
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.RtlUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.RtlUtils;
|
||||||
|
|
||||||
@ -230,6 +231,13 @@ public class GBDeviceService implements DeviceService {
|
|||||||
invokeService(intent);
|
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
|
@Override
|
||||||
public void onSetMusicInfo(MusicSpec musicSpec) {
|
public void onSetMusicInfo(MusicSpec musicSpec) {
|
||||||
Intent intent = createIntent().setAction(ACTION_SETMUSICINFO)
|
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_SET_ALARMS = PREFIX + ".action.set_alarms";
|
||||||
String ACTION_SAVE_ALARMS = PREFIX + ".action.save_alarms";
|
String ACTION_SAVE_ALARMS = PREFIX + ".action.save_alarms";
|
||||||
String ACTION_SET_REMINDERS = PREFIX + ".action.set_reminders";
|
String ACTION_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_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
|
||||||
String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
|
String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
|
||||||
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
|
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
|
||||||
@ -110,6 +111,7 @@ public interface DeviceService extends EventHandler {
|
|||||||
String EXTRA_CONFIG = "config";
|
String EXTRA_CONFIG = "config";
|
||||||
String EXTRA_ALARMS = "alarms";
|
String EXTRA_ALARMS = "alarms";
|
||||||
String EXTRA_REMINDERS = "reminders";
|
String EXTRA_REMINDERS = "reminders";
|
||||||
|
String EXTRA_WORLD_CLOCKS = "world_clocks";
|
||||||
String EXTRA_CONNECT_FIRST_TIME = "connect_first_time";
|
String EXTRA_CONNECT_FIRST_TIME = "connect_first_time";
|
||||||
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
|
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
|
||||||
String EXTRA_INTERVAL_SECONDS = "interval_seconds";
|
String EXTRA_INTERVAL_SECONDS = "interval_seconds";
|
||||||
|
@ -0,0 +1,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.NotificationType;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
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.AutoConnectIntervalReceiver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBAutoFetchReceiver;
|
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBAutoFetchReceiver;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
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_HEARTRATE_MEASUREMENT_INTERVAL;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_LED_COLOR;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_LED_COLOR;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_REMINDERS;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_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_START;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION;
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION;
|
||||||
@ -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_URI;
|
||||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_VIBRATION_INTENSITY;
|
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_WEATHER;
|
||||||
|
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WORLD_CLOCKS;
|
||||||
|
|
||||||
public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
|
public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
|
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);
|
ArrayList<? extends Reminder> reminders = (ArrayList<? extends Reminder>) intent.getSerializableExtra(EXTRA_REMINDERS);
|
||||||
mDeviceSupport.onSetReminders(reminders);
|
mDeviceSupport.onSetReminders(reminders);
|
||||||
break;
|
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: {
|
case ACTION_ENABLE_REALTIME_STEPS: {
|
||||||
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
|
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
|
||||||
mDeviceSupport.onEnableRealtimeSteps(enable);
|
mDeviceSupport.onEnableRealtimeSteps(enable);
|
||||||
|
@ -39,6 +39,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
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.
|
* 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);
|
delegate.onSetReminders(reminders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
|
||||||
|
if (checkBusy("set world clocks")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delegate.onSetWorldClocks(clocks);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnableRealtimeSteps(boolean enable) {
|
public void onEnableRealtimeSteps(boolean enable) {
|
||||||
if (checkBusy("enable realtime steps: " + enable)) {
|
if (checkBusy("enable realtime steps: " + enable)) {
|
||||||
|
@ -38,6 +38,7 @@ import java.util.UUID;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
import nodomain.freeyourgadget.gadgetbridge.Logging;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
|
||||||
@ -378,6 +379,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSetWorldClocks(ArrayList<? extends WorldClock> clocks) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
|
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.apache.commons.lang3.ArrayUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
@ -52,6 +58,7 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.SimpleTimeZone;
|
import java.util.SimpleTimeZone;
|
||||||
|
import java.util.TimeZone;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
import java.util.UUID;
|
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.Reminder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
|
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
|
||||||
@ -925,6 +933,120 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
writeToChunked(builder, 2, buf.array());
|
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
|
@Override
|
||||||
public void onNotification(NotificationSpec notificationSpec) {
|
public void onNotification(NotificationSpec notificationSpec) {
|
||||||
if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) {
|
if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) {
|
||||||
@ -3351,6 +3473,7 @@ public class HuamiSupport extends AbstractBTLEDeviceSupport {
|
|||||||
setExposeHRThridParty(builder);
|
setExposeHRThridParty(builder);
|
||||||
setHeartrateMeasurementInterval(builder, getHeartRateMeasurementInterval());
|
setHeartrateMeasurementInterval(builder, getHeartRateMeasurementInterval());
|
||||||
sendReminders(builder);
|
sendReminders(builder);
|
||||||
|
setWorldClocks(builder);
|
||||||
requestAlarms(builder);
|
requestAlarms(builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -276,4 +277,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
|
|||||||
byte[] bytes = gbDeviceProtocol.encodeReminders(reminders);
|
byte[] bytes = gbDeviceProtocol.encodeReminders(reminders);
|
||||||
sendToDevice(bytes);
|
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.NotificationSpec;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
|
||||||
|
|
||||||
public abstract class GBDeviceProtocol {
|
public abstract class GBDeviceProtocol {
|
||||||
|
|
||||||
@ -151,6 +152,10 @@ public abstract class GBDeviceProtocol {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] encodeWorldClocks(ArrayList<? extends WorldClock> clocks) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] encodeFmFrequency(float frequency) {
|
public byte[] encodeFmFrequency(float frequency) {
|
||||||
return null;
|
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:id="@+id/device_image"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_below="@id/device_item_infos_box"
|
android:layout_below="@id/device_item_infos_box"
|
||||||
android:contentDescription="@string/candidate_item_device_image"
|
android:layout_alignParentStart="true"
|
||||||
android:clickable="true"
|
|
||||||
android:longClickable="true"
|
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
card_view:srcCompat="@drawable/ic_device_pebble"
|
|
||||||
android:layout_marginTop="2dp"
|
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
|
<TextView
|
||||||
android:id="@+id/device_name"
|
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_charts">Activity and Sleep</string>
|
||||||
<string name="title_activity_set_alarm">Configure alarms</string>
|
<string name="title_activity_set_alarm">Configure alarms</string>
|
||||||
<string name="title_activity_set_reminders">Configure reminders</string>
|
<string name="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_alarms">Configure alarms</string>
|
||||||
<string name="controlcenter_start_configure_reminders">Configure reminders</string>
|
<string name="controlcenter_start_configure_reminders">Configure reminders</string>
|
||||||
<string name="reminder_repeat">Repeat</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_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_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="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_alarm_details">Alarm details</string>
|
||||||
<string name="title_activity_reminder_details">Reminder 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_sun_short">Sun</string>
|
||||||
<string name="alarm_mon_short">Mon</string>
|
<string name="alarm_mon_short">Mon</string>
|
||||||
<string name="alarm_tue_short">Tue</string>
|
<string name="alarm_tue_short">Tue</string>
|
||||||
|
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