Skip to content

Commit 1009407

Browse files
committed
Merge commit '018dc41dad335cace94b395041085eb6c7669435' into develop
2 parents 5e7154e + 018dc41 commit 1009407

File tree

12 files changed

+477
-53
lines changed

12 files changed

+477
-53
lines changed

android/app/src/main/kotlin/nl/jknaapen/fladder/MainActivity.kt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
package nl.jknaapen.fladder
22

3+
import BatteryOptimizationPigeon
34
import NativeVideoActivity
45
import PlayerSettingsPigeon
56
import StartResult
67
import TranslationsPigeon
78
import VideoPlayerApi
89
import VideoPlayerControlsCallback
910
import VideoPlayerListenerCallback
11+
import android.annotation.SuppressLint
1012
import android.content.Intent
13+
import android.os.PowerManager
14+
import android.net.Uri
15+
import android.util.Log
16+
import android.provider.Settings
1117
import androidx.activity.result.ActivityResultLauncher
1218
import androidx.activity.result.contract.ActivityResultContracts
19+
import androidx.compose.ui.platform.LocalContext
1320
import com.ryanheise.audioservice.AudioServiceFragmentActivity
1421
import io.flutter.embedding.engine.FlutterEngine
1522
import nl.jknaapen.fladder.objects.PlayerSettingsObject
1623
import nl.jknaapen.fladder.objects.TranslationsMessenger
1724
import nl.jknaapen.fladder.objects.VideoPlayerObject
1825
import nl.jknaapen.fladder.utility.leanBackEnabled
26+
import androidx.core.net.toUri
1927

