WIP #49 Location tracking service and test alarm manager

This commit is contained in:
estevez-dev 2019-08-30 15:04:51 +03:00
parent be53500104
commit dca8c309aa
9 changed files with 209 additions and 21 deletions

View File

@ -8,6 +8,8 @@
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<!-- io.flutter.app.FlutterApplication is an android.app.Application that <!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method. calls FlutterMain.startInitialization(this); in its onCreate method.
@ -54,5 +56,20 @@
<action android:name="com.lyokone.location.BackgroundLocationBroadcastReceiver.ACTION_PROCESS_UPDATES" /> <action android:name="com.lyokone.location.BackgroundLocationBroadcastReceiver.ACTION_PROCESS_UPDATES" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<service
android:name="io.flutter.plugins.androidalarmmanager.AlarmService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
<receiver
android:name="io.flutter.plugins.androidalarmmanager.AlarmBroadcastReceiver"
android:exported="false"/>
<receiver
android:name="io.flutter.plugins.androidalarmmanager.RebootBroadcastReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>
</application> </application>
</manifest> </manifest>

View File

@ -4,6 +4,7 @@ import io.flutter.app.FlutterApplication;
import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback;
import io.flutter.plugins.GeneratedPluginRegistrant; import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugins.androidalarmmanager.AlarmService;
import com.lyokone.location.LocationPlugin; import com.lyokone.location.LocationPlugin;
public class Application extends FlutterApplication implements PluginRegistrantCallback { public class Application extends FlutterApplication implements PluginRegistrantCallback {
@ -11,6 +12,7 @@ public class Application extends FlutterApplication implements PluginRegistrantC
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
LocationPlugin.setPluginRegistrant(this); LocationPlugin.setPluginRegistrant(this);
AlarmService.setPluginRegistrant(this);
} }
@Override @Override

View File

