Compare commits

...

30 Commits

Author SHA1 Message Date
1c686402d0 Update purchase information 2020-06-11 11:47:07 +03:00
5f4a3fbdfc Update device_info to 0.4.2+4 2020-06-03 19:27:26 +00:00
312ed99e9f Remove irrelevant errors log 2020-06-03 18:42:26 +00:00
25e6d51c17 Ignore map state filters in cards 2020-06-03 18:35:06 +00:00
b501574bab Bump version code 2020-06-03 18:25:54 +00:00
53b31d8e90 Fix HTTP exceptions handling for notification images 2020-06-03 18:25:07 +00:00
6d80420a9b Fix cover null attributes handling 2020-06-02 22:07:37 +00:00
e977054139 Fix mobile app registration with unknown manufacturer or model 2020-06-02 21:54:54 +00:00
6367d38524 Fix timer duration parsing 2020-06-02 21:43:32 +00:00
f9b2d7d84c Fix light card with wrong domain entity. Show custom cards if there is entitites 2020-06-02 21:36:45 +00:00
44c28ad106 isExist fix 2020-05-30 10:39:00 +00:00
fec3c525e1 Bump version code 2020-05-30 10:23:21 +00:00
b1bbed6d80 Bump version code 2020-05-30 10:09:48 +00:00
13878cfc51 Fix FCM token crash 2020-05-30 10:02:50 +00:00
be49180205 Update github urls 2020-05-29 18:38:57 +00:00
c4a0b16553 Resolves #567 Login connection timeout issue 2020-05-29 18:26:20 +00:00
caacd5e9f4 Fix display mode name 2020-05-29 18:20:25 +00:00
5fa28abb6c Bump version code 2020-05-29 18:18:38 +00:00
e0a28c0b59 Resolves #563 Fullscreen mode 2020-05-29 18:16:59 +00:00
096e714a04 Real fullscreen for camera view 2020-05-29 16:52:57 +00:00
78893ea01f Fix for cropped screans 2020-05-29 16:35:34 +00:00
90efb29be5 Resolves #555 PayPal donate button removed 2020-05-29 16:25:07 +00:00
fca323c56b Resolves #559 2020-05-29 16:14:39 +00:00
e5fe6af5f3 Resolves #513 Allow to install on SD card 2020-05-29 09:24:23 +00:00
f0090d522d Merge pull request #566 from estevez-dev/rc/1.1.0-b3
Bump version code
2020-05-29 00:16:12 +03:00
edbfd8359b Bump version code 2020-05-28 21:15:26 +00:00
2702bb254a Bump version code 2020-05-28 20:55:08 +00:00
ca7b6ed550 Resolves #564 - Show picture-elements as button if camera_image provided 2020-05-28 20:52:23 +00:00
fb00b5d9ff Replace secure storage with encripted db 2020-05-28 20:23:13 +00:00
7ffba397ce Fix crash whne no google play services available 2020-05-28 19:17:32 +00:00
24 changed files with 201 additions and 90 deletions

View File

@ -1,5 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.keyboardcrumbs.hassclient">
package="com.keyboardcrumbs.hassclient"
android:installLocation="auto">
<uses-feature android:name="android.hardware.touchscreen"
android:required="false" />

View File

