From 3c261d6533d22df8c55602662866fbdde7bfbb33 Mon Sep 17 00:00:00 2001 From: problematicconsumer Date: Sat, 9 Sep 2023 15:04:52 +0330 Subject: [PATCH] Add android battery optimizations settings --- .../com/hiddify/hiddify/MainActivity.kt | 1 + .../hiddify/PlatformSettingsHandler.kt | 103 ++++++++++++++++++ assets/translations/strings.i18n.json | 4 +- assets/translations/strings_fa.i18n.json | 4 +- lib/features/settings/view/settings_page.dart | 1 + .../widgets/platform_settings_tiles.dart | 58 ++++++++++ lib/features/settings/widgets/widgets.dart | 1 + lib/services/platform_settings.dart | 32 ++++++ lib/services/service_providers.dart | 5 + 9 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 android/app/src/main/kotlin/com/hiddify/hiddify/PlatformSettingsHandler.kt create mode 100644 lib/features/settings/widgets/platform_settings_tiles.dart create mode 100644 lib/services/platform_settings.dart diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/MainActivity.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/MainActivity.kt index ec3288f2..3c3a6a33 100644 --- a/android/app/src/main/kotlin/com/hiddify/hiddify/MainActivity.kt +++ b/android/app/src/main/kotlin/com/hiddify/hiddify/MainActivity.kt @@ -40,6 +40,7 @@ class MainActivity : FlutterFragmentActivity(), ServiceConnection.Callback { instance = this reconnect() flutterEngine.plugins.add(MethodHandler()) + flutterEngine.plugins.add(PlatformSettingsHandler()) flutterEngine.plugins.add(EventHandler()) flutterEngine.plugins.add(LogHandler()) flutterEngine.plugins.add(GroupsChannel(lifecycleScope)) diff --git a/android/app/src/main/kotlin/com/hiddify/hiddify/PlatformSettingsHandler.kt b/android/app/src/main/kotlin/com/hiddify/hiddify/PlatformSettingsHandler.kt new file mode 100644 index 00000000..0f2a5706 --- /dev/null +++ b/android/app/src/main/kotlin/com/hiddify/hiddify/PlatformSettingsHandler.kt @@ -0,0 +1,103 @@ +package com.hiddify.hiddify + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Build +import androidx.annotation.NonNull +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.PluginRegistry +import io.flutter.plugin.common.StandardMethodCodec + +class PlatformSettingsHandler : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware, + PluginRegistry.ActivityResultListener { + private lateinit var channel: MethodChannel + private var activity: Activity? = null + private lateinit var ignoreRequestResult: MethodChannel.Result + + companion object { + const val channelName = "com.hiddify.app/platform.settings" + + const val REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = 44 + + enum class Trigger(val method: String) { + IsIgnoringBatteryOptimizations("is_ignoring_battery_optimizations"), + RequestIgnoreBatteryOptimizations("request_ignore_battery_optimizations"), + } + } + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + val taskQueue = flutterPluginBinding.binaryMessenger.makeBackgroundTaskQueue() + channel = MethodChannel( + flutterPluginBinding.binaryMessenger, + channelName, + StandardMethodCodec.INSTANCE, + taskQueue + ) + channel.setMethodCallHandler(this) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activity = binding.activity + binding.addActivityResultListener(this) + } + + override fun onDetachedFromActivityForConfigChanges() { + activity = null; + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + activity = binding.activity + binding.addActivityResultListener(this) + } + + override fun onDetachedFromActivity() { + activity = null; + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + if (requestCode == REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) { + ignoreRequestResult.success(resultCode == Activity.RESULT_OK) + return true + } + return false + } + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + Trigger.IsIgnoringBatteryOptimizations.method -> { + result.runCatching { + success( + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Application.powerManager.isIgnoringBatteryOptimizations(Application.application.packageName) + } else { + true + } + ) + } + } + + Trigger.RequestIgnoreBatteryOptimizations.method -> { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return result.success(true) + } + val intent = Intent( + android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + Uri.parse("package:${Application.application.packageName}") + ) + ignoreRequestResult = result + activity?.startActivityForResult(intent, REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + } + + else -> result.notImplemented() + } + } +} \ No newline at end of file diff --git a/assets/translations/strings.i18n.json b/assets/translations/strings.i18n.json index 0410657c..c4570d6d 100644 --- a/assets/translations/strings.i18n.json +++ b/assets/translations/strings.i18n.json @@ -106,7 +106,9 @@ }, "trueBlack": "True Black", "silentStart": "Silent Start", - "openWorkingDir": "Open Working Directory" + "openWorkingDir": "Open Working Directory", + "ignoreBatteryOptimizations": "Ignore Battery Optimization", + "ignoreBatteryOptimizationsMsg": "Remove restrictions for VPN to work properly" }, "advanced": { "sectionTitle": "Advanced", diff --git a/assets/translations/strings_fa.i18n.json b/assets/translations/strings_fa.i18n.json index 16a4bb80..840225a5 100644 --- a/assets/translations/strings_fa.i18n.json +++ b/assets/translations/strings_fa.i18n.json @@ -106,7 +106,9 @@ }, "trueBlack": "کاملا سیاه", "silentStart": "اجرای ساکت", - "openWorkingDir": "باز کردن دایرکتوری کاری" + "openWorkingDir": "باز کردن دایرکتوری کاری", + "ignoreBatteryOptimizations": "نادیده‌گرفتن بهینه‌سازی باتری", + "ignoreBatteryOptimizationsMsg": "حذف محدودیت‌ها برای عملکرد بهتر VPN" }, "advanced": { "sectionTitle": "پیشرفته", diff --git a/lib/features/settings/view/settings_page.dart b/lib/features/settings/view/settings_page.dart index a8d39d34..21290819 100644 --- a/lib/features/settings/view/settings_page.dart +++ b/lib/features/settings/view/settings_page.dart @@ -19,6 +19,7 @@ class SettingsPage extends HookConsumerWidget { children: [ SettingsSection(t.settings.general.sectionTitle), const GeneralSettingTiles(), + const PlatformSettingsTiles(), const SettingsDivider(), SettingsSection(t.settings.advanced.sectionTitle), const AdvancedSettingTiles(), diff --git a/lib/features/settings/widgets/platform_settings_tiles.dart b/lib/features/settings/widgets/platform_settings_tiles.dart new file mode 100644 index 00000000..37414ca3 --- /dev/null +++ b/lib/features/settings/widgets/platform_settings_tiles.dart @@ -0,0 +1,58 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:hiddify/core/core_providers.dart'; +import 'package:hiddify/services/service_providers.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'platform_settings_tiles.g.dart'; + +@riverpod +Future isIgnoringBatteryOptimizations( + IsIgnoringBatteryOptimizationsRef ref, +) async => + ref + .watch(platformSettingsProvider) + .isIgnoringBatteryOptimizations() + .getOrElse((l) => false) + .run(); + +class PlatformSettingsTiles extends HookConsumerWidget { + const PlatformSettingsTiles({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final t = ref.watch(translationsProvider); + + final isIgnoringBatteryOptimizations = + ref.watch(isIgnoringBatteryOptimizationsProvider); + + ListTile buildIgnoreTile(bool enabled) => ListTile( + title: Text(t.settings.general.ignoreBatteryOptimizations), + subtitle: Text(t.settings.general.ignoreBatteryOptimizationsMsg), + leading: const Icon(Icons.running_with_errors), + enabled: enabled, + onTap: () async { + await ref + .read(platformSettingsProvider) + .requestIgnoreBatteryOptimizations() + .run(); + await Future.delayed(const Duration(seconds: 1)); + ref.invalidate(isIgnoringBatteryOptimizationsProvider); + }, + ); + + return Column( + children: [ + if (Platform.isAndroid) + switch (isIgnoringBatteryOptimizations) { + AsyncData(:final value) when value == false => + buildIgnoreTile(true), + AsyncData(:final value) when value == true => const SizedBox(), + _ => buildIgnoreTile(false), + }, + ], + ); + } +} diff --git a/lib/features/settings/widgets/widgets.dart b/lib/features/settings/widgets/widgets.dart index c69f75e5..e0a96295 100644 --- a/lib/features/settings/widgets/widgets.dart +++ b/lib/features/settings/widgets/widgets.dart @@ -1,4 +1,5 @@ export 'advanced_setting_tiles.dart'; export 'general_setting_tiles.dart'; +export 'platform_settings_tiles.dart'; export 'sections_widgets.dart'; export 'settings_input_dialog.dart'; diff --git a/lib/services/platform_settings.dart b/lib/services/platform_settings.dart new file mode 100644 index 00000000..17267ea2 --- /dev/null +++ b/lib/services/platform_settings.dart @@ -0,0 +1,32 @@ +import 'package:flutter/services.dart'; +import 'package:fpdart/fpdart.dart'; +import 'package:hiddify/utils/utils.dart'; + +class PlatformSettings with InfraLogger { + late final MethodChannel _methodChannel = + const MethodChannel("com.hiddify.app/platform.settings"); + + TaskEither isIgnoringBatteryOptimizations() { + return TaskEither( + () async { + loggy.debug("checking battery optimization status"); + final result = await _methodChannel + .invokeMethod("is_ignoring_battery_optimizations"); + loggy.debug("is ignoring battery optimizations? [$result]"); + return right(result!); + }, + ); + } + + TaskEither requestIgnoreBatteryOptimizations() { + return TaskEither( + () async { + loggy.debug("requesting ignore battery optimization"); + final result = await _methodChannel + .invokeMethod("request_ignore_battery_optimizations"); + loggy.debug("ignore battery optimization result: [$result]"); + return right(result!); + }, + ); + } +} diff --git a/lib/services/service_providers.dart b/lib/services/service_providers.dart index 774b4c66..4c6222aa 100644 --- a/lib/services/service_providers.dart +++ b/lib/services/service_providers.dart @@ -1,6 +1,7 @@ import 'package:hiddify/services/connectivity/connectivity.dart'; import 'package:hiddify/services/files_editor_service.dart'; import 'package:hiddify/services/notification/notification.dart'; +import 'package:hiddify/services/platform_settings.dart'; import 'package:hiddify/services/runtime_details_service.dart'; import 'package:hiddify/services/singbox/singbox_service.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -28,3 +29,7 @@ ConnectivityService connectivityService(ConnectivityServiceRef ref) => ref.watch(singboxServiceProvider), ref.watch(notificationServiceProvider), ); + +@riverpod +PlatformSettings platformSettings(PlatformSettingsRef ref) => + PlatformSettings();