Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions android/app/src/main/kotlin/org/lichess/mobileV2/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.app.ActivityManager
import android.content.Context
import android.graphics.Rect
import android.os.Bundle
import android.provider.Settings
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import io.flutter.embedding.android.FlutterActivity
Expand Down Expand Up @@ -45,11 +46,10 @@ class MainActivity: FlutterActivity() {
val totalMemInMb = memoryInfo.totalMem / 1048576L
result.success(totalMemInMb.toInt())
}
// "getAvailableRam" -> {
// val memoryInfo = getAvailableMemory()
// val availMemInMb = memoryInfo.availMem / 1048576L
// result.success(availMemInMb.toInt())
// }
"areAnimationsEnabled" -> {
val animationsEnabled = areAnimationsEnabled()
result.success(animationsEnabled)
}
else -> {
result.notImplemented()
}
Expand All @@ -73,4 +73,30 @@ class MainActivity: FlutterActivity() {
activityManager.getMemoryInfo(memoryInfo)
}
}

private fun areAnimationsEnabled(): Boolean {
return try {
// Check both animator duration scale and transition animation scale
// TRANSITION_ANIMATION_SCALE controls "remove animations" setting
val animatorScale = Settings.Global.getFloat(
contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
1.0f
)
val transitionScale = Settings.Global.getFloat(
contentResolver,
Settings.Global.TRANSITION_ANIMATION_SCALE,
1.0f
)
val windowScale = Settings.Global.getFloat(
contentResolver,
Settings.Global.WINDOW_ANIMATION_SCALE,
1.0f
)
// If any of these are 0, animations are effectively disabled
animatorScale > 0.0f && transitionScale > 0.0f && windowScale > 0.0f
} catch (e: Settings.SettingNotFoundException) {
true
}
}
}
2 changes: 1 addition & 1 deletion lib/src/model/game/game_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ class GameController extends AsyncNotifier<GameState> {

/// Move feedback while playing
void _playMoveFeedback(SanMove sanMove, {bool skipAnimationDelay = false}) {
final animationDuration = ref.read(boardPreferencesProvider).pieceAnimationDuration;
final animationDuration = ref.read(effectivePieceAnimationDurationProvider);

final delay = animationDuration ~/ 2;

Expand Down
19 changes: 19 additions & 0 deletions lib/src/model/settings/board_preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:lichess_mobile/src/model/common/chess.dart';
import 'package:lichess_mobile/src/model/settings/preferences_storage.dart';
import 'package:lichess_mobile/src/styles/styles.dart';
import 'package:lichess_mobile/src/utils/color_palette.dart';
import 'package:lichess_mobile/src/utils/system.dart';

part 'board_preferences.freezed.dart';
part 'board_preferences.g.dart';
Expand All @@ -21,6 +22,24 @@ final boardPreferencesProvider = NotifierProvider<BoardPreferences, BoardPrefs>(
name: 'BoardPreferencesProvider',
);

/// A provider that returns the effective piece animation duration, considering Android system settings.
final effectivePieceAnimationDurationProvider = Provider<Duration>((ref) {
final boardPrefs = ref.watch(boardPreferencesProvider);
final androidAnimationsAsync = ref.watch(androidAnimationsProvider);

final androidAnimationsEnabled = androidAnimationsAsync.maybeWhen(
data: (enabled) => enabled,
orElse: () => true, // Default to enabled if we can't determine
);

// If Android animations are disabled, always return Duration.zero regardless of app setting
if (!androidAnimationsEnabled) {
return Duration.zero;
}

return boardPrefs.pieceAnimationDuration;
});

class BoardPreferences extends Notifier<BoardPrefs> with PreferencesStorage<BoardPrefs> {
@override
@protected
Expand Down
23 changes: 23 additions & 0 deletions lib/src/utils/system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ class System {

throw UnimplementedError('This method is only available on Android');
}

/// Returns whether animations are enabled in Android system settings.
///
/// Only available on Android. On other platforms, returns true (animations enabled).
Future<bool> areAnimationsEnabled() async {
if (Platform.isAndroid) {
try {
final result = await _channel.invokeMethod<bool>('areAnimationsEnabled');
return result ?? true;
} on PlatformException catch (e) {
debugPrint('Failed to get animation settings: ${e.message}');
return true;
} on MissingPluginException catch (_) {
return true;
}
}
return true;
}
}

/// A provider that returns OS version of an Android device.
Expand All @@ -51,3 +69,8 @@ final androidVersionProvider = FutureProvider<AndroidBuildVersion?>((ref) async
final info = await DeviceInfoPlugin().androidInfo;
return info.version;
});

/// A provider that returns whether animations are enabled in Android system settings.
final androidAnimationsProvider = FutureProvider<bool>((ref) async {
return await System.instance.areAnimationsEnabled();
});
4 changes: 2 additions & 2 deletions lib/src/view/game/game_body.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,12 @@ class GameBody extends ConsumerWidget {
youAre == Side.white && !isBoardTurned || youAre == Side.black && isBoardTurned;
final topPlayer = topPlayerIsBlack ? black : white;
final bottomPlayer = topPlayerIsBlack ? white : black;

final pieceAnimationDuration = ref.read(effectivePieceAnimationDurationProvider);
final animationDuration =
gameState.game.meta.speed == Speed.ultraBullet ||
gameState.game.meta.speed == Speed.bullet
? Duration.zero
: boardPreferences.pieceAnimationDuration;
: pieceAnimationDuration;

return FocusDetector(
onFocusRegained: () {
Expand Down
84 changes: 59 additions & 25 deletions lib/src/view/settings/board_settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,18 @@ class _Body extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final boardPrefs = ref.watch(boardPreferencesProvider);
final isAndroid = Theme.of(context).platform == TargetPlatform.android;

final androidVersionAsync = ref.watch(androidVersionProvider);
// Only watch Android-specific providers on Android
final androidVersionAsync = isAndroid ? ref.watch(androidVersionProvider) : null;
final androidAnimationsAsync = isAndroid ? ref.watch(androidAnimationsProvider) : null;

// Only refresh on Android
if (isAndroid) {
WidgetsBinding.instance.addPostFrameCallback((_) {
ref.invalidate(androidAnimationsProvider);
});
}

return ListView(
children: [
Expand Down Expand Up @@ -70,32 +80,56 @@ class _Body extends ConsumerWidget {
);
},
),
SwitchSettingTile(
title: Text(context.l10n.preferencesPieceAnimation),
value: boardPrefs.pieceAnimation,
onChanged: (value) {
ref.read(boardPreferencesProvider.notifier).togglePieceAnimation();
Consumer(
builder: (context, ref, child) {
// Only check animations on Android, default to enabled for others
final systemAnimationsEnabled =
!isAndroid ||
(androidAnimationsAsync?.maybeWhen(
data: (enabled) => enabled,
orElse: () => true,
) ??
true);

return SwitchSettingTile(
title: Text(context.l10n.preferencesPieceAnimation),
subtitle: systemAnimationsEnabled
? null
: Text(
'Disabled by system settings',
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6),
),
),
value: systemAnimationsEnabled && boardPrefs.pieceAnimation,
onChanged: systemAnimationsEnabled
? (value) {
ref.read(boardPreferencesProvider.notifier).togglePieceAnimation();
}
: null,
);
},
),
if (Theme.of(context).platform == TargetPlatform.android && !isTabletOrLarger(context))
androidVersionAsync.maybeWhen(
data: (version) => version != null && version.sdkInt >= 29
? SwitchSettingTile(
title: Text(context.l10n.mobileSettingsImmersiveMode),
subtitle: Text(
context.l10n.mobileSettingsImmersiveModeSubtitle,
maxLines: 5,
),
value: boardPrefs.immersiveModeWhilePlaying ?? false,
onChanged: (value) {
ref
.read(boardPreferencesProvider.notifier)
.toggleImmersiveModeWhilePlaying();
},
)
: const SizedBox.shrink(),
orElse: () => const SizedBox.shrink(),
),
if (isAndroid && !isTabletOrLarger(context))
androidVersionAsync?.maybeWhen(
data: (version) => version != null && version.sdkInt >= 29
? SwitchSettingTile(
title: Text(context.l10n.mobileSettingsImmersiveMode),
subtitle: Text(
context.l10n.mobileSettingsImmersiveModeSubtitle,
maxLines: 5,
),
value: boardPrefs.immersiveModeWhilePlaying ?? false,
onChanged: (value) {
ref
.read(boardPreferencesProvider.notifier)
.toggleImmersiveModeWhilePlaying();
},
)
: const SizedBox.shrink(),
orElse: () => const SizedBox.shrink(),
) ??
const SizedBox.shrink(),
SwitchSettingTile(
title: Text(context.l10n.preferencesPieceDestinations),
value: boardPrefs.showLegalMoves,
Expand Down