mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-06-19 11:30:44 +02:00
35217aa405
The Xiaomi Smart Band 8 Pro shows widgets in a two by three grid. Previously, opening the widget configuration for such a device from the device-specific preferences would crash Gadgetbridge because the layouts in such a grid was not supported. This commit adds definitions for layouts in a 2x3 grid to the WidgetLayout enum, adds a definition for a full screen widget to the WidgetType enum, defines rendering definitions for the new layouts to WidgetScreenDetailsActivity, and defines translations for the new layouts and type to XiaomiWidgetManager.
281 lines
12 KiB
Java
281 lines
12 KiB
Java
/* Copyright (C) 2023-2024 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 <https://www.gnu.org/licenses/>. */
|
|
package nodomain.freeyourgadget.gadgetbridge.activities.widgets;
|
|
|
|
import android.app.Activity;
|
|
import android.os.Bundle;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.widget.ArrayAdapter;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
|
import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetLayout;
|
|
import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetManager;
|
|
import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetPart;
|
|
import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetPartSubtype;
|
|
import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetScreen;
|
|
import nodomain.freeyourgadget.gadgetbridge.capabilities.widgets.WidgetType;
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
|
|
|
public class WidgetScreenDetailsActivity extends AbstractGBActivity {
|
|
private static final Logger LOG = LoggerFactory.getLogger(WidgetScreenDetailsActivity.class);
|
|
|
|
private WidgetScreen widgetScreen;
|
|
private WidgetManager widgetManager;
|
|
|
|
private View cardWidgetTopLeft;
|
|
private View cardWidgetTopRight;
|
|
private View cardWidgetCenter;
|
|
private View cardWidgetBotLeft;
|
|
private View cardWidgetBotRight;
|
|
|
|
private TextView labelWidgetScreenLayout;
|
|
private TextView labelWidgetTopLeft;
|
|
private TextView labelWidgetTopRight;
|
|
private TextView labelWidgetCenter;
|
|
private TextView labelWidgetBotLeft;
|
|
private TextView labelWidgetBotRight;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
setContentView(R.layout.activity_widget_screen_details);
|
|
|
|
final GBDevice device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
|
if (device == null) {
|
|
LOG.error("device must not be null");
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
widgetScreen = (WidgetScreen) getIntent().getSerializableExtra(WidgetScreen.EXTRA_WIDGET_SCREEN);
|
|
if (widgetScreen == null) {
|
|
GB.toast("No widget screen provided to WidgetScreenDetailsActivity", Toast.LENGTH_LONG, GB.ERROR);
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
widgetManager = device.getDeviceCoordinator().getWidgetManager(device);
|
|
|
|
// Save button
|
|
final FloatingActionButton fab = findViewById(R.id.fab_save);
|
|
fab.setOnClickListener(view -> {
|
|
for (final WidgetPart part : widgetScreen.getParts()) {
|
|
if (part.getId() == null) {
|
|
GB.toast(getBaseContext().getString(R.string.widget_missing_parts), Toast.LENGTH_LONG, GB.WARN);
|
|
return;
|
|
}
|
|
}
|
|
|
|
updateWidgetScreen();
|
|
WidgetScreenDetailsActivity.this.setResult(Activity.RESULT_OK);
|
|
finish();
|
|
});
|
|
|
|
// Layouts
|
|
final List<WidgetLayout> supportedLayouts = widgetManager.getSupportedWidgetLayouts();
|
|
final String[] layoutStrings = new String[supportedLayouts.size()];
|
|
for (int i = 0; i < supportedLayouts.size(); i++) {
|
|
layoutStrings[i] = getBaseContext().getString(supportedLayouts.get(i).getName());
|
|
}
|
|
final ArrayAdapter<String> layoutAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, layoutStrings);
|
|
|
|
final View cardLayout = findViewById(R.id.card_layout);
|
|
cardLayout.setOnClickListener(view -> {
|
|
new MaterialAlertDialogBuilder(WidgetScreenDetailsActivity.this).setAdapter(layoutAdapter, (dialogInterface, i) -> {
|
|
if (widgetScreen.getLayout() != supportedLayouts.get(i)) {
|
|
final ArrayList<WidgetPart> defaultParts = new ArrayList<>();
|
|
for (final WidgetType widgetType : supportedLayouts.get(i).getWidgetTypes()) {
|
|
defaultParts.add(new WidgetPart(null, "", widgetType));
|
|
}
|
|
widgetScreen.setParts(defaultParts);
|
|
}
|
|
widgetScreen.setLayout(supportedLayouts.get(i));
|
|
updateUiFromWidget();
|
|
}).setTitle(R.string.widget_layout).create().show();
|
|
});
|
|
|
|
cardWidgetTopLeft = findViewById(R.id.card_widget_top_left);
|
|
cardWidgetTopRight = findViewById(R.id.card_widget_top_right);
|
|
cardWidgetCenter = findViewById(R.id.card_widget_center);
|
|
cardWidgetBotLeft = findViewById(R.id.card_widget_bottom_left);
|
|
cardWidgetBotRight = findViewById(R.id.card_widget_bottom_right);
|
|
|
|
labelWidgetScreenLayout = findViewById(R.id.widget_screen_layout);
|
|
labelWidgetTopLeft = findViewById(R.id.widget_top_left);
|
|
labelWidgetTopRight = findViewById(R.id.widget_top_right);
|
|
labelWidgetCenter = findViewById(R.id.widget_center);
|
|
labelWidgetBotLeft = findViewById(R.id.widget_bottom_left);
|
|
labelWidgetBotRight = findViewById(R.id.widget_bottom_right);
|
|
|
|
updateUiFromWidget();
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(final 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 updateWidgetScreen() {
|
|
widgetManager.saveScreen(widgetScreen);
|
|
}
|
|
|
|
@Override
|
|
protected void onSaveInstanceState(@NonNull final Bundle state) {
|
|
super.onSaveInstanceState(state);
|
|
state.putSerializable("widgetScreen", widgetScreen);
|
|
}
|
|
|
|
@Override
|
|
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
|
super.onRestoreInstanceState(savedInstanceState);
|
|
widgetScreen = (WidgetScreen) savedInstanceState.getSerializable("widgetScreen");
|
|
updateUiFromWidget();
|
|
}
|
|
|
|
private void updateUiFromWidget() {
|
|
labelWidgetScreenLayout.setText(widgetScreen.getLayout().getName());
|
|
|
|
switch (widgetScreen.getLayout()) {
|
|
case TOP_1_BOT_2:
|
|
case TOP_2X2_BOT_2:
|
|
updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, -1);
|
|
updateWidget(cardWidgetTopRight, labelWidgetTopRight, -1);
|
|
updateWidget(cardWidgetCenter, labelWidgetCenter, 0);
|
|
updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, 1);
|
|
updateWidget(cardWidgetBotRight, labelWidgetBotRight, 2);
|
|
break;
|
|
case TOP_2_BOT_1:
|
|
case TOP_2_BOT_2X2:
|
|
updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, 0);
|
|
updateWidget(cardWidgetTopRight, labelWidgetTopRight, 1);
|
|
updateWidget(cardWidgetCenter, labelWidgetCenter, 2);
|
|
updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, -1);
|
|
updateWidget(cardWidgetBotRight, labelWidgetBotRight, -1);
|
|
break;
|
|
case TOP_2_BOT_2:
|
|
updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, 0);
|
|
updateWidget(cardWidgetTopRight, labelWidgetTopRight, 1);
|
|
updateWidget(cardWidgetCenter, labelWidgetCenter, -1);
|
|
updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, 2);
|
|
updateWidget(cardWidgetBotRight, labelWidgetBotRight, 3);
|
|
break;
|
|
case ONE_BY_TWO_SINGLE:
|
|
case TWO_BY_TWO_SINGLE:
|
|
case TWO_BY_THREE_SINGLE:
|
|
updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, -1);
|
|
updateWidget(cardWidgetTopRight, labelWidgetTopRight, -1);
|
|
updateWidget(cardWidgetCenter, labelWidgetCenter, 0);
|
|
updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, -1);
|
|
updateWidget(cardWidgetBotRight, labelWidgetBotRight, -1);
|
|
break;
|
|
case TWO:
|
|
case TOP_1_BOT_2X2:
|
|
case TOP_2X2_BOT_1:
|
|
updateWidget(cardWidgetTopLeft, labelWidgetTopLeft, -1);
|
|
updateWidget(cardWidgetTopRight, labelWidgetTopRight, 0);
|
|
updateWidget(cardWidgetCenter, labelWidgetCenter, 1);
|
|
updateWidget(cardWidgetBotLeft, labelWidgetBotLeft, -1);
|
|
updateWidget(cardWidgetBotRight, labelWidgetBotRight, -1);
|
|
break;
|
|
default:
|
|
throw new IllegalStateException("Unknown layout " + widgetScreen.getLayout());
|
|
}
|
|
}
|
|
|
|
private void updateWidget(final View card, final TextView label, final int partIdx) {
|
|
final boolean validPart = partIdx >= 0 && partIdx < widgetScreen.getParts().size();
|
|
|
|
card.setVisibility(validPart ? View.VISIBLE : View.GONE);
|
|
|
|
if (!validPart) {
|
|
card.setOnClickListener(null);
|
|
label.setText(R.string.not_set);
|
|
return;
|
|
}
|
|
|
|
final WidgetPart widgetPart = widgetScreen.getParts().get(partIdx);
|
|
|
|
if (widgetPart.getId() == null) {
|
|
label.setText(R.string.not_set);
|
|
} else {
|
|
label.setText(widgetPart.getFullName());
|
|
}
|
|
|
|
// Select widget part
|
|
|
|
final List<WidgetPart> supportedParts = widgetManager.getSupportedWidgetParts(widgetPart.getType());
|
|
final String[] layoutStrings = new String[supportedParts.size()];
|
|
for (int i = 0; i < supportedParts.size(); i++) {
|
|
layoutStrings[i] = supportedParts.get(i).getName();
|
|
}
|
|
final ArrayAdapter<String> partAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, layoutStrings);
|
|
|
|
card.setOnClickListener(view -> {
|
|
new MaterialAlertDialogBuilder(WidgetScreenDetailsActivity.this).setAdapter(partAdapter, (dialogInterface, i) -> {
|
|
final WidgetPart selectedPart = supportedParts.get(i);
|
|
|
|
final List<WidgetPartSubtype> supportedSubtypes = selectedPart.getSupportedSubtypes();
|
|
if (supportedSubtypes.isEmpty()) {
|
|
// No subtypes selected
|
|
|
|
widgetScreen.getParts().set(partIdx, selectedPart);
|
|
updateUiFromWidget();
|
|
return;
|
|
}
|
|
|
|
// If the selected part supports subtypes, the user must select a subtype
|
|
|
|
final String[] subtypeStrings = new String[supportedSubtypes.size()];
|
|
for (int j = 0; j < supportedSubtypes.size(); j++) {
|
|
subtypeStrings[j] = supportedSubtypes.get(j).getName();
|
|
}
|
|
final ArrayAdapter<String> subtypesAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, subtypeStrings);
|
|
|
|
new MaterialAlertDialogBuilder(WidgetScreenDetailsActivity.this).setAdapter(subtypesAdapter, (dialogInterface1, j) -> {
|
|
final WidgetPartSubtype selectedSubtype = supportedSubtypes.get(j);
|
|
selectedPart.setSubtype(selectedSubtype);
|
|
widgetScreen.getParts().set(partIdx, selectedPart);
|
|
updateUiFromWidget();
|
|
}).setTitle(R.string.widget_subtype).create().show();
|
|
}).setTitle(R.string.widget).create().show();
|
|
});
|
|
}
|
|
}
|