mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-19 07:07:46 +01:00
Fossil/Skagen Hybrids: Embed menu_structure in watchface apps (#3245)
This PR aims to optimize the method of synchronizing the menu_structure for the openSourceWatchface by making the menu_structure request from the watch obsolete. Instead, when a new menu_structure is sent to GB via the Intent `nodomain.freeyourgadget.gadgetbridge.Q_SET_MENU_STRUCTURE`, GB remembers that JSON. Next time a watchface is built, the JSON is embedded into that app package so the watch doesn't forget the structure. This requires a full GB rebuild to work properky. Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/3245 Co-authored-by: Daniel Dakhno <dakhnod@gmail.com> Co-committed-by: Daniel Dakhno <dakhnod@gmail.com>
This commit is contained in:
parent
b029aa252d
commit
73d67d4093
Binary file not shown.
@ -23,6 +23,7 @@ import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
@ -39,6 +40,7 @@ import android.view.DragEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
@ -73,6 +75,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.BitmapUtil;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@ -154,6 +157,14 @@ public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implem
|
||||
findViewById(R.id.watchface_rotate_left).setOnClickListener(this);
|
||||
findViewById(R.id.watchface_rotate_right).setOnClickListener(this);
|
||||
findViewById(R.id.watchface_remove_image).setOnClickListener(this);
|
||||
findViewById(R.id.button_watchface_open_menu_companion).setOnClickListener(this);
|
||||
findViewById(R.id.button_watchface_reset_menu_structure).setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
reloadMenuStructureIndicator();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -207,7 +218,8 @@ public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implem
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.button_edit_name) {
|
||||
int buttonId = v.getId();
|
||||
if (buttonId == R.id.button_edit_name) {
|
||||
final EditText input = new EditText(this);
|
||||
input.setText(watchfaceName);
|
||||
input.setId(0);
|
||||
@ -226,7 +238,7 @@ public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implem
|
||||
})
|
||||
.setTitle(R.string.watchface_dialog_title_set_name)
|
||||
.show();
|
||||
} else if (v.getId() == R.id.watchface_invert_colors) {
|
||||
} else if (buttonId == R.id.watchface_invert_colors) {
|
||||
if (selectedBackgroundImage != null) {
|
||||
selectedBackgroundImage = BitmapUtil.invertBitmapColors(selectedBackgroundImage);
|
||||
for (int i=0; i<widgets.size(); i++) {
|
||||
@ -241,28 +253,57 @@ public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implem
|
||||
defaultWidgetColor = HybridHRWatchfaceWidget.COLOR_WHITE;
|
||||
}
|
||||
}
|
||||
} else if (v.getId() == R.id.button_set_background) {
|
||||
} else if (buttonId == R.id.button_set_background) {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("image/*");
|
||||
startActivityForResult(intent, CHILD_ACTIVITY_IMAGE_CHOOSER);
|
||||
} else if (v.getId() == R.id.button_add_widget) {
|
||||
} else if (buttonId == R.id.button_add_widget) {
|
||||
showWidgetEditPopup(-1);
|
||||
} else if (v.getId() == R.id.button_watchface_settings) {
|
||||
} else if (buttonId == R.id.button_watchface_settings) {
|
||||
showWatchfaceSettingsPopup();
|
||||
} else if (v.getId() == R.id.watchface_rotate_left) {
|
||||
} else if (buttonId == R.id.watchface_rotate_left) {
|
||||
if (selectedBackgroundImage != null) {
|
||||
selectedBackgroundImage = BitmapUtil.rotateImage(selectedBackgroundImage, -90);
|
||||
renderWatchfacePreview();
|
||||
}
|
||||
} else if (v.getId() == R.id.watchface_rotate_right) {
|
||||
} else if (buttonId== R.id.watchface_rotate_right) {
|
||||
if (selectedBackgroundImage != null) {
|
||||
selectedBackgroundImage = BitmapUtil.rotateImage(selectedBackgroundImage, 90);
|
||||
renderWatchfacePreview();
|
||||
}
|
||||
} else if (v.getId() == R.id.watchface_remove_image) {
|
||||
} else if (buttonId == R.id.watchface_remove_image) {
|
||||
deleteWatchfaceBackground();
|
||||
renderWatchfacePreview();
|
||||
} else if(buttonId == R.id.button_watchface_open_menu_companion){
|
||||
try {
|
||||
AndroidUtils.openApp("d.d.hrmenucompanion");
|
||||
} catch (Exception e) {
|
||||
GB.toast(getString(R.string.error_menu_companion_not_installed), Toast.LENGTH_SHORT, GB.INFO);
|
||||
|
||||
AndroidUtils.openWebsite("https://github.com/dakhnod/Fossil-HR-Menu-Companion/releases/latest");
|
||||
}
|
||||
} else if(buttonId == R.id.button_watchface_reset_menu_structure) {
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress());
|
||||
prefs.edit().remove("MENU_STRUCTURE_JSON").apply();
|
||||
GB.toast(getString(R.string.info_menu_structure_removed), Toast.LENGTH_SHORT, GB.INFO);
|
||||
reloadMenuStructureIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadMenuStructureIndicator(){
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress());
|
||||
String menuStructureJson = prefs.getString("MENU_STRUCTURE_JSON", "");
|
||||
|
||||
boolean active = !menuStructureJson.isEmpty();
|
||||
|
||||
((Button) findViewById(R.id.button_watchface_reset_menu_structure))
|
||||
.setEnabled(active);
|
||||
findViewById(R.id.fossil_menu_structure_hint_container).setVisibility(active ? View.VISIBLE : View.GONE);
|
||||
|
||||
if(active){
|
||||
((TextView)findViewById(R.id.text_watchface_menu_structure))
|
||||
.setText(getString(R.string.info_menu_structure_contents, menuStructureJson));
|
||||
}
|
||||
}
|
||||
|
||||
@ -572,6 +613,18 @@ public class HybridHRWatchfaceDesignerActivity extends AbstractGBActivity implem
|
||||
wfFactory.setSettings(watchfaceSettings);
|
||||
wfFactory.setBackground(selectedBackgroundImage);
|
||||
wfFactory.addWidgets(widgets);
|
||||
|
||||
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(mGBDevice.getAddress());
|
||||
String menuStructureJson = prefs.getString("MENU_STRUCTURE_JSON", "");
|
||||
if(!menuStructureJson.isEmpty()){
|
||||
try {
|
||||
JSONObject menuStructure = new JSONObject(menuStructureJson);
|
||||
wfFactory.setMenuStructure(menuStructure);
|
||||
} catch (JSONException e) {
|
||||
LOG.error("Error loading menu structure", e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
File tempFile = File.createTempFile("tmpWatchfaceFile", null);
|
||||
tempFile.deleteOnExit();
|
||||
|
@ -47,6 +47,7 @@ public class HybridHRWatchfaceFactory {
|
||||
private static final int PREVIEW_WIDTH = 192;
|
||||
private static final int PREVIEW_HEIGHT = 192;
|
||||
private ArrayList<JSONObject> widgets = new ArrayList<>();
|
||||
private JSONObject menuStructure = new JSONObject();
|
||||
|
||||
public HybridHRWatchfaceFactory(String name) {
|
||||
watchfaceName = name.replaceAll("[^-A-Za-z0-9]", "");
|
||||
@ -130,6 +131,10 @@ public class HybridHRWatchfaceFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public void setMenuStructure(JSONObject menuStructure){
|
||||
this.menuStructure = menuStructure;
|
||||
}
|
||||
|
||||
public void addWidgets(ArrayList<HybridHRWatchfaceWidget> widgets) {
|
||||
for (HybridHRWatchfaceWidget widget : widgets) {
|
||||
addWidget(widget);
|
||||
@ -311,6 +316,8 @@ public class HybridHRWatchfaceFactory {
|
||||
config.put("light_up_on_notification", settings.getLightUpOnNotification());
|
||||
configuration.put("config", config);
|
||||
|
||||
configuration.put("menu_structure", menuStructure);
|
||||
|
||||
return configuration.toString();
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ import java.util.Map;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public final class QHybridConstants {
|
||||
public static final String HYBRIDHR_WATCHFACE_VERSION = "1.10";
|
||||
public static final String HYBRIDHR_WATCHFACE_VERSION = "1.11";
|
||||
public static final int HYBRID_HR_WATCHFACE_WIDGET_SIZE = 76;
|
||||
|
||||
public static Map<String, String> KNOWN_WAPP_VERSIONS = new HashMap<String, String>() {
|
||||
|
@ -30,6 +30,8 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -99,6 +101,8 @@ public class QHybridSupport extends QHybridBaseSupport {
|
||||
public static final String QHYBRID_COMMAND_DOWNLOAD_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_DOWNLOAD_FILE";
|
||||
public static final String QHYBRID_COMMAND_UPLOAD_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_UPLOAD_FILE";
|
||||
|
||||
public static final String QHYBRID_COMMAND_SET_MENU_STRUCTURE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_MENU_STRUCTURE";
|
||||
|
||||
public static final String QHYBRID_ACTION_DOWNLOADED_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_DOWNLOADED_FILE";
|
||||
public static final String QHYBRID_ACTION_UPLOADED_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_UPLOADED_FILE";
|
||||
|
||||
@ -305,6 +309,7 @@ public class QHybridSupport extends QHybridBaseSupport {
|
||||
globalFilter.addAction(QHYBRID_COMMAND_UPLOAD_FILE);
|
||||
globalFilter.addAction(QHYBRID_COMMAND_PUSH_CONFIG);
|
||||
globalFilter.addAction(QHYBRID_COMMAND_SWITCH_WATCHFACE);
|
||||
globalFilter.addAction(QHYBRID_COMMAND_SET_MENU_STRUCTURE);
|
||||
globalCommandReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@ -380,6 +385,10 @@ public class QHybridSupport extends QHybridBaseSupport {
|
||||
handleSwitchWatchfaceIntent(intent);
|
||||
break;
|
||||
}
|
||||
case QHYBRID_COMMAND_SET_MENU_STRUCTURE:{
|
||||
handleSetMenuStructure(intent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -398,6 +407,30 @@ public class QHybridSupport extends QHybridBaseSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSetMenuStructure(Intent intent){
|
||||
if(intent == null){
|
||||
logger.error("intent null");
|
||||
return;
|
||||
}
|
||||
String menuStructureJson = intent.getStringExtra("EXTRA_MENU_STRUCTURE_JSON");
|
||||
if(menuStructureJson == null){
|
||||
logger.error("Menu structure json null");
|
||||
return;
|
||||
}
|
||||
if(menuStructureJson.isEmpty()){
|
||||
logger.error("Menu structure json empty");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
JSONObject menuStructure = new JSONObject(menuStructureJson);
|
||||
watchAdapter.handleSetMenuStructure(menuStructure);
|
||||
GB.toast(getContext().getString(R.string.info_menu_structure_set), Toast.LENGTH_SHORT, GB.INFO);
|
||||
} catch (JSONException e) {
|
||||
logger.error("Menu structure json empty");
|
||||
GB.toast(getContext().getString(R.string.error_invalid_menu_structure), Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean dangerousIntentsAllowed(){
|
||||
return GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(DeviceSettingsPreferenceConst.PREF_HYBRID_HR_DANGEROUS_EXTERNAL_INTENTS, true);
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.NotificationConfiguration;
|
||||
@ -165,4 +167,8 @@ public abstract class WatchAdapter {
|
||||
public void pushConfigJson(String configJson){
|
||||
|
||||
}
|
||||
|
||||
public void handleSetMenuStructure(JSONObject menuStructure) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1068,6 +1068,28 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSetMenuStructure(JSONObject menuStructure) {
|
||||
String serialized = menuStructure.toString();
|
||||
getDeviceSpecificPreferences()
|
||||
.edit()
|
||||
.putString("MENU_STRUCTURE_JSON", serialized)
|
||||
.apply();
|
||||
|
||||
try {
|
||||
String payload = new JSONObject()
|
||||
.put("push", new JSONObject()
|
||||
.put("set", new JSONObject()
|
||||
.put("customWatchFace._.config.menu_structure", menuStructure)
|
||||
)
|
||||
).toString();
|
||||
pushConfigJson(payload);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWidgetContent(String widgetID, String content, boolean renderOnWatch) {
|
||||
boolean update = false;
|
||||
@ -1898,16 +1920,12 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
|
||||
queueWrite(new JsonPutRequest(responseObject, this));
|
||||
}
|
||||
} else if (request.optString("custom_menu").equals("request_config")) {
|
||||
// watchface requests custom menu data to be initialized
|
||||
LOG.info("Got custom_menu config request, sending intent to HR Menu Companion app...");
|
||||
Intent intent = new Intent();
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setClassName("d.d.hrmenucompanion", "d.d.hrmenucompanion.MainActivity");
|
||||
intent.putExtra("SEND_CONFIG", true);
|
||||
PackageManager manager = getContext().getPackageManager();
|
||||
try{
|
||||
getContext().startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
LOG.info("Couldn't send intent to Fossil-HR-Menu-Companion app, is it installed?");
|
||||
// only show toast when companion app is installed
|
||||
manager.getApplicationInfo("d.d.hrmenucompanion", 0);
|
||||
GB.toast(getContext().getString(R.string.info_fossil_rebuild_watchface_custom_menu), Toast.LENGTH_SHORT, GB.INFO);
|
||||
}catch (PackageManager.NameNotFoundException e){
|
||||
}
|
||||
} else {
|
||||
LOG.warn("Unhandled request from watch: " + requestJson.toString());
|
||||
|
@ -317,4 +317,20 @@ public class AndroidUtils {
|
||||
Toast.makeText(context, R.string.activity_error_share_failed, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
public static void openWebsite(String url){
|
||||
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
GBApplication.getContext().startActivity(i);
|
||||
}
|
||||
|
||||
public static void openApp(String packageName) throws ClassNotFoundException {
|
||||
Context context = GBApplication.getContext();
|
||||
|
||||
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
|
||||
if(launchIntent == null){
|
||||
throw new ClassNotFoundException("App " + packageName + " cannot be found");
|
||||
}
|
||||
GBApplication.getContext().startActivity(launchIntent);
|
||||
}
|
||||
}
|
||||
|
@ -112,21 +112,53 @@
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_set_background"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_watchface_select_image" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_add_widget"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_watchface_add_widget" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_watchface_settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_watchface_settings" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_watchface_open_menu_companion"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_open_menu_companion" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_watchface_reset_menu_structure"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_reset_menu_structure" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/fossil_menu_structure_hint_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
app:cardBackgroundColor="?attr/cardview_background_color"
|
||||
app:cardElevation="3dp"
|
||||
app:contentPadding="8dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/text_watchface_menu_structure"
|
||||
android:lines="1"
|
||||
android:ellipsize="end" />
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -2242,4 +2242,12 @@
|
||||
<string name="pref_activity_full_sync_trigger_warning">This will trigger a full sync of all activity data from the device. It may take a few minutes to complete.</string>
|
||||
<string name="pref_theme_dynamic_colors_not_available_warning">Dynamic colors are not available on your device, only Android 12+ supports this functionality. Gadgetbridge will use the default Material 3 colors.</string>
|
||||
<string name="pref_theme_dynamic_colors_explanation">Note: for the dynamic colors theme you need to enable Wallpaper Colors or Color Palette in your Android 12+ appearance settings. If you don\'t, Gadgetbridge will use the default Material 3 colors.</string>
|
||||
<string name="info_menu_structure_set">Menu structure JSON set in GB</string>
|
||||
<string name="error_invalid_menu_structure">Invalid menu structure JSON</string>
|
||||
<string name="button_open_menu_companion">Open menu companion app</string>
|
||||
<string name="button_reset_menu_structure">Reset menu structure</string>
|
||||
<string name="info_menu_structure_removed">Menu structure removed</string>
|
||||
<string name="error_menu_companion_not_installed">\'HR Menu Companion\' probably not installed</string>
|
||||
<string name="info_menu_structure_contents">Menu structure: %s</string>
|
||||
<string name="info_fossil_rebuild_watchface_custom_menu">Please rebuild your watchface for custom menu</string>
|
||||
</resources>
|
||||
|
2
external/fossil-hr-watchface
vendored
2
external/fossil-hr-watchface
vendored
@ -1 +1 @@
|
||||
Subproject commit d19c3b84acc03cd7f5dc03c59374920a63aa998f
|
||||
Subproject commit 24247ae23e1b903ddcc1e4e9cfb4ad7280a77db2
|
Loading…
Reference in New Issue
Block a user