2028
class MainActivity : AudioServiceFragmentActivity(), NativeVideoActivity {
2129
private lateinit var videoPlayerLauncher: ActivityResultLauncher<Intent>
@@ -47,6 +55,20 @@ class MainActivity : AudioServiceFragmentActivity(), NativeVideoActivity {
4755
api = PlayerSettingsObject
4856
)
4957

58+
BatteryOptimizationPigeon.setUp(
59+
flutterEngine.dartExecutor.binaryMessenger,
60+
api = object : BatteryOptimizationPigeon {
61+
override fun isIgnoringBatteryOptimizations(): Boolean {
62+
val pm = getSystemService(POWER_SERVICE) as PowerManager
63+
return pm.isIgnoringBatteryOptimizations(packageName)
64+
}
65+
66+
override fun openBatteryOptimizationSettings() {
67+
startActivity(Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS))
68+
}
69+
}
70+
)
71+
5072
videoPlayerLauncher = registerForActivityResult(
5173
ActivityResultContracts.StartActivityForResult()
5274
) { result ->
@@ -74,9 +96,7 @@ class MainActivity : AudioServiceFragmentActivity(), NativeVideoActivity {
7496
override fun launchActivity(callback: (Result<StartResult>) -> Unit) {
7597
try {
7698
videoPlayerCallback = callback
77-
7899
val intent = Intent(this, VideoPlayerActivity::class.java)
79-
80100
videoPlayerLauncher.launch(intent)
81101
} catch (e: Exception) {
82102
e.printStackTrace()
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Autogenerated from Pigeon (v26.1.0), do not edit directly.
2+
// See also: https://pub.dev/packages/pigeon
3+
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
4+
5+
6+
import android.util.Log
7+
import io.flutter.plugin.common.BasicMessageChannel
8+
import io.flutter.plugin.common.BinaryMessenger
9+
import io.flutter.plugin.common.EventChannel
10+
import io.flutter.plugin.common.MessageCodec
11+
import io.flutter.plugin.common.StandardMethodCodec
12+
import io.flutter.plugin.common.StandardMessageCodec
13+
import java.io.ByteArrayOutputStream
14+
import java.nio.ByteBuffer
15+
16+
private object BatteryOptimizationPigeonPigeonUtils {
17+
18+
fun wrapResult(result: Any?): List<Any?> {
19+
return listOf(result)
20+
}
21+
22+
fun wrapError(exception: Throwable): List<Any?> {
23+
return if (exception is FlutterError) {
24+
listOf(
25+
exception.code,
26+
exception.message,
27+
exception.details
28+
)
29+
} else {
30+
listOf(
31+
exception.javaClass.simpleName,
32+
exception.toString(),
33+
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
34+
)
35+
}
36+
}
37+
}
38+
39+
private open class BatteryOptimizationPigeonPigeonCodec : StandardMessageCodec() {
40+
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
41+
return super.readValueOfType(type, buffer)
42+
}
43+
44+
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
45+
super.writeValue(stream, value)
46+
}
47+
}
48+
49+
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
50+
interface BatteryOptimizationPigeon {
51+
/** Returns whether the app is currently *ignored* from battery optimizations. */
52+
fun isIgnoringBatteryOptimizations(): Boolean
53+
54+
/** Opens the battery-optimization/settings screen for this app (Android). */
55+
fun openBatteryOptimizationSettings()
56+
57+
companion object {
58+
/** The codec used by BatteryOptimizationPigeon. */
59+
val codec: MessageCodec<Any?> by lazy {
60+
BatteryOptimizationPigeonPigeonCodec()
61+
}
62+
63+
/** Sets up an instance of `BatteryOptimizationPigeon` to handle messages through the `binaryMessenger`. */
64+
@JvmOverloads
65+
fun setUp(
66+
binaryMessenger: BinaryMessenger,
67+
api: BatteryOptimizationPigeon?,
68+
messageChannelSuffix: String = ""
69+
) {
70+
val separatedMessageChannelSuffix =
71+
if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
72+
run {
73+
val channel = BasicMessageChannel<Any?>(
74+
binaryMessenger,
75+
"dev.flutter.pigeon.nl_jknaapen_fladder.settings.BatteryOptimizationPigeon.isIgnoringBatteryOptimizations$separatedMessageChannelSuffix",
76+
codec
77+
)
78+
if (api != null) {
79+
channel.setMessageHandler { _, reply ->
80+
val wrapped: List<Any?> = try {
81+
listOf(api.isIgnoringBatteryOptimizations())
82+
} catch (exception: Throwable) {
83+
BatteryOptimizationPigeonPigeonUtils.wrapError(exception)
84+
}
85+
reply.reply(wrapped)
86+
}
87+
} else {
88+
channel.setMessageHandler(null)
89+
}
90+
}
91+
run {
92+
val channel = BasicMessageChannel<Any?>(
93+
binaryMessenger,
94+
"dev.flutter.pigeon.nl_jknaapen_fladder.settings.BatteryOptimizationPigeon.openBatteryOptimizationSettings$separatedMessageChannelSuffix",
95+
codec
96+
)
97+
if (api != null) {
98+
channel.setMessageHandler { _, reply ->
99+
val wrapped: List<Any?> = try {
100+
api.openBatteryOptimizationSettings()
101+
listOf(null)
102+
} catch (exception: Throwable) {
103+
BatteryOptimizationPigeonPigeonUtils.wrapError(exception)
104+
}
105+
reply.reply(wrapped)
106+
}
107+
} else {
108+
channel.setMessageHandler(null)
109+
}
110+
}
111+
}
112+
}
113+
}

lib/background/update_notifications_worker.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ Future<List<NotificationModel>> _fetchAndNotifyLatestItemsForAccount(
151151
account.credentials.token,
152152
limit,
153153
includeHiddenViews: includeHiddenViews,
154-
since: debug ? lastUpdateCheck.subtract(const Duration(days: 32)) : lastUpdateCheck,
154+
since: debug ? lastUpdateCheck.subtract(const Duration(days: 3)) : lastUpdateCheck,
155155
);
156156

157157
final items = dtoItems.map((d) => ItemBaseModel.fromBaseDto(d, null)).toList();

lib/l10n/app_en.arb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2451,6 +2451,8 @@
24512451
}
24522452
},
24532453
"notificationTimerIOSWarning": " iOS decides when/if updates actually run, notifications may be delayed or not show up at all.",
2454+
"batteryOptimizationDesc": "Battery optimizations may delay background notifications for Fladder.\nTap to open system settings and disable optimizations for Fladder to improve notification reliability.",
2455+
"@batteryOptimizationDesc": {},
24542456
"updateCheckInterval": "Update check interval",
24552457
"updateCheckIntervalDesc": "How often the background task runs",
24562458
"notificationsIntervalClientReminder": "Notification interval is set for all users.",

