1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-25 11:26:47 +01:00

created UI to design custom widgets

This commit is contained in:
Daniel Dakhno 2020-01-19 04:01:57 +01:00
parent 4c86d227f1
commit 132d97ee95
17 changed files with 991 additions and 107 deletions

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="nodomain.freeyourgadget.gadgetbridge"> package="nodomain.freeyourgadget.gadgetbridge">
<!-- <!--
Comment in for testing Pebble Emulator Comment in for testing Pebble Emulator
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@ -22,7 +21,6 @@
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.VIBRATE" /> <!-- Used for reverse find device --> <uses-permission android:name="android.permission.VIBRATE" /> <!-- Used for reverse find device -->
<uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" /> <uses-permission android:name="cyanogenmod.permission.ACCESS_WEATHER_MANAGER" />
<uses-permission android:name="cyanogenmod.permission.READ_WEATHER" /> <uses-permission android:name="cyanogenmod.permission.READ_WEATHER" />
<uses-permission android:name="lineageos.permission.ACCESS_WEATHER_MANAGER" /> <uses-permission android:name="lineageos.permission.ACCESS_WEATHER_MANAGER" />
@ -44,16 +42,19 @@
android:allowBackup="false" android:allowBackup="false"
android:fullBackupContent="false" android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/GadgetbridgeTheme"> android:theme="@style/GadgetbridgeTheme">
<activity android:name=".devices.qhybrid.WidgetSettingsActivity"></activity>
<activity <activity
android:name=".activities.ControlCenterv2" android:name=".activities.ControlCenterv2"
android:label="@string/title_activity_controlcenter" android:label="@string/title_activity_controlcenter"
android:theme="@style/SplashTheme"> android:theme="@style/SplashTheme">
<intent-filter> <intent-filter>
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
@ -78,9 +79,9 @@
android:label="@string/activity_summaries" android:label="@string/activity_summaries"
android:parentActivityName=".activities.ControlCenterv2" /> android:parentActivityName=".activities.ControlCenterv2" />
<activity <activity
android:launchMode="singleTop"
android:name=".activities.appmanager.AppManagerActivity" android:name=".activities.appmanager.AppManagerActivity"
android:label="@string/title_activity_appmanager" android:label="@string/title_activity_appmanager"
android:launchMode="singleTop"
android:parentActivityName=".activities.ControlCenterv2" /> android:parentActivityName=".activities.ControlCenterv2" />
<activity <activity
android:name=".activities.AppBlacklistActivity" android:name=".activities.AppBlacklistActivity"
@ -290,7 +291,7 @@
<data android:mimeType="application/octet-stream" /> <data android:mimeType="application/octet-stream" />
</intent-filter> </intent-filter>
<!-- to receive firmwares from the download content provider if recognized as zip--> <!-- to receive firmwares from the download content provider if recognized as zip -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -306,7 +307,6 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" /> <data android:mimeType="*/*" />
</intent-filter> </intent-filter>
</activity> </activity>
@ -331,18 +331,19 @@
<activity android:name=".externalevents.WeatherNotificationConfig"> <activity android:name=".externalevents.WeatherNotificationConfig">
<intent-filter> <intent-filter>
<action android:name="ru.gelin.android.weather.notification.ACTION_WEATHER_SKIN_PREFERENCES"/> <action android:name="ru.gelin.android.weather.notification.ACTION_WEATHER_SKIN_PREFERENCES" />
</intent-filter> </intent-filter>
</activity> </activity>
<receiver android:name=".externalevents.AutoStartReceiver" <receiver
android:name=".externalevents.AutoStartReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED"> android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver <receiver
android:name=".externalevents.BluetoothStateChangeReceiver" android:name=".externalevents.BluetoothStateChangeReceiver"
android:exported="false"> android:exported="false">
@ -365,12 +366,9 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver <receiver
android:name=".database.PeriodicExporter"
android:enabled="true" android:enabled="true"
android:name="nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter" android:exported="false"></receiver>
android:exported="false">
</receiver>
<!-- <!--
forcing the DebugActivity to portrait mode avoids crashes with the progress forcing the DebugActivity to portrait mode avoids crashes with the progress
dialog when changing orientation dialog when changing orientation
@ -429,8 +427,8 @@
<activity <activity
android:name=".activities.NotificationFilterActivity" android:name=".activities.NotificationFilterActivity"
android:label="@string/title_activity_notification_filter" android:label="@string/title_activity_notification_filter"
android:windowSoftInputMode="stateHidden|adjustPan" android:parentActivityName=".activities.AppBlacklistActivity"
android:parentActivityName=".activities.AppBlacklistActivity" /> android:windowSoftInputMode="stateHidden|adjustPan" />
<activity <activity
android:name=".activities.FindPhoneActivity" android:name=".activities.FindPhoneActivity"
android:label="Find Phone" /> android:label="Find Phone" />
@ -439,7 +437,6 @@
android:name=".contentprovider.PebbleContentProvider" android:name=".contentprovider.PebbleContentProvider"
android:authorities="com.getpebble.android.provider" android:authorities="com.getpebble.android.provider"
android:exported="true" /> android:exported="true" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.screenshot_provider" android:authorities="${applicationId}.screenshot_provider"
@ -450,7 +447,8 @@
android:resource="@xml/shared_paths" /> android:resource="@xml/shared_paths" />
</provider> </provider>
<receiver android:name=".SleepAlarmWidget" <receiver
android:name=".SleepAlarmWidget"
android:label="@string/appwidget_sleep_alarm_widget_label"> android:label="@string/appwidget_sleep_alarm_widget_label">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -461,7 +459,6 @@
android:name="android.appwidget.provider" android:name="android.appwidget.provider"
android:resource="@xml/sleep_alarm_widget_info" /> android:resource="@xml/sleep_alarm_widget_info" />
</receiver> </receiver>
<receiver <receiver
android:name=".Widget" android:name=".Widget"
android:label="@string/widget_listing_label"> android:label="@string/widget_listing_label">
@ -475,27 +472,28 @@
android:resource="@xml/widget_info" /> android:resource="@xml/widget_info" />
</receiver> </receiver>
<activity <activity
android:name=".activities.WidgetAlarmsActivity" android:name=".activities.WidgetAlarmsActivity"
android:excludeFromRecents="true"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:theme="@style/Theme.AppCompat.Light.Dialog" android:theme="@style/Theme.AppCompat.Light.Dialog" />
android:excludeFromRecents="true"/>
<activity <activity
android:launchMode="singleTask" android:name=".activities.ExternalPebbleJSActivity"
android:allowTaskReparenting="true" android:allowTaskReparenting="true"
android:clearTaskOnLaunch="true" android:clearTaskOnLaunch="true"
android:name=".activities.ExternalPebbleJSActivity"
android:label="@string/app_configure" android:label="@string/app_configure"
android:launchMode="singleTask"
android:parentActivityName=".activities.appmanager.AppManagerActivity"> android:parentActivityName=".activities.appmanager.AppManagerActivity">
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2" /> android:value="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="gadgetbridge" /> <data android:scheme="gadgetbridge" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

@ -1,15 +1,11 @@
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid; package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.SparseArray;
import android.view.Menu;
import android.view.TextureView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView; import android.widget.AdapterView;
@ -25,21 +21,33 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.w3c.dom.Text; import org.json.JSONObject;
import java.sql.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
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.AbstractGBActivity; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidget;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.Widget;
import static nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.WidgetSettingsActivity.RESULT_CODE_WIDGET_DELETED;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport.QHYBRID_COMMAND_UPDATE_WIDGETS;
public class HRConfigActivity extends AbstractGBActivity implements View.OnClickListener, DialogInterface.OnClickListener, AdapterView.OnItemClickListener { public class HRConfigActivity extends AbstractGBActivity implements View.OnClickListener, DialogInterface.OnClickListener, AdapterView.OnItemClickListener {
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private ActionListAdapter actionListAdapter; private ActionListAdapter actionListAdapter;
private WidgetListAdapter widgetListAdapter;
private ArrayList<MenuAction> menuActions = new ArrayList<>(); private ArrayList<MenuAction> menuActions = new ArrayList<>();
private ArrayList<CustomWidget> customWidgets = new ArrayList<>();
SparseArray<String> widgetButtonsMapping = new SparseArray<>(4);
static public final String CONFIG_KEY_Q_ACTIONS = "Q_ACTIONS"; static public final String CONFIG_KEY_Q_ACTIONS = "Q_ACTIONS";
@ -52,14 +60,266 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
sharedPreferences = GBApplication.getPrefs().getPreferences(); sharedPreferences = GBApplication.getPrefs().getPreferences();
initMappings();
loadWidgetConfigs();
ListView actionListView = findViewById(R.id.qhybrid_action_list); ListView actionListView = findViewById(R.id.qhybrid_action_list);
actionListAdapter = new ActionListAdapter(menuActions); actionListAdapter = new ActionListAdapter(menuActions);
actionListView.setAdapter(actionListAdapter); actionListView.setAdapter(actionListAdapter);
actionListView.setOnItemClickListener(this); actionListView.setOnItemClickListener(this);
final ListView widgetListView = findViewById(R.id.qhybrid_widget_list);
widgetListAdapter = new WidgetListAdapter(customWidgets);
widgetListView.setAdapter(widgetListAdapter);
widgetListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Widget widget = widgetListAdapter.getItem(position);
Intent startIntent = new Intent(HRConfigActivity.this, WidgetSettingsActivity.class);
startIntent.putExtra("EXTRA_WIDGET", widget);
startIntent.putExtra("EXTRA_WIDGET_IDNEX", position);
startActivityForResult(startIntent, 0);
}
});
loadCustomWidgetList();
findViewById(R.id.qhybrid_widget_add).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent startIntent = new Intent(HRConfigActivity.this, WidgetSettingsActivity.class);
startActivityForResult(startIntent, 0);
}
});
for (int i = 0; i < widgetButtonsMapping.size(); i++) {
final int widgetButtonId = widgetButtonsMapping.keyAt(i);
findViewById(widgetButtonId).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Widget.WidgetType[] types = Widget.WidgetType.values();
final ArrayList<String> names = new ArrayList<>(types.length);
for (Widget.WidgetType type : types) {
names.add(getResources().getString(type.getStringResource()));
}
for(CustomWidget customWidget : customWidgets){
names.add(customWidget.getName());
}
final String[] nameStrings = names.toArray(new String[0]);
new AlertDialog.Builder(HRConfigActivity.this)
.setItems(
nameStrings,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
saveWidgetSetting(widgetButtonId, which, nameStrings);
}
}
)
.show();
}
});
}
updateSettings(); updateSettings();
} }
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(data == null) return;
if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_CREATED) {
CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET");
this.customWidgets.add(widget);
refreshWidgetList();
saveCustomWidgetList();
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
} else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_UPDATED) {
CustomWidget widget = (CustomWidget) data.getExtras().get("EXTRA_WIDGET");
int updateIndex = data.getIntExtra("EXTRA_WIDGET_IDNEX", -1);
this.customWidgets.set(updateIndex, widget);
refreshWidgetList();
saveCustomWidgetList();
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
} else if (resultCode == WidgetSettingsActivity.RESULT_CODE_WIDGET_DELETED){
int updateIndex = data.getIntExtra("EXTRA_WIDGET_IDNEX", -1);
this.customWidgets.remove(updateIndex);
refreshWidgetList();
saveCustomWidgetList();
LocalBroadcastManager.getInstance(HRConfigActivity.this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
}
}
private void saveCustomWidgetList() {
try {
JSONArray widgetArray = new JSONArray();
for(CustomWidget widget : customWidgets){
JSONArray elementArray = new JSONArray();
for(CustomWidgetElement element : widget.getElements()){
JSONObject elementObject = new JSONObject();
elementObject
.put("type", element.getWidgetElementType().getJsonIdentifier())
.put("id", element.getId())
.put("value", element.getValue())
.put("x", element.getX())
.put("y", element.getY());
elementArray.put(elementObject);
}
JSONObject widgetObject = new JSONObject();
widgetObject
.put("name", widget.getName())
.put("elements", elementArray);
widgetArray.put(widgetObject);
}
sharedPreferences.edit().putString("QHYBRID_CUSTOM_WIDGETS", widgetArray.toString()).apply();
} catch (JSONException e) {
e.printStackTrace();
}
}
private void loadCustomWidgetList() {
String customWidgetJson = sharedPreferences.getString("QHYBRID_CUSTOM_WIDGETS", "[]");
try {
JSONArray customWidgets = new JSONArray(customWidgetJson);
this.customWidgets.clear();
for (int i = 0; i < customWidgets.length(); i++) {
JSONObject customWidgetObject = customWidgets.getJSONObject(i);
CustomWidget widget = new CustomWidget(
customWidgetObject.getString("name"), 0, 0
);
JSONArray elements = customWidgetObject.getJSONArray("elements");
for (int i2 = 0; i2 < elements.length(); i2++) {
JSONObject element = elements.getJSONObject(i2);
if (element.getString("type").equals("text")) {
widget.addElement(new CustomTextWidgetElement(
element.getString("id"),
element.getString("value"),
element.getInt("x"),
element.getInt("y")
));
} else if (element.getString("type").equals("background")) {
widget.addElement(new CustomBackgroundWidgetElement(
element.getString("id"),
element.getString("value")
));
}
}
this.customWidgets.add(widget);
}
refreshWidgetList();
} catch (JSONException e) {
e.printStackTrace();
}
}
private void refreshWidgetList() {
widgetListAdapter.notifyDataSetChanged();
}
private void saveWidgetSetting(int buttonId, int option, String[] names) {
String jsonKey = widgetButtonsMapping.get(buttonId);
Widget.WidgetType[] types = Widget.WidgetType.values();
String identifier = null;
if(option < types.length){
Widget.WidgetType type = types[option];
identifier = type.getIdentifier();
}else{
identifier = "custom_" + names[option];
}
try {
JSONObject keyConfig = new JSONObject(sharedPreferences.getString("FOSSIL_HR_WIDGETS", "{}"));
if (identifier != null) {
keyConfig.put(jsonKey, identifier);
} else {
keyConfig.remove(jsonKey);
}
sharedPreferences.edit().putString("FOSSIL_HR_WIDGETS", keyConfig.toString()).apply();
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(QHYBRID_COMMAND_UPDATE_WIDGETS));
loadWidgetConfigs();
} catch (JSONException e) {
e.printStackTrace();
}
}
private void loadWidgetConfigs() {
try {
for (int i = 0; i < widgetButtonsMapping.size(); i++) {
((TextView) findViewById(widgetButtonsMapping.keyAt(i))).setText(widgetButtonsMapping.valueAt(i) + " widget");
}
JSONObject keyConfig = new JSONObject(sharedPreferences.getString("FOSSIL_HR_WIDGETS", "{}"));
Iterator<String> keyIterator = keyConfig.keys();
loop:
while (keyIterator.hasNext()) {
String position = keyIterator.next();
for (int widgetButtonIndex = 0; widgetButtonIndex < widgetButtonsMapping.size(); widgetButtonIndex++) {
if (position.equals(widgetButtonsMapping.valueAt(widgetButtonIndex))) {
int buttonId = widgetButtonsMapping.keyAt(widgetButtonIndex);
String function = keyConfig.getString(position);
Widget.WidgetType[] types = Widget.WidgetType.values();
if(function.startsWith("custom_")){
((TextView) findViewById(buttonId)).setText(
position + " widget: " + function.substring(7)
);
continue loop;
}
for (int widgetIdIndex = 0; widgetIdIndex < types.length; widgetIdIndex++) {
String widgetIdMappingValue = types[widgetIdIndex].getIdentifier();
if (widgetIdMappingValue != null && widgetIdMappingValue.equals(function)) {
((TextView) findViewById(buttonId)).setText(
position + " widget: "
+ getResources().getText(
types[widgetIdIndex].getStringResource()
)
);
break;
}
}
}
}
}
} catch (JSONException e) {
e.printStackTrace();
}
}
private void initMappings() {
widgetButtonsMapping.put(R.id.qhybrid_button_widget_top, "top");
widgetButtonsMapping.put(R.id.qhybrid_button_widget_right, "right");
widgetButtonsMapping.put(R.id.qhybrid_button_widget_bottom, "bottom");
widgetButtonsMapping.put(R.id.qhybrid_button_widget_left, "left");
}
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (v.getId() == R.id.qhybrid_action_add) { if (v.getId() == R.id.qhybrid_action_add) {
@ -147,7 +407,7 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
.show(); .show();
} }
private void putActionItems(List<MenuAction> actions){ private void putActionItems(List<MenuAction> actions) {
JSONArray array = new JSONArray(); JSONArray array = new JSONArray();
for (MenuAction action : actions) array.put(action.getAction()); for (MenuAction action : actions) array.put(action.getAction());
@ -170,6 +430,25 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
} }
} }
class WidgetListAdapter extends ArrayAdapter<CustomWidget> {
public WidgetListAdapter(@NonNull List<CustomWidget> objects) {
super(HRConfigActivity.this, 0, objects);
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
if (convertView == null) convertView = new TextView(getContext());
TextView view = (TextView) convertView;
view.setText(getItem(position).getName());
// view.setTextColor(Color.WHITE);
view.setTextSize(25);
return view;
}
}
class ActionListAdapter extends ArrayAdapter<MenuAction> { class ActionListAdapter extends ArrayAdapter<MenuAction> {
public ActionListAdapter(@NonNull ArrayList<MenuAction> objects) { public ActionListAdapter(@NonNull ArrayList<MenuAction> objects) {
super(HRConfigActivity.this, 0, objects); super(HRConfigActivity.this, 0, objects);
@ -183,7 +462,7 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
view.setText(getItem(position).getAction()); view.setText(getItem(position).getAction());
// view.setTextColor(Color.WHITE); // view.setTextColor(Color.WHITE);
view.setTextSize(30); view.setTextSize(25);
return view; return view;
} }

View File

@ -0,0 +1,208 @@
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.Widget;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidget;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidgetElement;
public class WidgetSettingsActivity extends AbstractGBActivity {
private CustomWidget subject;
private WidgetElementAdapter widgetElementAdapter;
public static final int RESULT_CODE_WIDGET_CREATED = 0;
public static final int RESULT_CODE_WIDGET_UPDATED = 1;
public static final int RESULT_CODE_WIDGET_DELETED = 2;
public static final int RESULT_CODE_CANCELED = 3;
private int resultCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.qhybrid_activity_widget_settings);
setResult(RESULT_CODE_CANCELED);
if(getIntent().hasExtra("EXTRA_WIDGET")){
subject = (CustomWidget) getIntent().getExtras().get("EXTRA_WIDGET");
((EditText) findViewById(R.id.qhybrid_widget_name)).setText(subject.getName());
resultCode = RESULT_CODE_WIDGET_UPDATED;
}else{
subject = new CustomWidget("", 0, 63);
resultCode = RESULT_CODE_WIDGET_CREATED;
findViewById(R.id.qhybrid_widget_delete).setEnabled(false);
}
findViewById(R.id.qhybrid_widget_save).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
subject.setName(((EditText) findViewById(R.id.qhybrid_widget_name)).getText().toString());
Intent resultIntent = getIntent();
resultIntent.putExtra("EXTRA_WIDGET", WidgetSettingsActivity.this.subject);
setResult(resultCode, resultIntent);
finish();
}
});
findViewById(R.id.qhybrid_widget_delete).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setResult(RESULT_CODE_WIDGET_DELETED, getIntent());
finish();
}
});
widgetElementAdapter = new WidgetElementAdapter(subject.getElements());
ListView elementList = findViewById(R.id.qhybrid_widget_elements_list);
elementList.setAdapter(widgetElementAdapter);
elementList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
showElementDialog(widgetElementAdapter.getItem(position));
}
});
findViewById(R.id.qhybrid_widget_elements_add).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showElementDialog(null);
}
});
}
private void showElementDialog(@Nullable final CustomWidgetElement element){
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(WidgetSettingsActivity.this)
.setView(R.layout.qhybrid_element_popup_view);
if(element == null) {
dialogBuilder
.setTitle("create element")
.setNegativeButton("cancel", null)
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if(((RadioButton)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_elements_type_text)).isChecked()){
subject.addElement(new CustomTextWidgetElement(
((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_id)).getText().toString(),
((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_value)).getText().toString(),
CustomWidgetElement.X_CENTER,
((RadioButton)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_elements_position_uppper)).isChecked() ? CustomTextWidgetElement.Y_UPPER_HALF : CustomTextWidgetElement.Y_LOWER_HALF
));
}else{
subject.addElement(new CustomBackgroundWidgetElement(
((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_id)).getText().toString(),
((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_value)).getText().toString()
));
}
refreshElementsList();
}
});
}else{
dialogBuilder
.setTitle("edit element")
.setNegativeButton("delete", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
WidgetSettingsActivity.this.subject.getElements().remove(element);
refreshElementsList();
}
})
.setPositiveButton("ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
element.setId(((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_id)).getText().toString());
element.setValue(((EditText)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_value)).getText().toString());
element.setY(((RadioButton)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_elements_position_uppper)).isChecked() ? CustomTextWidgetElement.Y_UPPER_HALF : CustomTextWidgetElement.Y_LOWER_HALF);
element.setWidgetElementType(CustomWidgetElement.WidgetElementType.fromRadioButtonRessource(((RadioGroup)((AlertDialog)dialog).findViewById(R.id.qhybrid_widget_element_type)).getCheckedRadioButtonId()));
refreshElementsList();
}
});
}
AlertDialog dialog = dialogBuilder.show();
if(element != null){
String elementId = element.getId();
String elementValue = element.getValue();
CustomWidgetElement.WidgetElementType type = element.getWidgetElementType();
((EditText)dialog.findViewById(R.id.qhybrid_widget_element_id)).setText(elementId);
((EditText)dialog.findViewById(R.id.qhybrid_widget_element_value)).setText(elementValue);
((RadioGroup)dialog.findViewById(R.id.qhybrid_widget_element_type)).check(type.getRadioButtonResource());
((RadioGroup)dialog.findViewById(R.id.qhybrid_widget_element_position)).check(element.getY() == CustomWidgetElement.Y_UPPER_HALF ? R.id.qhybrid_widget_elements_position_uppper : R.id.qhybrid_widget_elements_position_lower);
}
}
private void refreshElementsList(){
this.widgetElementAdapter.notifyDataSetChanged();
}
class WidgetElementAdapter extends ArrayAdapter<CustomWidgetElement>{
public WidgetElementAdapter(@NonNull List<CustomWidgetElement> objects) {
super(WidgetSettingsActivity.this, 0, objects);
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
RelativeLayout layout = new RelativeLayout(WidgetSettingsActivity.this);
layout.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
TextView idView = new TextView(WidgetSettingsActivity.this);
idView.setText(getItem(position).getId());
// view.setTextColor(Color.WHITE);
idView.setTextSize(25);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_START, RelativeLayout.TRUE);
idView.setLayoutParams(params);
TextView contentView = new TextView(WidgetSettingsActivity.this);
contentView.setText(getItem(position).getValue());
contentView.setTextSize(25);
params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_END, RelativeLayout.TRUE);
contentView.setLayoutParams(params);
layout.addView(idView);
layout.addView(contentView);
return layout;
}
}
}

View File

@ -87,6 +87,7 @@ public class QHybridSupport extends QHybridBaseSupport {
public static final String QHYBRID_COMMAND_NOTIFICATION = "qhybrid_command_notification"; public static final String QHYBRID_COMMAND_NOTIFICATION = "qhybrid_command_notification";
public static final String QHYBRID_COMMAND_UPDATE_SETTINGS = "nodomain.freeyourgadget.gadgetbridge.Q_UPDATE_SETTINGS"; public static final String QHYBRID_COMMAND_UPDATE_SETTINGS = "nodomain.freeyourgadget.gadgetbridge.Q_UPDATE_SETTINGS";
public static final String QHYBRID_COMMAND_OVERWRITE_BUTTONS = "nodomain.freeyourgadget.gadgetbridge.Q_OVERWRITE_BUTTONS"; public static final String QHYBRID_COMMAND_OVERWRITE_BUTTONS = "nodomain.freeyourgadget.gadgetbridge.Q_OVERWRITE_BUTTONS";
public static final String QHYBRID_COMMAND_UPDATE_WIDGETS = "nodomain.freeyourgadget.gadgetbridge.Q_UPDATE_WIDGETS";
public static final String QHYBRID_COMMAND_SET_MENU_MESSAGE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_MENU_MESSAGE"; public static final String QHYBRID_COMMAND_SET_MENU_MESSAGE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_MENU_MESSAGE";
public static final String QHYBRID_COMMAND_SEND_MENU_ITEMS = "nodomain.freeyourgadget.gadgetbridge.Q_SEND_MENU_ITEMS"; public static final String QHYBRID_COMMAND_SEND_MENU_ITEMS = "nodomain.freeyourgadget.gadgetbridge.Q_SEND_MENU_ITEMS";
public static final String QHYBRID_COMMAND_SET_WIDGET_CONTENT = "nodomain.freeyourgadget.gadgetbridge.Q_SET_WIDGET_CONTENT"; public static final String QHYBRID_COMMAND_SET_WIDGET_CONTENT = "nodomain.freeyourgadget.gadgetbridge.Q_SET_WIDGET_CONTENT";
@ -143,6 +144,7 @@ public class QHybridSupport extends QHybridBaseSupport {
commandFilter.addAction(QHYBRID_COMMAND_UPDATE_SETTINGS); commandFilter.addAction(QHYBRID_COMMAND_UPDATE_SETTINGS);
commandFilter.addAction(QHYBRID_COMMAND_OVERWRITE_BUTTONS); commandFilter.addAction(QHYBRID_COMMAND_OVERWRITE_BUTTONS);
commandFilter.addAction(QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED); commandFilter.addAction(QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED);
commandFilter.addAction(QHYBRID_COMMAND_UPDATE_WIDGETS);
commandFilter.addAction(QHYBRID_COMMAND_SEND_MENU_ITEMS); commandFilter.addAction(QHYBRID_COMMAND_SEND_MENU_ITEMS);
commandReceiver = new BroadcastReceiver() { commandReceiver = new BroadcastReceiver() {
@ -217,6 +219,10 @@ public class QHybridSupport extends QHybridBaseSupport {
watchAdapter.syncNotificationSettings(); watchAdapter.syncNotificationSettings();
break; break;
} }
case QHYBRID_COMMAND_UPDATE_WIDGETS: {
watchAdapter.updateWidgets();
break;
}
} }
} }
}; };
@ -278,6 +284,7 @@ public class QHybridSupport extends QHybridBaseSupport {
widgetValues.put(key.substring(16), String.valueOf(intent.getExtras().get(key))); widgetValues.put(key.substring(16), String.valueOf(intent.getExtras().get(key)));
} }
} }
boolean render = intent.getBooleanExtra("EXTRA_RENDER", true);
if(widgetValues.size() > 0){ if(widgetValues.size() > 0){
Iterator<String> valuesIterator = widgetValues.keySet().iterator(); Iterator<String> valuesIterator = widgetValues.keySet().iterator();
valuesIterator.next(); valuesIterator.next();
@ -289,11 +296,10 @@ public class QHybridSupport extends QHybridBaseSupport {
valuesIterator = widgetValues.keySet().iterator(); valuesIterator = widgetValues.keySet().iterator();
String id = valuesIterator.next(); String id = valuesIterator.next();
watchAdapter.setWidgetContent(id, widgetValues.get(id), true); watchAdapter.setWidgetContent(id, widgetValues.get(id), render);
}else { }else {
String id = String.valueOf(intent.getExtras().get("EXTRA_WIDGET_ID")); String id = String.valueOf(intent.getExtras().get("EXTRA_WIDGET_ID"));
String content = String.valueOf(intent.getExtras().get("EXTRA_CONTENT")); String content = String.valueOf(intent.getExtras().get("EXTRA_CONTENT"));
boolean render = intent.getBooleanExtra("EXTRA_RENDER", true);
watchAdapter.setWidgetContent(id, content, render); watchAdapter.setWidgetContent(id, content, render);
} }
break; break;
@ -352,7 +358,7 @@ public class QHybridSupport extends QHybridBaseSupport {
.read(getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb"))) .read(getCharacteristic(UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb")))
.read(getCharacteristic(UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb"))) .read(getCharacteristic(UUID.fromString("00002a26-0000-1000-8000-00805f9b34fb")))
.read(getCharacteristic(UUID.fromString("00002a24-0000-1000-8000-00805f9b34fb"))) .read(getCharacteristic(UUID.fromString("00002a24-0000-1000-8000-00805f9b34fb")))
.notify(getCharacteristic(UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")), true) // .notify(getCharacteristic(UUID.fromString("00002a37-0000-1000-8000-00805f9b34fb")), true)
; ;
@ -474,6 +480,12 @@ public class QHybridSupport extends QHybridBaseSupport {
return notificationProgress; return notificationProgress;
} }
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
watchAdapter.onConnectionStateChange(gatt, status, newState);
}
//TODO toggle "Notifications when screen on" options on this check //TODO toggle "Notifications when screen on" options on this check
private void showNotificationCountOnActivityHand(double progress) { private void showNotificationCountOnActivityHand(double progress) {
if (useActivityHand) { if (useActivityHand) {
@ -612,6 +624,12 @@ public class QHybridSupport extends QHybridBaseSupport {
} }
@Override
public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
watchAdapter.onCharacteristicWrite(gatt, characteristic, status);
return super.onCharacteristicWrite(gatt, characteristic, status);
}
@Override @Override
public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { public boolean onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
switch (characteristic.getUuid().toString()) { switch (characteristic.getUuid().toString()) {

View File

@ -108,4 +108,13 @@ public abstract class WatchAdapter {
public void setWidgetContent(String widgetID, String content, boolean render) { public void setWidgetContent(String widgetID, String content, boolean render) {
} }
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
}
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
}
public void updateWidgets() {
}
} }

View File

@ -43,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.PackageConfigHelper;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem; import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BTLEOperation;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter;
@ -130,6 +131,25 @@ public class FossilWatchAdapter extends WatchAdapter {
overwriteButtons(buttonConfig); overwriteButtons(buttonConfig);
} }
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if(status != BluetoothGatt.GATT_SUCCESS){
log("characteristic write failed: " + status);
fossilRequest = null;
queueNextRequest();
}
}
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if(newState != BluetoothGatt.STATE_CONNECTED){
log("status " + newState + " clearing queue...");
requestQueue.clear();
fossilRequest = null;
}
}
private SharedPreferences getDeviceSpecificPreferences(){ private SharedPreferences getDeviceSpecificPreferences(){
return GBApplication.getDeviceSpecificSharedPrefs( return GBApplication.getDeviceSpecificSharedPrefs(
getDeviceSupport().getDevice().getAddress() getDeviceSupport().getDevice().getAddress()

View File

@ -9,47 +9,39 @@ import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.os.Build; import android.os.Build;
import android.os.CpuUsageInfo;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.Random; import java.util.HashMap;
import java.util.Iterator;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.UUID;
import cyanogenmod.app.CustomTile;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.Widget;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HRConfigActivity; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.HRConfigActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationHRConfiguration; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationHRConfiguration;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.RequestMtuRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.RequestMtuRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.SetDeviceStateRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.SetDeviceStateRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.TimeConfigItem; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.TimeConfigItem;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileCloseRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileDeleteRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayNotificationRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.VerifyPrivateKeyRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.VerifyPrivateKeyRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfigurationPutRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfigurationPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationGetRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationPutRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFile;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFilePutRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFilePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImage; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImageFactory; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImageFactory;
@ -58,14 +50,12 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicInfoSetRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicInfoSetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationFilterPutHRRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationFilterPutHRRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationImage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.NotificationImagePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.notification.PlayNotificationHRRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomBackgroundWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomTextWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidget; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidget;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidgetElement; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.CustomWidgetElement;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.utils.StringUtils; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.Widget;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget.WidgetsPutRequest;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest.MUSIC_PHONE_REQUEST; import static nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.music.MusicControlRequest.MUSIC_PHONE_REQUEST;
@ -75,7 +65,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
private byte[] phoneRandomNumber; private byte[] phoneRandomNumber;
private byte[] watchRandomNumber; private byte[] watchRandomNumber;
CustomWidget[] widgets = new CustomWidget[0]; ArrayList<Widget> widgets = new ArrayList<>();
NotificationHRConfiguration[] notificationConfigurations; NotificationHRConfiguration[] notificationConfigurations;
@ -176,37 +166,87 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
.decodeFile("/sdcard/DCIM/Camera/IMG_20191129_200726.jpg"); .decodeFile("/sdcard/DCIM/Camera/IMG_20191129_200726.jpg");
try { try {
this.backGroundImage = AssetImageFactory.createAssetImage(backgroundBitmap, false, 0, 0, 0); this.backGroundImage = AssetImageFactory.createAssetImage(backgroundBitmap, false, 0,:wq
0, 0);
} catch (IOException e) { } catch (IOException e) {
GB.log("Backgroundimage error", GB.ERROR, e); GB.log("Backgroundimage error", GB.ERROR, e);
}*/ }*/
} }
private void loadWidgets() { private void loadWidgets() {
CustomWidget ethWidget = new CustomWidget(90, 63); this.widgets.clear();
// ethWidget.addElement(new CustomWidgetElement(CustomWidgetElement.WidgetElementType.TYPE_TEXT, "date", "-", CustomWidgetElement.X_CENTER, CustomWidgetElement.Y_UPPER_HALF)); String widgetJson = GBApplication.getPrefs().getPreferences().getString("FOSSIL_HR_WIDGETS", "{}");
ethWidget.addElement(new CustomTextWidgetElement("ETH", CustomWidgetElement.X_CENTER, CustomWidgetElement.Y_UPPER_HALF)); String customWidgetJson = GBApplication.getPrefs().getString("QHYBRID_CUSTOM_WIDGETS", "[]");
ethWidget.addElement(new CustomTextWidgetElement("eth", "-", CustomWidgetElement.X_CENTER, CustomWidgetElement.Y_LOWER_HALF));
try {
JSONObject widgetConfig = new JSONObject(widgetJson);
JSONArray customWidgets = new JSONArray(customWidgetJson);
CustomWidget btcWidget = new CustomWidget(270, 63); Iterator<String> keyIterator = widgetConfig.keys();
btcWidget.addElement(new CustomTextWidgetElement("BTC", CustomWidgetElement.X_CENTER, CustomWidgetElement.Y_UPPER_HALF)); HashMap<String, Integer> positionMap = new HashMap<>(4);
btcWidget.addElement(new CustomTextWidgetElement("btc", "-", CustomWidgetElement.X_CENTER, CustomWidgetElement.Y_LOWER_HALF)); positionMap.put("top", 0);
positionMap.put("right", 90);
positionMap.put("bottom", 180);
positionMap.put("left", 270);
CustomWidget dateWidget = new CustomWidget(0, 63); while(keyIterator.hasNext()){
dateWidget.addElement(new CustomTextWidgetElement("Time", CustomWidgetElement.X_CENTER, CustomWidgetElement.Y_UPPER_HALF)); String position = keyIterator.next();
dateWidget.addElement(new CustomTextWidgetElement("date", "-", CustomWidgetElement.X_CENTER, CustomWidgetElement.Y_LOWER_HALF)); String identifier = widgetConfig.getString(position);
Widget.WidgetType type = Widget.WidgetType.fromJsonIdentifier(identifier);
CustomWidget helloWidget = new CustomWidget(180, 63); Widget widget = null;
// helloWidget.addElement(new CustomTextWidgetElement("Hello", CustomWidgetElement.X_CENTER, CustomWidgetElement.Y_UPPER_HALF)); if(type != null) {
// helloWidget.addElement(new CustomTextWidgetElement("Reddit", CustomWidgetElement.X_CENTER, CustomWidgetElement.Y_LOWER_HALF)); widget = new Widget(type, positionMap.get(position), 63);
helloWidget.addElement(new CustomBackgroundWidgetElement("reddit", "/sdcard/reddit.png")); }else{
this.widgets = new CustomWidget[]{ identifier = identifier.substring(7);
ethWidget, for(int i = 0; i < customWidgets.length(); i++){
btcWidget, JSONObject customWidget = customWidgets.getJSONObject(i);
dateWidget, if(customWidget.getString("name").equals(identifier)){
helloWidget CustomWidget newWidget = new CustomWidget(
}; customWidget.getString("name"),
positionMap.get(position),
63
);
JSONArray elements = customWidget.getJSONArray("elements");
for (int i2 = 0; i2 < elements.length(); i2++) {
JSONObject element = elements.getJSONObject(i2);
if (element.getString("type").equals("text")) {
newWidget.addElement(new CustomTextWidgetElement(
element.getString("id"),
element.getString("value"),
element.getInt("x"),
element.getInt("y")
));
} else if (element.getString("type").equals("background")) {
newWidget.addElement(new CustomBackgroundWidgetElement(
element.getString("id"),
element.getString("value")
));
}
}
widget = newWidget;
}
}
}
if(widget == null) continue;
this.widgets.add(widget);
}
} catch (JSONException e) {
e.printStackTrace();
}
uploadWidgets();
}
private void uploadWidgets(){
negotiateSymmetricKey();
ArrayList<Widget> systemWidgets = new ArrayList<>(widgets.size());
for(Widget widget : this.widgets){
if(!(widget instanceof CustomWidget)) systemWidgets.add(widget);
}
queueWrite(new WidgetsPutRequest(systemWidgets.toArray(new Widget[0]), this));
} }
private void renderWidgets() { private void renderWidgets() {
@ -218,8 +258,10 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
} }
for (int i = 0; i < this.widgets.length; i++) { for (int i = 0; i < this.widgets.size(); i++) {
CustomWidget widget = widgets[i]; Widget w = widgets.get(i);
if(!(w instanceof CustomWidget)) continue;
CustomWidget widget = (CustomWidget) w;
Bitmap widgetBitmap = Bitmap.createBitmap(76, 76, Bitmap.Config.ARGB_8888); Bitmap widgetBitmap = Bitmap.createBitmap(76, 76, Bitmap.Config.ARGB_8888);
@ -312,8 +354,9 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
@Override @Override
public void setWidgetContent(String widgetID, String content, boolean renderOnWatch) { public void setWidgetContent(String widgetID, String content, boolean renderOnWatch) {
boolean update = false; boolean update = false;
for (CustomWidget widget : this.widgets) { for (Widget widget : this.widgets) {
if(widget.updateElementValue(widgetID, content)) update = true; if(!(widget instanceof CustomWidget)) continue;
if(((CustomWidget) widget).updateElementValue(widgetID, content)) update = true;
} }
if (renderOnWatch && update) renderWidgets(); if (renderOnWatch && update) renderWidgets();
@ -374,6 +417,12 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
)); ));
} }
@Override
public void updateWidgets() {
loadWidgets();
renderWidgets();
}
private void setBackgroundImages(AssetImage background, AssetImage[] complications) { private void setBackgroundImages(AssetImage background, AssetImage[] complications) {
queueWrite(new ImagesSetRequest(new AssetImage[]{background}, this)); queueWrite(new ImagesSetRequest(new AssetImage[]{background}, this));
} }

