Compare commits
25 Commits
1.1.0-beta
...
1.1.2
Author | SHA1 | Date | |
---|---|---|---|
d09afc37b5 | |||
1c686402d0 | |||
5f4a3fbdfc | |||
312ed99e9f | |||
25e6d51c17 | |||
b501574bab | |||
53b31d8e90 | |||
6d80420a9b | |||
e977054139 | |||
6367d38524 | |||
f9b2d7d84c | |||
44c28ad106 | |||
fec3c525e1 | |||
b1bbed6d80 | |||
13878cfc51 | |||
be49180205 | |||
c4a0b16553 | |||
caacd5e9f4 | |||
5fa28abb6c | |||
e0a28c0b59 | |||
096e714a04 | |||
78893ea01f | |||
90efb29be5 | |||
fca323c56b | |||
e5fe6af5f3 |
@ -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" />
|
||||
|
@ -46,7 +46,7 @@ public class MainActivity extends FlutterActivity {
|
||||
updateTokenTask.execute(token);
|
||||
result.success(token);
|
||||
} else {
|
||||
result.error("fcm_error", task.getException().getMessage(), task.getException());
|
||||
result.error("fcm_error", task.getException().getMessage(), null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx2g
|
||||
org.gradle.daemon=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.jvmargs=-Xmx512m
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableR8=true
|
||||
|
@ -90,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) {
|
||||
@ -103,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 = [];
|
||||
@ -374,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) {
|
||||
|
@ -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)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
@ -7,6 +7,6 @@ class UnsupportedCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
return Container(height: 20);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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}) {
|
||||
|
@ -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 {
|
||||
|
@ -160,7 +160,7 @@ EventBus eventBus = new EventBus();
|
||||
//FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
||||
const String appName = 'HA Client';
|
||||
const String appVersion = String.fromEnvironment('versionName', defaultValue: '0.0.0');
|
||||
const whatsNewUrl = 'http://ha-client.app/service/whats_new_1.1.0-b2.md';
|
||||
const whatsNewUrl = 'http://ha-client.app/service/whats_new_1.1.2.md';
|
||||
|
||||
Future<void> _reportError(dynamic error, dynamic stackTrace) async {
|
||||
// Print the exception to the console.
|
||||
@ -185,8 +185,13 @@ void main() async {
|
||||
};
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await AppSettings().loadAppTheme();
|
||||
await AppSettings().loadStartupSettings();
|
||||
await Hive.initFlutter();
|
||||
if (AppSettings().displayMode == DisplayMode.fullscreen) {
|
||||
SystemChrome.setEnabledSystemUIOverlays([]);
|
||||
} else {
|
||||
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
|
||||
}
|
||||
|
||||
runZoned(() {
|
||||
runApp(new HAClientApp(
|
||||
@ -244,6 +249,7 @@ class _HAClientAppState extends State<HAClientApp> {
|
||||
positiveText: "Ok"
|
||||
)
|
||||
));
|
||||
InAppPurchaseConnection.instance.completePurchase(purchase[0]);
|
||||
} else {
|
||||
Logger.d("Purchase change handler: ${purchase[0].status}");
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
part of '../main.dart';
|
||||
|
||||
enum DisplayMode {normal, fullscreen}
|
||||
|
||||
class AppSettings {
|
||||
|
||||
static const DEFAULT_HIVE_BOX = 'defaultSettingsBox';
|
||||
@ -26,6 +28,7 @@ class AppSettings {
|
||||
String webhookId;
|
||||
double haVersion;
|
||||
bool scrollBadges;
|
||||
DisplayMode displayMode;
|
||||
AppTheme appTheme;
|
||||
final int defaultLocationUpdateIntervalMinutes = 20;
|
||||
Duration locationUpdateInterval;
|
||||
@ -34,9 +37,10 @@ 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 {
|
||||
@ -104,7 +108,7 @@ class AppSettings {
|
||||
Hive.box(DEFAULT_HIVE_BOX).delete(AUTH_TOKEN_KEY);
|
||||
}
|
||||
|
||||
Future saveLongLivedToken(token) async {
|
||||
void saveLongLivedToken(token) {
|
||||
longLivedToken = token;
|
||||
tempToken = null;
|
||||
Hive.box(DEFAULT_HIVE_BOX).put(AUTH_TOKEN_KEY, longLivedToken);
|
||||
|
@ -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")]));
|
||||
});
|
||||
|
@ -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");
|
||||
}
|
||||
)
|
||||
));
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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>(
|
||||
|
@ -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: (){
|
||||
|
@ -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: () {
|
||||
|
@ -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);
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -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;
|
||||
|
@ -1,7 +1,7 @@
|
||||
name: hass_client
|
||||
description: Home Assistant Android Client
|
||||
|
||||
version: 1.1.0+1153
|
||||
version: 1.1.2+1159
|
||||
|
||||
|
||||
environment:
|
||||
@ -19,13 +19,13 @@ dependencies:
|
||||
date_format: ^1.0.8
|
||||
charts_flutter: ^0.8.1
|
||||
flutter_markdown: ^0.3.3
|
||||
in_app_purchase: ^0.3.0+3
|
||||
in_app_purchase: ^0.3.4
|
||||
flutter_custom_tabs: ^0.6.0
|
||||
flutter_webview_plugin: ^0.3.10+1
|
||||
webview_flutter: ^0.3.19+7
|
||||
hive: ^1.4.1+1
|
||||
hive_flutter: ^0.3.0+2
|
||||
device_info: ^0.4.1+4
|
||||
device_info: ^0.4.2+4
|
||||
geolocator: ^5.3.1
|
||||
workmanager: ^0.2.2
|
||||
battery: ^1.0.0
|
||||
|
Reference in New Issue
Block a user