@ -24,9 +24,9 @@ import 'package:device_info/device_info.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:location/location.dart'; import 'package:location/location.dart';
import 'package:android_alarm_manager/android_alarm_manager.dart';
part 'const.dart'; part 'const.dart';
part 'premium_features_manager.class.dart';
part 'entities/entity.class.dart'; part 'entities/entity.class.dart';
part 'entities/entity_wrapper.class.dart'; part 'entities/entity_wrapper.class.dart';
part 'entities/timer/timer_entity.class.dart'; part 'entities/timer/timer_entity.class.dart';
@ -100,7 +100,8 @@ part 'pages/entity.page.dart';
part 'utils.class.dart'; part 'utils.class.dart';
part 'mdi.class.dart'; part 'mdi.class.dart';
part 'entity_collection.class.dart'; part 'entity_collection.class.dart';
part 'auth_manager.class.dart'; part 'managers/auth_manager.class.dart';
part 'managers/location_manager.class.dart';
part 'connection.class.dart'; part 'connection.class.dart';
part 'device.class.dart'; part 'device.class.dart';
part 'ui_class/ui.dart'; part 'ui_class/ui.dart';
@ -129,9 +130,10 @@ void main() async {
}; };
runZoned(() { runZoned(() {
AndroidAlarmManager.initialize().then((_) {
runApp(new HAClientApp()); runApp(new HAClientApp());
print("Running MAIN isolate ${Isolate.current.hashCode}"); print("Running MAIN isolate ${Isolate.current.hashCode}");
});
}, onError: (error, stack) { }, onError: (error, stack) {
Logger.e("$error"); Logger.e("$error");
@ -283,7 +285,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
_showInfoBottomBar(progress: true,); _showInfoBottomBar(progress: true,);
_subscribe().then((_) { _subscribe().then((_) {
Connection().init(loadSettings: true, forceReconnect: true).then((__){ Connection().init(loadSettings: true, forceReconnect: true).then((__){
PremiumFeaturesManager(); LocationManager();
_fetchData(); _fetchData();
}, onError: (e) { }, onError: (e) {
_setErrorState(e); _setErrorState(e);
@ -331,7 +333,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
void _handlePurchaseUpdates(purchase) { void _handlePurchaseUpdates(purchase) {
if (purchase is List<PurchaseDetails>) { if (purchase is List<PurchaseDetails>) {
if (purchase[0].status == PurchaseStatus.purchased) { if (purchase[0].status == PurchaseStatus.purchased) {
PremiumFeaturesManager().addPurchase(purchase[0]);
eventBus.fire(ShowPopupMessageEvent( eventBus.fire(ShowPopupMessageEvent(
title: "Thanks a lot!", title: "Thanks a lot!",
body: "Thank you for supporting HA Client development!", body: "Thank you for supporting HA Client development!",

View File

@ -1,4 +1,4 @@
part of 'main.dart'; part of '../main.dart';
class AuthManager { class AuthManager {

View File

@ -0,0 +1,117 @@
part of '../main.dart';
class LocationManager {
static void updateDeviceLocation(List<LocationData> locations) {
print("[GPS isolate #${Isolate.current.hashCode}] Got device location update");
SharedPreferences.getInstance().then((prefs){
print("[GPS isolate #${Isolate.current.hashCode}] loading settings");
String webhookId = prefs.getString('app-webhook-id');
String domain = prefs.getString('hassio-domain');
String port = prefs.getString('hassio-port');
String httpWebHost =
"${prefs.getString('hassio-res-protocol')}://$domain:$port";
if (webhookId != null && webhookId.isNotEmpty) {
int battery = DateTime.now().hour;
try {
print("[GPS isolate #${Isolate.current.hashCode}] Sending data home...");
String url = "$httpWebHost/api/webhook/$webhookId";
Map<String, String> headers = {};
headers["Content-Type"] = "application/json";
var data = {
"type": "update_location",
"data": {
"gps": [locations[0].latitude, locations[0].longitude],
"gps_accuracy": locations[0].accuracy,
"battery": battery
}
};
http.post(
url,
headers: headers,
body: json.encode(data)
);
} on PlatformException catch (e) {
if (e.code == 'PERMISSION_DENIED') {
print("[GPS isolate #${Isolate.current.hashCode}] No location permission. Aborting");
}
}
} else {
print("[GPS isolate #${Isolate.current.hashCode}] No webhook id. Aborting");
}
});
}
static void updateTestEntity() {
print("[Test isolate #${Isolate.current.hashCode}] alarm service callback");
SharedPreferences.getInstance().then((prefs){
print("[Test isolate #${Isolate.current.hashCode}] loading settings");
String webhookId = prefs.getString('app-webhook-id');
String domain = prefs.getString('hassio-domain');
String port = prefs.getString('hassio-port');
String httpWebHost =
"${prefs.getString('hassio-res-protocol')}://$domain:$port";
if (webhookId != null && webhookId.isNotEmpty) {
DateTime currentTime = DateTime.now();
String timeData = "${currentTime.year}-${currentTime.month}-${currentTime.day} ${currentTime.hour}:${currentTime.minute}";
try {
print("[Test isolate #${Isolate.current.hashCode}] Sending data home...");
String url = "$httpWebHost/api/webhook/$webhookId";
Map<String, String> headers = {};
headers["Content-Type"] = "application/json";
var data = {
"type": "call_service",
"data": {
"domain": "input_datetime",
"service": "set_datetime",
"service_data": {
"entity_id": "input_datetime.app_alarm_service_test",
"datetime": timeData
}
}
};
http.post(
url,
headers: headers,
body: json.encode(data)
);
} catch (e) {
print("[Test isolate #${Isolate.current.hashCode}] Error: ${e.toString()}");
}
} else {
print("[Test isolate #${Isolate.current.hashCode}] No webhook id. Aborting");
}
});
}
static final LocationManager _instance = LocationManager
._internal();
factory LocationManager() {
return _instance;
}
LocationManager._internal() {
_registerLocationListener();
}
final int alarmId = 349011;
final Duration testAlarmUpdateInterval = Duration(minutes: 10);
void _registerLocationListener() async {
var _locationService = Location();
bool _permission = await _locationService.requestPermission();
if (_permission) {
Logger.d("Activating device location tracking");
_locationService.changeSettings(interval: 10000, accuracy: LocationAccuracy.BALANCED);
bool statusBackgroundLocation = await _locationService.registerBackgroundLocation(LocationManager.updateDeviceLocation);
Logger.d("Location listener status: $statusBackgroundLocation");
} else {
Logger.e("Location permission not granted");
}
//await AndroidAlarmManager.cancel(alarmId);
Logger.d("Activating alarm service test");
await AndroidAlarmManager.periodic(testAlarmUpdateInterval, alarmId, LocationManager.updateTestEntity);
}
}

View File

@ -13,26 +13,63 @@ class _PurchasePageState extends State<PurchasePage> {
bool _loaded = false; bool _loaded = false;
String _error = ""; String _error = "";
List<ProductDetails> _products;
List<PurchaseDetails> _purchases;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (PremiumFeaturesManager().products.isEmpty) { _loadProducts();
_error = "Subscription is not loaded"; }
_loadProducts() async {
final bool available = await InAppPurchaseConnection.instance.isAvailable();
if (!available) {
setState(() {
_error = "Error connecting to store";
});
} else { } else {
_loaded = true; 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) {
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;
});
}
} }
} }
Widget _buildProducts() { Widget _buildProducts() {
List<Widget> productWidgets = []; List<Widget> productWidgets = [];
for (ProductDetails product in PremiumFeaturesManager().products) { for (ProductDetails product in _products) {
productWidgets.add( productWidgets.add(
ProductPurchase( ProductPurchase(
product: product, product: product,
onBuy: (product) => _buyProduct(product), onBuy: (product) => _buyProduct(product),
purchased: PremiumFeaturesManager().purchases.any((purchase) { return purchase.productID == product.id;}),) purchased: _purchases.any((purchase) { return purchase.productID == product.id;}),)
); );
} }
return ListView( return ListView(

View File

@ -10,13 +10,19 @@ class ProductPurchase extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String period = "/ "; String period = "";
Color priceColor; Color priceColor;
String buttonText = '';
String buttonTextInactive = '';
if (product.id.contains("year")) { if (product.id.contains("year")) {
period += "year"; period += "/ year";
buttonText = "Subscribe";
buttonTextInactive = "Already";
priceColor = Colors.amber; priceColor = Colors.amber;
} else { } else {
period += "month"; period += "";
buttonText = "Pay";
buttonTextInactive = "Paid";
priceColor = Colors.deepOrangeAccent; priceColor = Colors.deepOrangeAccent;
} }
return Card( return Card(
@ -55,7 +61,7 @@ class ProductPurchase extends StatelessWidget {
Expanded( Expanded(
flex: 2, flex: 2,
child: RaisedButton( child: RaisedButton(
child: Text(this.purchased ? "Bought" : "Buy", style: TextStyle(color: Colors.white)), child: Text(this.purchased ? buttonTextInactive : buttonText, style: TextStyle(color: Colors.white)),
color: Colors.blue, color: Colors.blue,
onPressed: this.purchased ? null : () => this.onBuy(this.product), onPressed: this.purchased ? null : () => this.onBuy(this.product),
), ),

View File

@ -1,6 +1,13 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
android_alarm_manager:
dependency: "direct main"
description:
name: android_alarm_manager
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.4"
archive: archive:
dependency: transitive dependency: transitive
description: description:
@ -49,14 +56,14 @@ packages:
name: charts_common name: charts_common
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.7.0" version: "0.8.0"
charts_flutter: charts_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: charts_flutter name: charts_flutter
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.7.0" version: "0.8.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -192,7 +199,7 @@ packages:
name: in_app_purchase name: in_app_purchase
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.1" version: "0.2.1+2"
intl: intl:
dependency: transitive dependency: transitive
description: description:

View File

@ -26,6 +26,7 @@ dependencies:
flutter_secure_storage: ^3.2.1+1 flutter_secure_storage: ^3.2.1+1
device_info: ^0.4.0+2 device_info: ^0.4.0+2
flutter_local_notifications: ^0.8.2 flutter_local_notifications: ^0.8.2
android_alarm_manager: ^0.4.4
location: location:
git: git:
url: git://github.com/Lyokone/flutterlocation.git url: git://github.com/Lyokone/flutterlocation.git