View File

@ -8,6 +8,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSuppo
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedLookupAndGetRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedLookupAndGetRequest;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class ConfigurationGetRequest extends FileEncryptedLookupAndGetRequest { public class ConfigurationGetRequest extends FileEncryptedLookupAndGetRequest {
public ConfigurationGetRequest(FossilHRWatchAdapter adapter) { public ConfigurationGetRequest(FossilHRWatchAdapter adapter) {
@ -49,6 +50,8 @@ public class ConfigurationGetRequest extends FileEncryptedLookupAndGetRequest {
} }
} }
GB.toast("got config", 0, GB.INFO);
device.sendDeviceUpdateIntent(getAdapter().getContext()); device.sendDeviceUpdateIntent(getAdapter().getContext());
} }
} }

View File

@ -9,6 +9,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos
public class JsonPutRequest extends FilePutRawRequest { public class JsonPutRequest extends FilePutRawRequest {
public JsonPutRequest(JSONObject object, FossilHRWatchAdapter adapter) { public JsonPutRequest(JSONObject object, FossilHRWatchAdapter adapter) {
super((short)(0x0500 | adapter.getJsonIndex()), object.toString().getBytes(), adapter); super((short)(0x0500 | (adapter.getJsonIndex() & 0xFF)), object.toString().getBytes(), adapter);
} }
} }

