WIP #49 Location tracking service and test alarm manager
This commit is contained in:
parent
be53500104
commit
dca8c309aa
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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!",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
part of 'main.dart';
|
part of '../main.dart';
|
||||||
|
|
||||||
class AuthManager {
|
class AuthManager {
|
||||||
|
|
117
lib/managers/location_manager.class.dart
Normal file
117
lib/managers/location_manager.class.dart
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 {
|
||||||
|
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;
|
_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(
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
|
13
pubspec.lock
13
pubspec.lock
@ -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:
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user