@ -16,6 +16,8 @@ import io.flutter.plugin.common.MethodChannel;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.ConnectionResult;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;
import com.google.firebase.messaging.FirebaseMessaging;
@ -31,28 +33,36 @@ public class MainActivity extends FlutterActivity {
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
Context context = getActivity();
if (call.method.equals("getFCMToken")) {
FirebaseInstanceId.getInstance().getInstanceId()
if (checkPlayServices()) {
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
@Override
public void onComplete(@NonNull Task<InstanceIdResult> task) {
if (task.isSuccessful()) {
Context context = getActivity();
String token = task.getResult().getToken();
UpdateTokenTask updateTokenTask = new UpdateTokenTask(context);
updateTokenTask.execute(token);
result.success(token);
} else {
result.error("fcm_error", task.getException().getMessage(), task.getException());
result.error("fcm_error", task.getException().getMessage(), null);
}
}
});
} else {
result.error("google_play_service_error", "Google Play Services unavailable", null);
}
}
}
}
);
}
private boolean checkPlayServices() {
return (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

View File

@ -145,7 +145,7 @@ public class MessagingService extends FirebaseMessagingService {
connection.connect();
InputStream input = connection.getInputStream();
return BitmapFactory.decodeStream(input);
} catch (IOException e) {
} catch (Exception e) {
return null;
}
}

View File

@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx2g
org.gradle.jvmargs=-Xmx1g
org.gradle.daemon=true
org.gradle.caching=true
android.useAndroidX=true

View File

@ -38,6 +38,15 @@ class CardData {
case CardType.LIGHT:
return LightCardData(rawData);
break;
case CardType.PICTURE_ELEMENTS:
//TODO temporary solution
if (rawData.containsKey('camera_image')) {
rawData['entity'] = rawData['camera_image'];
return ButtonCardData(rawData);
} else {
return CardData(null);
}
break;
case CardType.ENTITY_BUTTON:
case CardType.BUTTON:
case CardType.PICTURE_ENTITY:
@ -81,6 +90,12 @@ class CardData {
return BadgesData(rawData);
break;
default:
if (rawData.containsKey('entity')) {
rawData['entities'] = [rawData['entity']];
}
if (rawData.containsKey('entities') && rawData['entities'] is List) {
return EntitiesCardData(rawData);
}
return CardData(null);
}
} catch (error, stacktrace) {
@ -94,7 +109,11 @@ class CardData {
type = rawData['type'];
conditions = rawData['conditions'] ?? [];
showEmpty = rawData['show_empty'] ?? true;
stateFilter = rawData['state_filter'] ?? [];
if (rawData.containsKey('state_filter') && rawData['state_filter'] is List) {
stateFilter = rawData['state_filter'];
} else {
stateFilter = [];
}
} else {
type = CardType.UNKNOWN;
conditions = [];
@ -365,7 +384,13 @@ class LightCardData extends CardData {
@override
Widget buildCardWidget() {
return LightCard(card: this);
if (this.entity != null && this.entity.entity is LightEntity) {
return LightCard(card: this);
}
return ErrorCard(
errorText: 'Specify an entity from within the light domain.',
showReportButton: false,
);
}
LightCardData(rawData) : super(rawData) {

View File

@ -2,12 +2,21 @@ part of '../main.dart';
class ErrorCard extends StatelessWidget {
final ErrorCardData card;
final String errorText;
final bool showReportButton;
const ErrorCard({Key key, this.card}) : super(key: key);
const ErrorCard({Key key, this.card, this.errorText, this.showReportButton: true}) : super(key: key);
@override
Widget build(BuildContext context) {
String error;
if (errorText == null) {
error = 'There was an error showing ${card?.type}';
} else {
error = errorText;
}
return CardWrapper(
color: Theme.of(context).errorColor,
child: Padding(
padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding),
child: Column(
@ -15,21 +24,25 @@ class ErrorCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'There was an error rendering card: ${card.type}. Please copy card config to clipboard and report this issue. Thanks!',
error,
textAlign: TextAlign.center,
),
card != null ?
RaisedButton(
onPressed: () {
Clipboard.setData(new ClipboardData(text: card.cardConfig));
},
child: Text('Copy card config'),
),
) :
Container(width: 0, height: 0),
showReportButton ?
RaisedButton(
onPressed: () {
Launcher.launchURLInBrowser("https://github.com/estevez-dev/ha_client/issues/new?assignees=&labels=&template=bug_report.md&title=");
},
child: Text('Report issue'),
)
) :
Container(width: 0, height: 0)
],
),
)

View File

@ -7,6 +7,6 @@ class UnsupportedCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
return Container(height: 20);
}
}

View File

@ -4,12 +4,14 @@ class CardWrapper extends StatelessWidget {
final Widget child;
final EdgeInsets padding;
final Color color;
const CardWrapper({Key key, this.child, this.padding: const EdgeInsets.all(0)}) : super(key: key);
const CardWrapper({Key key, this.child, this.color, this.padding: const EdgeInsets.all(0)}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
color: color,
child: Padding(
padding: padding,
child: child

View File

@ -40,8 +40,8 @@ class CoverEntity extends Entity {
CoverEntity.SUPPORT_SET_TILT_POSITION);
double get currentPosition => _getDoubleAttributeValue('current_position');
double get currentTiltPosition => _getDoubleAttributeValue('current_tilt_position');
double get currentPosition => _getDoubleAttributeValue('current_position') ?? 0;
double get currentTiltPosition => _getDoubleAttributeValue('current_tilt_position') ?? 0;
bool get canBeOpened => ((state != EntityState.opening) && (state != EntityState.open)) || (state == EntityState.open && currentPosition != null && currentPosition > 0.0 && currentPosition < 100.0);
bool get canBeClosed => ((state != EntityState.closing) && (state != EntityState.closed));
bool get canTiltBeOpened => currentTiltPosition < 100;

View File

@ -8,8 +8,8 @@ class TimerEntity extends Entity {
@override
void update(Map rawData, String webHost) {
super.update(rawData, webHost);
String durationSource = "${attributes["duration"]}";
if (durationSource != null && durationSource.isNotEmpty) {
if (attributes.containsKey('duration')) {
String durationSource = "${attributes["duration"]}";
try {
List<String> durationList = durationSource.split(":");
if (durationList.length == 1) {

View File

@ -149,7 +149,7 @@ class EntityCollection {
}
bool isExist(String entityId) {
return _allEntities[entityId] != null;
return _allEntities.containsKey(entityId);
}
List<Entity> getByDomains({List<String> includeDomains: const [], List<String> excludeDomains: const [], List<String> stateFiler}) {

View File

@ -221,7 +221,7 @@ class HomeAssistant {
var data = json.decode(prefs.getString('cached_services'));
_parseServices(data ?? {});
} catch (e, stacktrace) {
Logger.e(e, stacktrace: stacktrace);
Logger.e(e, stacktrace: stacktrace, skipCrashlytics: true);
}
}
await ConnectionManager().sendSocketMessage(type: "get_services").then((data) => _parseServices(data)).catchError((e) {
@ -261,7 +261,7 @@ class HomeAssistant {
var data = json.decode(sharedPrefs.getString('cached_panels'));
_parsePanels(data ?? {});
} catch (e, stacktrace) {
Logger.e(e, stacktrace: stacktrace);
Logger.e(e, stacktrace: stacktrace, skipCrashlytics: true);
panels.clear();
}
} else {

View File

@ -16,7 +16,8 @@ import 'package:http/http.dart' as http;
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_custom_tabs/flutter_custom_tabs.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:device_info/device_info.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'plugins/dynamic_multi_column_layout.dart';
@ -184,7 +185,13 @@ void main() async {
};
WidgetsFlutterBinding.ensureInitialized();
AppSettings().loadAppTheme();
await AppSettings().loadStartupSettings();
await Hive.initFlutter();
if (AppSettings().displayMode == DisplayMode.fullscreen) {
SystemChrome.setEnabledSystemUIOverlays([]);
} else {
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
}
runZoned(() {
runApp(new HAClientApp(

View File

@ -1,7 +1,13 @@
part of '../main.dart';
enum DisplayMode {normal, fullscreen}
class AppSettings {
static const DEFAULT_HIVE_BOX = 'defaultSettingsBox';
static const AUTH_TOKEN_KEY = 'llt';
static final AppSettings _instance = AppSettings._internal();
factory AppSettings() {
@ -22,6 +28,7 @@ class AppSettings {
String webhookId;
double haVersion;
bool scrollBadges;
DisplayMode displayMode;
AppTheme appTheme;
final int defaultLocationUpdateIntervalMinutes = 20;
Duration locationUpdateInterval;
@ -30,13 +37,15 @@ class AppSettings {
bool get isAuthenticated => longLivedToken != null;
bool get isTempAuthenticated => tempToken != null;
loadAppTheme() async {
loadStartupSettings() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
appTheme = AppTheme.values[prefs.getInt('app-theme') ?? AppTheme.defaultTheme.index];
displayMode = DisplayMode.values[prefs.getInt('display-mode') ?? DisplayMode.normal.index];
}
Future load(bool full) async {
if (full) {
await Hive.openBox(DEFAULT_HIVE_BOX);
Logger.d('Loading settings...');
SharedPreferences prefs = await SharedPreferences.getInstance();
_domain = prefs.getString('hassio-domain');
@ -52,18 +61,11 @@ class AppSettings {
locationUpdateInterval = Duration(minutes: prefs.getInt("location-interval") ??
defaultLocationUpdateIntervalMinutes);
locationTrackingEnabled = prefs.getBool("location-enabled") ?? false;
Logger.d('Done. $_domain:$_port');
try {
final storage = new FlutterSecureStorage();
longLivedToken = await storage.read(key: "hacl_llt");
Logger.d("Long-lived token read successful");
oauthUrl = "$httpWebHost/auth/authorize?client_id=${Uri.encodeComponent(
'https://ha-client.app')}&redirect_uri=${Uri
.encodeComponent(
'https://ha-client.app/service/auth_callback.html')}";
} catch (e, stacktrace) {
Logger.e("Error reading secure storage: $e", stacktrace: stacktrace);
}
longLivedToken = Hive.box(DEFAULT_HIVE_BOX).get(AUTH_TOKEN_KEY);
oauthUrl = "$httpWebHost/auth/authorize?client_id=${Uri.encodeComponent(
'https://ha-client.app')}&redirect_uri=${Uri
.encodeComponent(
'https://ha-client.app/service/auth_callback.html')}";
}
}
@ -103,25 +105,13 @@ class AppSettings {
Future clearTokens() async {
longLivedToken = null;
tempToken = null;
try {
final storage = new FlutterSecureStorage();
await storage.delete(key: "hacl_llt");
} catch(e, stacktrace) {
Logger.e("Error clearing tokens: $e", stacktrace: stacktrace);
}
Hive.box(DEFAULT_HIVE_BOX).delete(AUTH_TOKEN_KEY);
}
Future saveLongLivedToken(token) async {
void saveLongLivedToken(token) {
longLivedToken = token;
tempToken = null;
try {
final storage = new FlutterSecureStorage();
await storage.write(key: "hacl_llt", value: "$longLivedToken");
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool("oauth-used", true);
} catch(e, stacktrace) {
Logger.e("Error saving long-lived token: $e", stacktrace: stacktrace);
}
Hive.box(DEFAULT_HIVE_BOX).put(AUTH_TOKEN_KEY, longLivedToken);
}
bool isNotConfigured() {

View File

@ -254,6 +254,7 @@ class ConnectionManager {
sendSocketMessage(type: "auth/long_lived_access_token", additionalData: {"client_name": "HA Client app ${DateTime.now().millisecondsSinceEpoch}", "lifespan": 365}).then((data) {
Logger.d("Got long-lived token.");
AppSettings().saveLongLivedToken(data);
completer.complete();
}).catchError((e) {
completer.completeError(HACException("Authentication error: $e", actions: [HAErrorAction.reload(title: "Retry"), HAErrorAction.loginAgain(title: "Relogin")]));
});

View File

@ -5,9 +5,9 @@ class MobileAppIntegrationManager {
static final _appRegistrationData = {
"device_name": "",
"app_version": "$appVersion",
"manufacturer": DeviceInfoManager().manufacturer,
"model": DeviceInfoManager().model,
"os_version": DeviceInfoManager().osVersion,
"manufacturer": DeviceInfoManager().manufacturer ?? "unknown",
"model": DeviceInfoManager().model ?? "unknown",
"os_version": DeviceInfoManager().osVersion ?? "0",
"app_data": {
"push_token": "",
"push_url": "https://us-central1-ha-client-c73c4.cloudfunctions.net/pushNotifyV3"
@ -139,7 +139,7 @@ class MobileAppIntegrationManager {
positiveText: "Report issue",
negativeText: "Close",
onPositive: () {
Launcher.launchURLInBrowser("https://github.com/estevez-dev/ha_client/issues/new");
Launcher.launchURLInBrowser("https://github.com/estevez-dev/ha_client/issues/new/choose");
}
)
));

View File

@ -1,18 +1,37 @@
part of '../main.dart';
class FullScreenPage extends StatelessWidget {
class FullScreenPage extends StatefulWidget {
final Widget child;
const FullScreenPage({Key key, this.child}) : super(key: key);
@override
_FullScreenPageState createState() => _FullScreenPageState();
}
class _FullScreenPageState extends State<FullScreenPage> {
@override
void initState() {
SystemChrome.setEnabledSystemUIOverlays([]);
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black,
child: Center(
child: this.child,
child: this.widget.child,
),
);
}
@override
void dispose() {
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
super.dispose();
}
}

View File

@ -301,7 +301,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
title: Text("Report an issue"),
onTap: () {
Navigator.of(context).pop();
Launcher.launchURLInBrowser("https://github.com/estevez-dev/ha_client/issues/new");
Launcher.launchURLInBrowser("https://github.com/estevez-dev/ha_client/issues/new/choose");
},
),
Divider(),
@ -484,7 +484,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
floating: true,
pinned: true,
snap: false,
primary: true,
primary: AppSettings().displayMode == DisplayMode.normal,
title: Text(HomeAssistant().locationName ?? ""),
actions: <Widget>[
PopupMenuButton(
@ -493,6 +493,15 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
child: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:dots-vertical"), color: Theme.of(context).primaryIconTheme.color)
),
onSelected: (String val) {
if (val == "reload") {
_quickLoad();
} else if (val == "logout") {
HomeAssistant().logout().then((_) {
_quickLoad();
});
}
},
itemBuilder: (BuildContext context) {
List<PopupMenuEntry<String>> result = [
PopupMenuItem<String>(
@ -654,6 +663,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
Hive.close();
//final flutterWebviewPlugin = new FlutterWebviewPlugin();
//flutterWebviewPlugin.dispose();
_viewsTabController?.dispose();

View File

@ -31,7 +31,7 @@ class _PurchasePageState extends State<PurchasePage> {
} else {
const Set<String> _kIds = {'one_time_support','just_few_bucks_per_year', 'app_fan_support_per_year', 'grateful_user_support_per_year'};
final ProductDetailsResponse response = await InAppPurchaseConnection.instance.queryProductDetails(_kIds);
if (!response.notFoundIDs.isEmpty) {
if (response.notFoundIDs.isNotEmpty) {
Logger.d("Products not found: ${response.notFoundIDs}");
}
_products = response.productDetails;
@ -63,7 +63,18 @@ class _PurchasePageState extends State<PurchasePage> {
}
List<Widget> _buildProducts() {
List<Widget> productWidgets = [];
List<Widget> productWidgets = [
Card(
child: Padding(
padding: EdgeInsets.all(15),
child: Text(
'This will not unlock any additional functionality. This is only a donation to the HA Client open source project.',
style: Theme.of(context).textTheme.headline5,
textAlign: TextAlign.center,
)
)
)
];
for (ProductDetails product in _products) {
productWidgets.add(
ProductPurchase(
@ -90,22 +101,6 @@ class _PurchasePageState extends State<PurchasePage> {
} else {
body = _buildProducts();
}
body.add(
Card(
child: Container(
height: 80,
child: InkWell(
child: Image.network('https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif'),
onTap: () {
Launcher.launchURLInCustomTab(
context: context,
url: 'https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ARWGETZD2D83Q&source=url'
);
},
)
),
)
);
return new Scaffold(
appBar: new AppBar(
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){

View File

@ -23,7 +23,7 @@ class _AppSettingsPageState extends State<AppSettingsPage> {
Widget _buildMenuItem(BuildContext context, IconData icon,String title, AppSettingsSection section) {
return ListTile(
title: Text(title, style: Theme.of(context).textTheme.subhead),
title: Text(title),
leading: Icon(icon),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {

View File

@ -13,6 +13,7 @@ class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> {
AppTheme _currentTheme;
bool _scrollBadges = false;
DisplayMode _displayMode;
@override
void initState() {
@ -25,7 +26,8 @@ class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> {
await prefs.reload();
SharedPreferences.getInstance().then((prefs) {
setState(() {
_currentTheme = AppTheme.values[prefs.getInt("app-theme") ?? AppTheme.defaultTheme.index];
_currentTheme = AppTheme.values[prefs.getInt('app-theme') ?? AppTheme.defaultTheme.index];
_displayMode = DisplayMode.values[prefs.getInt('display-mode') ?? DisplayMode.normal.index];
_scrollBadges = prefs.getBool('scroll-badges') ?? true;
});
});
@ -42,18 +44,34 @@ class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> {
});
}
Future _saveOther() async {
Future _saveBadgesSettings() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
AppSettings().scrollBadges = _scrollBadges;
await prefs.setBool('scroll-badges', _scrollBadges);
}
Future _saveDisplayMode(DisplayMode mode) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
AppSettings().displayMode = mode;
await prefs.setInt('display-mode', mode.index);
if (mode == DisplayMode.fullscreen) {
SystemChrome.setEnabledSystemUIOverlays([]);
} else {
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
}
}
Map appThemeName = {
AppTheme.defaultTheme: 'Default',
AppTheme.haTheme: 'Home Assistant theme',
AppTheme.darkTheme: 'Dark theme'
};
Map DisplayModeName = {
DisplayMode.normal: 'Normal',
DisplayMode.fullscreen: 'Fullscreen'
};
@override
Widget build(BuildContext context) {
return ListView(
@ -93,7 +111,28 @@ class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> {
setState(() {
_scrollBadges = val;
});
_saveOther();
_saveBadgesSettings();
},
),
Container(height: Sizes.doubleRowPadding),
Text("Display mode:", style: Theme.of(context).textTheme.body2),
Container(height: Sizes.rowPadding),
DropdownButton<DisplayMode>(
value: _displayMode,
iconSize: 30.0,
isExpanded: true,
style: Theme.of(context).textTheme.title,
items: DisplayMode.values.map((value) {
return new DropdownMenuItem<DisplayMode>(
value: value,
child: Text('${DisplayModeName[value]}'),
);
}).toList(),
onChanged: (DisplayMode val) {
setState(() {
_displayMode = val;
});
_saveDisplayMode(val);
},
),
]

View File

@ -15,7 +15,7 @@ class ProductPurchase extends StatelessWidget {
String buttonText = '';
String buttonTextInactive = '';
if (product.id.contains("year")) {
period += "/ year";
period += "once a year";
buttonText = "Subscribe";
buttonTextInactive = "Already";
priceColor = Colors.amber;

View File

@ -73,11 +73,9 @@ class TokenLoginPopup extends Popup {
padding: EdgeInsets.all(20),
child: TextFormField(
onSaved: (newValue) {
final storage = new FlutterSecureStorage();
storage.write(key: "hacl_llt", value: newValue.trim()).then((_) {
Navigator.of(context).pop();
eventBus.fire(SettingsChangedEvent(true));
});
Hive.box(AppSettings.DEFAULT_HIVE_BOX).put(AppSettings.AUTH_TOKEN_KEY, newValue.trim());
Navigator.of(context).pop();
eventBus.fire(SettingsChangedEvent(true));
},
decoration: InputDecoration(
hintText: 'Please enter long-lived token',

View File

@ -1,7 +1,7 @@
name: hass_client
description: Home Assistant Android Client
version: 0.0.0+1151
version: 1.1.1+1158
environment:
@ -23,8 +23,9 @@ dependencies:
flutter_custom_tabs: ^0.6.0
flutter_webview_plugin: ^0.3.10+1
webview_flutter: ^0.3.19+7
flutter_secure_storage: ^3.3.3
device_info: ^0.4.1+4
hive: ^1.4.1+1
hive_flutter: ^0.3.0+2
device_info: ^0.4.2+4
geolocator: ^5.3.1
workmanager: ^0.2.2
battery: ^1.0.0