View File

@ -5,13 +5,16 @@ import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
public class CustomWidget { public class CustomWidget extends Widget {
private ArrayList<CustomWidgetElement> elements = new ArrayList<>(); private ArrayList<CustomWidgetElement> elements = new ArrayList<>();
private int angle, distance; private int angle, distance;
private String name;
public CustomWidget(int angle, int distance) { public CustomWidget(String name, int angle, int distance) {
super(null, angle, distance);
this.angle = angle; this.angle = angle;
this.distance = distance; this.distance = distance;
this.name = name;
} }
public int getAngle() { public int getAngle() {
@ -22,7 +25,23 @@ public class CustomWidget {
return distance; return distance;
} }
public Collection<CustomWidgetElement> getElements(){ public void setElements(ArrayList<CustomWidgetElement> elements) {
this.elements = elements;
}
public void setAngle(int angle) {
this.angle = angle;
}
public void setDistance(int distance) {
this.distance = distance;
}
public void setName(String name) {
this.name = name;
}
public ArrayList<CustomWidgetElement> getElements(){
return this.elements; return this.elements;
} }
@ -49,4 +68,8 @@ public class CustomWidget {
} }
return null; return null;
} }
public String getName() {
return name;
}
} }

View File

@ -1,10 +1,37 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget; package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.widget;
public class CustomWidgetElement { import java.io.Serializable;
import nodomain.freeyourgadget.gadgetbridge.R;
public class CustomWidgetElement implements Serializable {
public enum WidgetElementType { public enum WidgetElementType {
TYPE_TEXT, TYPE_TEXT(R.id.qhybrid_widget_elements_type_text, "text"),
TYPE_IMAGE, TYPE_IMAGE(0, "image"),
TYPE_BACKGROUND TYPE_BACKGROUND(R.id.qhybrid_widget_elements_type_background, "background");
private String jsonIdentifier;
private int radioButtonResource;
WidgetElementType(int radioButtonResource, String jsonIdentifier){
this.radioButtonResource = radioButtonResource;
this.jsonIdentifier = jsonIdentifier;
}
public int getRadioButtonResource() {
return radioButtonResource;
}
public String getJsonIdentifier() {
return jsonIdentifier;
}
static public WidgetElementType fromRadioButtonRessource(int radioButtonResource){
for(WidgetElementType type : values()){
if(type.getRadioButtonResource() == radioButtonResource) return type;
}
return null;
}
} }
public final static int X_CENTER = 38; public final static int X_CENTER = 38;
@ -15,6 +42,22 @@ public class CustomWidgetElement {
private String id, value; private String id, value;
private int x, y; private int x, y;
public void setWidgetElementType(WidgetElementType widgetElementType) {
this.widgetElementType = widgetElementType;
}
public void setId(String id) {
this.id = id;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
protected CustomWidgetElement(WidgetElementType widgetElementType, String id, String value, int x, int y) { protected CustomWidgetElement(WidgetElementType widgetElementType, String id, String value, int x, int y) {
this.widgetElementType = widgetElementType; this.widgetElementType = widgetElementType;
this.id = id; this.id = id;

View File

@ -5,12 +5,18 @@ import androidx.annotation.NonNull;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
public abstract class Widget { import java.io.Serializable;
import nodomain.freeyourgadget.gadgetbridge.R;
public class Widget implements Serializable {
private WidgetType widgetType; private WidgetType widgetType;
int angle, distance; int angle, distance;
public Widget(WidgetType type, int angle, int distance){ public Widget(WidgetType type, int angle, int distance) {
this.widgetType = type; this.widgetType = type;
this.angle = angle;
this.distance = distance;
} }
@NonNull @NonNull
@ -19,7 +25,7 @@ public abstract class Widget {
return toJson().toString(); return toJson().toString();
} }
public JSONObject toJson(){ public JSONObject toJson() {
JSONObject object = new JSONObject(); JSONObject object = new JSONObject();
try { try {
@ -43,17 +49,35 @@ public abstract class Widget {
} }
enum WidgetType{ public enum WidgetType {
TIMEZONE("timeZone2SSE"); HEART_RATE("hrSSE", R.string.hr_widget_heart_rate),
STEPS("stepsSSE", R.string.hr_widget_steps),
DATE("dateSSE", R.string.hr_widget_date),
ACTIVE_MINUTES("activeMinutesSSE", R.string.hr_widget_active_minutes),
CALORIES("caloriesSSE", R.string.hr_widget_calories),
BATTERY("batterySSE", R.string.hr_widget_battery),
NOTHING(null, R.string.hr_widget_nothing);
private String identifier; private String identifier;
private int stringResource;
WidgetType(String identifier, int stringResource) {
WidgetType(String identifier){
this.identifier = identifier; this.identifier = identifier;
this.stringResource = stringResource;
} }
public String getIdentifier(){ public static WidgetType fromJsonIdentifier(String jsonIdentifier){
for(WidgetType type : values()){
if(type.getIdentifier() != null && type.getIdentifier().equals(jsonIdentifier)) return type;
}
return null;
}
public int getStringResource() {
return stringResource;
}
public String getIdentifier() {
return this.identifier; return this.identifier;
} }
} }

View File

@ -16,7 +16,11 @@ public class WidgetsPutRequest extends JsonPutRequest {
private static JSONObject prepareFile(Widget[] widgets){ private static JSONObject prepareFile(Widget[] widgets){
try { try {
JSONArray widgetArray = new JSONArray(widgets); JSONArray widgetArray = new JSONArray();
for(Widget widget : widgets){
widgetArray.put(widget.toJson());
}
JSONObject object = new JSONObject() JSONObject object = new JSONObject()
.put( .put(

View File

@ -37,4 +37,51 @@
android:id="@+id/qhybrid_button_bottom_single_press" android:id="@+id/qhybrid_button_bottom_single_press"
android:textSize="20dp"/> android:textSize="20dp"/>
<Space
android:layout_width="match_parent"
android:layout_height="20dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="top widget"
android:id="@+id/qhybrid_button_widget_top"
android:textSize="20dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="right widget"
android:id="@+id/qhybrid_button_widget_right"
android:textSize="20dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="bottom widget"
android:id="@+id/qhybrid_button_widget_bottom"
android:textSize="20dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="left widget"
android:id="@+id/qhybrid_button_widget_left"
android:textSize="20dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="custom widgets" />
<ListView
android:layout_width="match_parent"
android:layout_weight="0.4"
android:layout_height="0dp"
android:id="@+id/qhybrid_widget_list"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="add widget"
android:id="@+id/qhybrid_widget_add"/>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".devices.qhybrid.WidgetSettingsActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="widget name" />
<EditText
android:id="@+id/qhybrid_widget_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="elements" />
<ListView
android:id="@+id/qhybrid_widget_elements_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.4" />
<Button
android:id="@+id/qhybrid_widget_elements_add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="add element" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:weightSum="1">
<Button
android:id="@+id/qhybrid_widget_delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="delete"
android:layout_weight="0.5" />
<Button
android:id="@+id/qhybrid_widget_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="save"
android:layout_weight="0.5" />
</LinearLayout>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="element id" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/qhybrid_widget_element_id"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="default value" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/qhybrid_widget_element_value"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="type" />
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/qhybrid_widget_element_type"
android:checkedButton="@id/qhybrid_widget_elements_type_text">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="text"
android:id="@+id/qhybrid_widget_elements_type_text"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="background image"
android:id="@+id/qhybrid_widget_elements_type_background" />
</RadioGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="position" />
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/qhybrid_widget_element_position"
android:checkedButton="@id/qhybrid_widget_elements_position_uppper">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="upper half"
android:id="@+id/qhybrid_widget_elements_position_uppper"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="lower half"
android:id="@+id/qhybrid_widget_elements_position_lower" />
</RadioGroup>
</LinearLayout>

View File

@ -773,6 +773,13 @@
<string name="qhybrid_offset_timezone">offset timezone by</string> <string name="qhybrid_offset_timezone">offset timezone by</string>
<string name="qhybrid_changes_delay_prompt">change might take some seconds…</string> <string name="qhybrid_changes_delay_prompt">change might take some seconds…</string>
<string name="qhybrid_offset_time_by">offset time by</string> <string name="qhybrid_offset_time_by">offset time by</string>
<string name="hr_widget_heart_rate">Heart rate</string>
<string name="hr_widget_steps">Steps</string>
<string name="hr_widget_date">Date</string>
<string name="hr_widget_active_minutes">Active minutes</string>
<string name="hr_widget_calories">Calories</string>
<string name="hr_widget_battery">Battery</string>
<string name="hr_widget_nothing">Nothing</string>
<plurals name="widget_alarm_target_hours"> <plurals name="widget_alarm_target_hours">
<item quantity="one">%d hour</item> <item quantity="one">%d hour</item>
<item quantity="two">%d hours</item> <item quantity="two">%d hours</item>