Compare commits

...

11 Commits

Author SHA1 Message Date
b8ff80680b Pump version code 2020-09-09 15:30:02 +03:00
e633449578 Remove in-app purchase 2020-09-09 15:04:20 +03:00
18c48ad067 Update README.md 2020-08-25 14:03:14 +03:00
07dafa7039 fix 2020-08-25 13:19:46 +03:00
8a1f539668 minor fix 2020-08-25 13:17:35 +03:00
19325b5b7f lib updates 2020-08-25 13:15:14 +03:00
b68d6539b6 Bump version code 2020-08-25 12:48:34 +03:00
aa8c402f97 text change 2020-08-25 12:46:04 +03:00
343d39d157 Start location tracking in foreground after migration 2020-08-25 12:44:22 +03:00
b056de5230 Merge pull request #584 from estevez-dev/rc/1.3.0-b1
Rc/1.3.0 b1
2020-08-25 12:38:59 +03:00
f7e139b963 Merge pull request #583 from estevez-dev/rc/1.3.0-b1
Rc/1.3.0 b1
2020-08-25 12:14:05 +03:00
9 changed files with 11 additions and 273 deletions

View File

@ -1,4 +1,9 @@
# HA Client
| # IMPORTANT! |
|:-:|
| As you may know official Home Assistant android app continue to grow and improve. Today is the day when it has so much more and better functionality that I see no any reason not to move from HA Client to official Home Assistant app. You need to make the same as it has more device sensors, better location tracking, widgets etc.
| **I'm stopping an active HA Client development as now the only advantage it has over the official app is native UI that is very far from completeness.** |
| I need to say a HUGE THANK YOU all for a grate support you all made for HA Client. I really appreciate each and every issue reported to GitHub, each and every message from each of you and each donation made to support development. Now it is time to cancel subscription if any and migrate your HA setup to an official Home Assistant app. You can find it in [Google Play](https://play.google.com/store/apps/details?id=io.homeassistant.companion.android). |
## Native Android client for Home Assistant
### With actionable notifications, location tracking and Lovelace UI support

View File

@ -19,7 +19,6 @@ import 'package:flutter_custom_tabs/flutter_custom_tabs.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';
import 'plugins/spoiler_card.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
@ -103,8 +102,6 @@ part 'entities/vacuum/widgets/vacuum_controls.dart';
part 'entities/vacuum/widgets/vacuum_state_button.dart';
part 'entities/error_entity_widget.dart';
part 'pages/settings/connection_settings.part.dart';
part 'pages/purchase.page.dart';
part 'pages/widgets/product_purchase.widget.dart';
part 'pages/widgets/page_loading_indicator.dart';
part 'pages/widgets/bottom_info_bar.dart';
part 'pages/widgets/page_loading_error.dart';
@ -160,7 +157,7 @@ part 'managers/app_settings.dart';
EventBus eventBus = new EventBus();
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.3.0.md';
const whatsNewUrl = 'http://ha-client.app/service/whats_new_1.3.1.md';
Future<void> _reportError(dynamic error, dynamic stackTrace) async {
// Print the exception to the console.
@ -214,18 +211,11 @@ class HAClientApp extends StatefulWidget {
}
class _HAClientAppState extends State<HAClientApp> {
StreamSubscription<List<PurchaseDetails>> _purchaseUpdateSubscription;
StreamSubscription _themeChangeSubscription;
AppTheme _currentTheme = AppTheme.defaultTheme;
@override
void initState() {
InAppPurchaseConnection.enablePendingPurchases();
final Stream purchaseUpdates =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_purchaseUpdateSubscription = purchaseUpdates.listen((purchases) {
_handlePurchaseUpdates(purchases);
});
_currentTheme = widget.theme;
_themeChangeSubscription = eventBus.on<ChangeThemeEvent>().listen((event){
setState(() {
@ -235,25 +225,6 @@ class _HAClientAppState extends State<HAClientApp> {
super.initState();
}
void _handlePurchaseUpdates(purchase) {
if (purchase is List<PurchaseDetails>) {
if (purchase[0].status == PurchaseStatus.purchased) {
eventBus.fire(ShowPopupEvent(
popup: Popup(
title: "Thanks a lot!",
body: "Thank you for supporting HA Client development!",
positiveText: "Ok"
)
));
InAppPurchaseConnection.instance.completePurchase(purchase[0]);
} else {
Logger.d("Purchase change handler: ${purchase[0].status}");
}
} else {
Logger.e("Something wrong with purchase handling. Got: $purchase");
}
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
@ -267,7 +238,6 @@ class _HAClientAppState extends State<HAClientApp> {
"/app-settings": (context) => AppSettingsPage(),
"/connection-settings": (context) => AppSettingsPage(showSection: AppSettingsSection.connectionSettings),
"/integration-settings": (context) => AppSettingsPage(showSection: AppSettingsSection.integrationSettings),
"/putchase": (context) => PurchasePage(title: "Support app development"),
"/play-media": (context) => PlayMediaPage(
mediaUrl: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['url'] : ''}",
mediaType: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['type'] ?? '' : ''}",
@ -314,7 +284,6 @@ class _HAClientAppState extends State<HAClientApp> {
@override
void dispose() {
_purchaseUpdateSubscription.cancel();
_themeChangeSubscription.cancel();
super.dispose();
}

View File

@ -83,7 +83,7 @@ class AppSettings {
'location-updates-interval': oldLocationTrackingInterval * 60 * 1000,
//'location-updates-priority': 100,
'location-updates-show-notification': true,
'foreground-location-tracking': false
'foreground-location-tracking': true
});
} catch (e, stack) {
Logger.e("[MIGRATION] Can't start new location tracking: $e", stacktrace: stack);

View File

@ -11,53 +11,18 @@ class StartupUserMessagesManager {
StartupUserMessagesManager._internal();
bool _needToshowDonateMessage;
bool _whatsNewMessageShown;
static final _donateMsgTimerKey = "user-msg-donate-timer";
static final _donateMsgShownKey = "user-msg-donate-shpown";
static final _whatsNewMessageKey = "user-msg-whats-new-url";
void checkMessagesToShow() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.reload();
var tInt = prefs.getInt(_donateMsgTimerKey);
if (tInt == null) {
prefs.setInt(_donateMsgTimerKey, DateTime.now().millisecondsSinceEpoch);
_needToshowDonateMessage = false;
} else {
bool wasShown = prefs.getBool(_donateMsgShownKey) ?? false;
_needToshowDonateMessage = (Duration(milliseconds: DateTime.now().millisecondsSinceEpoch - tInt).inDays >= 14) && !wasShown;
}
_whatsNewMessageShown = '${prefs.getString(_whatsNewMessageKey)}' == whatsNewUrl;
if (!_whatsNewMessageShown) {
_showWhatsNewMessage();
} else if (_needToshowDonateMessage) {
_showSupportAppDevelopmentMessage();
}
}
void _showSupportAppDevelopmentMessage() {
eventBus.fire(ShowPopupEvent(
popup: Popup(
title: "Hi!",
body: "As you may have noticed this app contains no ads. Also all app features are available for you for free. I'm not planning to change this in nearest future, but still you can support this application development materially. There is one-time payment available as well as several subscription options. Thanks.",
positiveText: "Show options",
negativeText: "Later",
onPositive: () {
SharedPreferences.getInstance().then((prefs) {
prefs.setBool(_donateMsgShownKey, true);
eventBus.fire(ShowPageEvent(path: "/putchase"));
});
},
onNegative: () {
SharedPreferences.getInstance().then((prefs) {
prefs.setInt(_donateMsgTimerKey, DateTime.now().millisecondsSinceEpoch);
});
}
)
));
}
void _showWhatsNewMessage() {
SharedPreferences.getInstance().then((prefs) {
prefs.setString(_whatsNewMessageKey, whatsNewUrl);

View File

@ -304,15 +304,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
},
),
Divider(),
new ListTile(
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:food")),
title: Text("Support app development"),
onTap: () {
Navigator.of(context).pop();
Navigator.of(context).pushNamed('/putchase');
},
),
Divider(),
new ListTile(
leading: Icon(Icons.help),
title: Text("Help"),

View File

@ -1,118 +0,0 @@
part of '../main.dart';
class PurchasePage extends StatefulWidget {
PurchasePage({Key key, this.title}) : super(key: key);
final String title;
@override
_PurchasePageState createState() => new _PurchasePageState();
}
class _PurchasePageState extends State<PurchasePage> {
bool _loaded = false;
String _error = "";
List<ProductDetails> _products;
List<PurchaseDetails> _purchases;
@override
void initState() {
super.initState();
_loadProducts();
}
_loadProducts() async {
final bool available = await InAppPurchaseConnection.instance.isAvailable();
if (!available) {
setState(() {
_error = "Error connecting to store";
});
} 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.isNotEmpty) {
Logger.d("Products not found: ${response.notFoundIDs}");
}
_products = response.productDetails;
_loadPreviousPurchases();
}
}
_loadPreviousPurchases() async {
final QueryPurchaseDetailsResponse response = await InAppPurchaseConnection.instance.queryPastPurchases();
if (response.error != null) {
setState(() {
_error = "Error loading previous purchases";
});
} else {
_purchases = response.pastPurchases;
for (PurchaseDetails purchase in _purchases) {
Logger.d("Previous purchase: ${purchase.status}");
}
if (_products.isEmpty) {
setState(() {
_error = "No data found in store";
});
} else {
setState(() {
_loaded = true;
});
}
}
}
List<Widget> _buildProducts() {
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(
product: product,
onBuy: (product) => _buyProduct(product),
purchased: _purchases.any((purchase) { return purchase.productID == product.id;}),)
);
}
return productWidgets;
}
void _buyProduct(ProductDetails product) {
Logger.d("Starting purchase of ${product.id}");
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
InAppPurchaseConnection.instance.buyNonConsumable(purchaseParam: purchaseParam);
Navigator.of(context).pop();
}
@override
Widget build(BuildContext context) {
List<Widget> body;
if (!_loaded) {
body = [_error.isEmpty ? PageLoadingIndicator() : PageLoadingError(errorText: _error)];
} else {
body = _buildProducts();
}
return new Scaffold(
appBar: new AppBar(
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
Navigator.pop(context);
}),
title: new Text(widget.title),
),
body: ListView(
scrollDirection: Axis.vertical,
children: body
),
);
}
}

View File

@ -142,7 +142,7 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
if (_useForegroundService) {
notes.add(_getNoteWidget('* Notification is mandatory for foreground service', false));
} else {
notes.add(_getNoteWidget('* Use foreground service for intervals less then 15 minutes', false));
notes.add(_getNoteWidget('* Use foreground service for more accurate and stable tracking', false));
}
if (_useForegroundService && _locationInterval.inMinutes < 10) {
notes.add(_getNoteWidget('* Battery consumption will be noticeable', true));

View File

@ -1,73 +0,0 @@
part of '../../main.dart';
class ProductPurchase extends StatelessWidget {
final ProductDetails product;
final onBuy;
final purchased;
const ProductPurchase({Key key, @required this.product, @required this.onBuy, this.purchased}) : super(key: key);
@override
Widget build(BuildContext context) {
String period = "";
Color priceColor;
String buttonText = '';
String buttonTextInactive = '';
if (product.id.contains("year")) {
period += "once a year";
buttonText = "Subscribe";
buttonTextInactive = "Already";
priceColor = Colors.amber;
} else {
period += "";
buttonText = "Pay";
buttonTextInactive = "Paid";
priceColor = Colors.deepOrangeAccent;
}
return Card(
child: Padding(
padding: EdgeInsets.all(Sizes.leftWidgetPadding),
child: Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(
flex: 5,
child: Padding(
padding: EdgeInsets.only(right: Sizes.rightWidgetPadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"${product.title}",
style: Theme.of(context).textTheme.body2,
),
Container(height: Sizes.rowPadding,),
Text(
"${product.description}",
overflow: TextOverflow.ellipsis,
maxLines: 4,
softWrap: true,
),
Container(height: Sizes.rowPadding,),
Text("${product.price} $period", style: Theme.of(context).textTheme.body1.copyWith(
color: priceColor
)),
],
)
),
),
Expanded(
flex: 2,
child: RaisedButton(
child: Text(this.purchased ? buttonTextInactive : buttonText, style: Theme.of(context).textTheme.button),
color: Colors.blue,
onPressed: this.purchased ? null : () => this.onBuy(this.product),
),
)
],
),
)
);
}
}

View File

@ -1,7 +1,7 @@
name: hass_client
description: Home Assistant Android Client
version: 1.3.0+1309
version: 1.3.1+1312
environment:
@ -19,7 +19,6 @@ dependencies:
date_format: ^1.0.8
charts_flutter: ^0.8.1
flutter_markdown: ^0.3.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
@ -27,8 +26,8 @@ dependencies:
hive_flutter: ^0.3.0+2
device_info: ^0.4.2+4
firebase_crashlytics: ^0.1.3+3
syncfusion_flutter_core: ^18.2.44
syncfusion_flutter_gauges: ^18.2.44
syncfusion_flutter_core: ^18.2.54
syncfusion_flutter_gauges: ^18.2.54
flutter_map: ^0.10.1+1