From 5cbbdff45597912a78dfde377b0f027a7f405a6b Mon Sep 17 00:00:00 2001 From: syle Date: Mon, 26 Aug 2024 23:25:03 -0500 Subject: [PATCH] Adding Background/Foreground Services to GeoLocator UPDATE (#3803) * Adding Background/Foreground Services to GeoLocator UPDATE * Removing filepicker from pubspec.yaml devel area * Adding geolocator min version to flet_geolocator instead * Adding geolocator min version to flet_geolocator instead 2 * Permission_handler_html web 1.0.0 fix * Updating on_position to new format --- client/android/app/build.gradle | 11 ++- .../android/app/src/main/AndroidManifest.xml | 84 ++++++++++++------- client/android/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- client/android/settings.gradle | 4 +- .../flet_geolocator/lib/src/geolocator.dart | 84 +++++++++++++++++++ packages/flet_geolocator/pubspec.yaml | 4 +- packages/flet_permission_handler/pubspec.yaml | 3 +- .../flet-core/src/flet_core/geolocator.py | 68 ++++++++++++++- 9 files changed, 219 insertions(+), 45 deletions(-) diff --git a/client/android/app/build.gradle b/client/android/app/build.gradle index 9e9f2b028..5fea8634e 100644 --- a/client/android/app/build.gradle +++ b/client/android/app/build.gradle @@ -27,6 +27,12 @@ android { compileSdkVersion flutter.compileSdkVersion ndkVersion "25.1.8937393" + packagingOptions { + jniLibs { + useLegacyPackaging true + } + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -42,11 +48,10 @@ android { defaultConfig { applicationId "com.appveyor.flet" - minSdkVersion flutter.minSdkVersion + minSdkVersion 23 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName - minSdkVersion 23 } buildTypes { @@ -60,4 +65,4 @@ flutter { source '../..' } -dependencies {} \ No newline at end of file +dependencies {} diff --git a/client/android/app/src/main/AndroidManifest.xml b/client/android/app/src/main/AndroidManifest.xml index 8e5c26ce2..d7c4db353 100644 --- a/client/android/app/src/main/AndroidManifest.xml +++ b/client/android/app/src/main/AndroidManifest.xml @@ -1,38 +1,58 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + - - + + \ No newline at end of file diff --git a/client/android/build.gradle b/client/android/build.gradle index 2dcf86f90..52d31f376 100644 --- a/client/android/build.gradle +++ b/client/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.9.24' repositories { google() mavenCentral() @@ -27,4 +27,4 @@ subprojects { tasks.register("clean", Delete) { delete rootProject.buildDir -} \ No newline at end of file +} diff --git a/client/android/gradle/wrapper/gradle-wrapper.properties b/client/android/gradle/wrapper/gradle-wrapper.properties index 6b665338b..a35eb1fa3 100644 --- a/client/android/gradle/wrapper/gradle-wrapper.properties +++ b/client/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip diff --git a/client/android/settings.gradle b/client/android/settings.gradle index 45d92d220..b64b2eb73 100644 --- a/client/android/settings.gradle +++ b/client/android/settings.gradle @@ -23,7 +23,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false + id "com.android.application" version "8.3.1" apply false } -include ":app" \ No newline at end of file +include ":app" diff --git a/packages/flet_geolocator/lib/src/geolocator.dart b/packages/flet_geolocator/lib/src/geolocator.dart index 9653e90ca..353a97d44 100644 --- a/packages/flet_geolocator/lib/src/geolocator.dart +++ b/packages/flet_geolocator/lib/src/geolocator.dart @@ -1,3 +1,5 @@ +import 'dart:async'; +import 'dart:convert'; import 'package:flet/flet.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -22,6 +24,8 @@ class GeolocatorControl extends StatefulWidget { class _GeolocatorControlState extends State with FletStoreMixin { + StreamSubscription? _positionStream; + @override void initState() { super.initState(); @@ -41,10 +45,81 @@ class _GeolocatorControlState extends State super.deactivate(); } + void _onPosition(Position position) { + debugPrint("Geolocator onPosition: $position"); + final jsonData = jsonEncode({ + "latitude": position.latitude, + "longitude": position.longitude, + }); + widget.backend.triggerControlEvent(widget.control.id, "position", jsonData); + } + + Future _enableLocationService() async { + late LocationSettings locationSettings; + if (defaultTargetPlatform == TargetPlatform.android) { + locationSettings = AndroidSettings( + accuracy: LocationAccuracy.high, + distanceFilter: 0, + forceLocationManager: true, + intervalDuration: const Duration(seconds: 30), + // Needs this or when app goes in background, background service stops working + foregroundNotificationConfig: const ForegroundNotificationConfig( + notificationText: + "Location Updates", + notificationTitle: "Running in Background", + enableWakeLock: true, + ) + ); + } else if (defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS) { + locationSettings = AppleSettings( + accuracy: LocationAccuracy.bestForNavigation, + activityType: ActivityType.automotiveNavigation, + distanceFilter: 0, + pauseLocationUpdatesAutomatically: false, + showBackgroundLocationIndicator: true, + allowBackgroundLocationUpdates: true, + ); + } else if (kIsWeb) { + locationSettings = WebSettings( + accuracy: LocationAccuracy.high, + distanceFilter: 0, + // maximumAge: Duration(minutes: 5) + maximumAge: Duration.zero, + ); + } else { + locationSettings = LocationSettings( + accuracy: LocationAccuracy.high, + distanceFilter: 0, + ); + } + + _positionStream = Geolocator.getPositionStream(locationSettings: locationSettings, + ).listen( + (Position? position) { + if (position != null) { + _onPosition(position); + debugPrint('Geolocator: ${position.latitude}, ${position.longitude}, ${position}'); + } else { + debugPrint('Geolocator: Position is null.'); + } + }, + onError: (e) { + debugPrint('Geolocator: Error getting stream position: $e'); + }, + ); + return true; + } + + Future _disableLocationService() async { + await _positionStream?.cancel(); + return true; + } + @override Widget build(BuildContext context) { debugPrint( "Geolocator build: ${widget.control.id} (${widget.control.hashCode})"); + bool onPosition = widget.control.attrBool("onPosition", false)!; () async { widget.backend.subscribeMethods(widget.control.id, @@ -59,6 +134,15 @@ class _GeolocatorControlState extends State case "is_location_service_enabled": var serviceEnabled = await Geolocator.isLocationServiceEnabled(); return serviceEnabled.toString(); + case "service_enable": + var serviceEnabled = false; + if (onPosition) { + serviceEnabled = await _enableLocationService(); + } + return serviceEnabled.toString(); + case "service_disable": + var serviceDisabled = await _disableLocationService(); + return serviceDisabled.toString(); case "open_app_settings": if (!kIsWeb) { var opened = await Geolocator.openAppSettings(); diff --git a/packages/flet_geolocator/pubspec.yaml b/packages/flet_geolocator/pubspec.yaml index 3d9c40bab..d10a279ad 100644 --- a/packages/flet_geolocator/pubspec.yaml +++ b/packages/flet_geolocator/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - geolocator: ^11.0.0 + geolocator: ^13.0.1 flet: path: ../flet/ @@ -20,4 +20,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 \ No newline at end of file + flutter_lints: ^2.0.0 diff --git a/packages/flet_permission_handler/pubspec.yaml b/packages/flet_permission_handler/pubspec.yaml index a39b7abb9..98375f8d4 100644 --- a/packages/flet_permission_handler/pubspec.yaml +++ b/packages/flet_permission_handler/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: collection: ^1.16.0 permission_handler: ^11.3.1 + permission_handler_html: ^0.1.3+2 flet: path: ../flet/ @@ -21,4 +22,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 \ No newline at end of file + flutter_lints: ^2.0.0 diff --git a/sdk/python/packages/flet-core/src/flet_core/geolocator.py b/sdk/python/packages/flet-core/src/flet_core/geolocator.py index 5f6c7bab3..c53ec6a9a 100644 --- a/sdk/python/packages/flet-core/src/flet_core/geolocator.py +++ b/sdk/python/packages/flet-core/src/flet_core/geolocator.py @@ -1,10 +1,16 @@ -import json from dataclasses import dataclass, field from enum import Enum from typing import Any, Optional from flet_core.control import Control from flet_core.ref import Ref +from flet_core.types import ( + OptionalEventCallable, + OptionalControlEventCallable, +) +from flet_core.event_handler import EventHandler +from flet_core.control_event import ControlEvent +import json class GeolocatorPositionAccuracy(Enum): @@ -58,12 +64,17 @@ def __init__( # ref: Optional[Ref] = None, data: Any = None, + on_position: OptionalEventCallable["PositionEvent"] = None, + ): Control.__init__( self, ref=ref, data=data, ) + self.__on_position = EventHandler(lambda e: PositionEvent(e)) + self._add_event_handler("position", self.__on_position.get_handler()) + self.on_position = on_position def _get_control_name(self): return "geolocator" @@ -192,7 +203,7 @@ async def request_permission_async( def is_location_service_enabled(self, wait_timeout: Optional[float] = 10) -> bool: enabled = self.invoke_method( - "request_permission", + "is_location_service_enabled", wait_for_result=True, wait_timeout=wait_timeout, ) @@ -245,3 +256,56 @@ async def open_location_settings_async( wait_timeout=wait_timeout, ) return opened == "true" + + def service_enable(self, wait_timeout: Optional[float] = 10) -> bool: + opened = self.invoke_method( + "service_enable", + wait_for_result=True, + wait_timeout=wait_timeout, + ) + return opened == "true" + + async def service_enable_async( + self, wait_timeout: Optional[float] = 10 + ) -> bool: + opened = await self.invoke_method_async( + "service_enable", + wait_for_result=True, + wait_timeout=wait_timeout, + ) + return opened == "true" + + def service_disable(self, wait_timeout: Optional[float] = 10) -> bool: + opened = self.invoke_method( + "service_disable", + wait_for_result=True, + wait_timeout=wait_timeout, + ) + return opened == "true" + + async def service_disable_async( + self, wait_timeout: Optional[float] = 10 + ) -> bool: + opened = await self.invoke_method_async( + "service_disable", + wait_for_result=True, + wait_timeout=wait_timeout, + ) + return opened == "true" + + @property + def on_position(self) -> OptionalEventCallable["PositionEvent"]: + return self.__on_position.handler + + @on_position.setter + def on_position(self, handler: OptionalEventCallable["PositionEvent"]): + self.__on_position.handler = handler + self._set_attr("onPosition", True if handler is not None else None) + + +class PositionEvent(ControlEvent): + def __init__(self, e: ControlEvent): + super().__init__(e.target, e.name, e.data, e.control, e.page) + d = json.loads(e.data) + self.latitude: float = d.get("latitude") + self.longitude: float = d.get("longitude")