lib/main.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,13 @@ void main(List<String> args) async {
106106

107107
final sharedPreferences = await SharedPreferences.getInstance();
108108

109-
await NotificationService.init();
110-
try {
111-
await Workmanager().initialize(update_worker.callbackDispatcher);
112-
} catch (e) {
113-
log("Failed to initialize Workmanager for background tasks: $e");
109+
await NotificationService.init().timeout(const Duration(seconds: 5));
110+
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
111+
try {
112+
await Workmanager().initialize(update_worker.callbackDispatcher).timeout(const Duration(seconds: 3));
113+
} catch (e) {
114+
log("Failed to initialize Workmanager for background tasks: $e");
115+
}
114116
}
115117

116118
PackageInfo packageInfo = await PackageInfo.fromPlatform();

lib/providers/update_notifications_provider.dart

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,27 @@ import 'package:fladder/providers/shared_provider.dart';
1515

1616
final supportsNotificationsProvider = Provider.autoDispose<bool>((ref) {
1717
final leanBackMode = ref.watch(argumentsStateProvider.select((value) => value.leanBackMode));
18-
return (!kIsWeb && !leanBackMode) && Platform.isAndroid || Platform.isIOS;
18+
return (!kIsWeb && !leanBackMode) &&
19+
(Platform.isAndroid || Platform.isIOS || Platform.isWindows || Platform.isMacOS || Platform.isLinux);
1920
});
2021

2122
final updateNotificationsProvider = Provider<UpdateNotifications>((ref) => UpdateNotifications(ref));
2223

23-
final notificationsProvider = StateProvider<LastSeenNotificationsModel>((ref) => const LastSeenNotificationsModel());
24+
final notificationsProvider = StateProvider.autoDispose<LastSeenNotificationsModel>((ref) {
25+
final shared = ref.watch(sharedUtilityProvider);
26+
return shared.getLastSeenNotifications();
27+
});
2428

2529
class UpdateNotifications {
26-
UpdateNotifications(this.ref);
30+
UpdateNotifications(this.ref) {
31+
ref.onDispose(() {
32+
_desktopTimer?.cancel();
33+
_desktopTimer = null;
34+
});
35+
}
2736

2837
final Ref ref;
38+
Timer? _desktopTimer;
2939

3040
Future<void> registerBackgroundTask() async {
3141
await Future.delayed(const Duration(milliseconds: 500));
@@ -42,6 +52,15 @@ class UpdateNotifications {
4252
final interval = ref.read(clientSettingsProvider).updateNotificationsInterval;
4353

4454
try {
55+
if (!kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux)) {
56+
_desktopTimer?.cancel();
57+
_desktopTimer = Timer.periodic(interval, (_) {
58+
performHeadlessUpdateCheck();
59+
});
60+
await performHeadlessUpdateCheck();
61+
return;
62+
}
63+
4564
await Workmanager().registerPeriodicTask(
4665
updateTaskName,
4766
updateTaskName,
@@ -59,7 +78,11 @@ class UpdateNotifications {
5978

6079
Future<void> unregisterBackgroundTask() async {
6180
try {
62-
await Workmanager().cancelByUniqueName(updateTaskName);
81+
_desktopTimer?.cancel();
82+
_desktopTimer = null;
83+
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
84+
await Workmanager().cancelByUniqueName(updateTaskName);
85+
}
6386
} catch (e) {
6487
log('Error unregistering background task: $e');
6588
}
@@ -74,8 +97,11 @@ class UpdateNotifications {
7497
.where((a) => a.updateNotificationsEnabled || a.seerrRequestsEnabled)
7598
.toList();
7699
if (accounts.isEmpty) {
77-
log('No accounts have update notifications enabled, unregistering background task');
78-
await Workmanager().cancelByUniqueName(updateTaskName);
100+
_desktopTimer?.cancel();
101+
_desktopTimer = null;
102+
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
103+
await Workmanager().cancelByUniqueName(updateTaskName);
104+
}
79105
return;
80106
}
81107
} catch (e) {
@@ -86,19 +112,27 @@ class UpdateNotifications {
86112
//Used for debug purposes, to trigger the background task immediately and show a notification for any new items
87113
Future<void> executeBackgroundTask() async {
88114
try {
89-
// performHeadlessUpdateCheck(
90-
// debug: true,
91-
// );
92-
await Workmanager().registerOneOffTask(
93-
updateTaskNameDebug,
94-
updateTaskNameDebug,
95-
existingWorkPolicy: ExistingWorkPolicy.replace,
96-
constraints: Constraints(networkType: NetworkType.connected),
97-
);
115+
if (!kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux)) {
116+
await performHeadlessUpdateCheck(debug: true);
117+
return;
118+
} else {
119+
await Workmanager().registerOneOffTask(
120+
updateTaskNameDebug,
121+
updateTaskNameDebug,
122+
existingWorkPolicy: ExistingWorkPolicy.replace,
123+
constraints: Constraints(networkType: NetworkType.connected),
124+
);
125+
}
98126
} catch (e) {
99127
log('Error executing background task: $e');
100128
}
101129
}
102130

103-
Future<void> cancelAllTasks() => Workmanager().cancelAll();
131+
Future<void> cancelAllTasks() async {
132+
_desktopTimer?.cancel();
133+
_desktopTimer = null;
134+
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
135+
await Workmanager().cancelAll();
136+
}
137+
}
104138
}

0 commit comments

Comments
 (0)