Skip to content

Commit 5eda17d

Browse files
authored
feat: support custom mobile device orientations, and listening to device rotations (#5718)
* initial commit * orientation event * Add support for setting device orientations * Rename orientation_lock.py to device_orientation.py * Update documentation for set_allowed_device_orientations method with platform limitations * Add orientation field to test data in test_from_dict.py * Fix typos * remove leftovers | improve docs * cleanup unused classes
1 parent b363cee commit 5eda17d

File tree

16 files changed

+229
-19
lines changed

16 files changed

+229
-19
lines changed

client/android/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
33
zipStoreBase=GRADLE_USER_HOME
44
zipStorePath=wrapper/dists
5-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
5+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip

client/android/settings.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pluginManagement {
1818

1919
plugins {
2020
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
21-
id("com.android.application") version "8.7.3" apply false
21+
id("com.android.application") version "8.12.1" apply false
2222
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
2323
}
2424

packages/flet/lib/src/controls/page.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'dart:async';
12
import 'dart:ui' as ui;
23
import 'dart:ui';
34

@@ -23,6 +24,7 @@ import '../services/service_registry.dart';
2324
import '../utils/device_info.dart';
2425
import '../utils/locale.dart';
2526
import '../utils/numbers.dart';
27+
import '../utils/platform.dart';
2628
import '../utils/platform_utils_web.dart'
2729
if (dart.library.io) "../utils/platform_utils_non_web.dart";
2830
import '../utils/session_store_web.dart'
@@ -61,6 +63,7 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
6163

6264
final Map<int, MultiView> _multiViews = <int, MultiView>{};
6365
bool _registeredFromMultiViews = false;
66+
List<DeviceOrientation>? _appliedDeviceOrientations;
6467

6568
@override
6669
void initState() {
@@ -181,8 +184,19 @@ class _PageControlState extends State<PageControl> with WidgetsBindingObserver {
181184

182185
case "push_route":
183186
_routeState.route = args["route"];
187+
break;
184188
case "get_device_info":
185189
return await getDeviceInfo();
190+
case "set_allowed_device_orientations":
191+
if (isMobilePlatform()) {
192+
var orientations = args["orientations"]
193+
?.map((o) => parseDeviceOrientation(o))
194+
.whereType<DeviceOrientation>()
195+
.toList() ??
196+
List<DeviceOrientation>.from(DeviceOrientation.values);
197+
await SystemChrome.setPreferredOrientations(orientations);
198+
}
199+
break;
186200

187201
default:
188202
throw Exception("Unknown Page method: $name");

packages/flet/lib/src/flet_backend.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ class FletBackend extends ChangeNotifier {
7676
padding: PaddingData(EdgeInsets.zero),
7777
viewPadding: PaddingData(EdgeInsets.zero),
7878
viewInsets: PaddingData(EdgeInsets.zero),
79-
devicePixelRatio: 0);
79+
devicePixelRatio: 0,
80+
orientation: Orientation.portrait);
8081
TargetPlatform platform = defaultTargetPlatform;
8182

8283
late Control _page;

packages/flet/lib/src/protocol/page_media_data.dart

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,27 @@ class PageMediaData extends Equatable {
66
final PaddingData viewPadding;
77
final PaddingData viewInsets;
88
final double devicePixelRatio;
9+
final Orientation orientation;
910

10-
const PageMediaData(
11-
{required this.padding,
12-
required this.viewPadding,
13-
required this.viewInsets,
14-
required this.devicePixelRatio});
11+
const PageMediaData({
12+
required this.padding,
13+
required this.viewPadding,
14+
required this.viewInsets,
15+
required this.devicePixelRatio,
16+
required this.orientation,
17+
});
1518

1619
Map<String, dynamic> toMap() => <String, dynamic>{
1720
'padding': padding.toMap(),
1821
'view_padding': viewPadding.toMap(),
1922
'view_insets': viewInsets.toMap(),
20-
'device_pixel_ratio': devicePixelRatio
23+
'device_pixel_ratio': devicePixelRatio,
24+
'orientation': orientation.name,
2125
};
2226

2327
@override
2428
List<Object?> get props =>
25-
[padding, viewPadding, viewInsets, devicePixelRatio];
29+
[padding, viewPadding, viewInsets, devicePixelRatio, orientation];
2630
}
2731

2832
class PaddingData extends Equatable {

packages/flet/lib/src/utils/device_info.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
import 'package:collection/collection.dart';
12
import 'package:device_info_plus/device_info_plus.dart';
3+
import 'package:flutter/services.dart';
24

35
import 'platform.dart';
46

@@ -161,3 +163,11 @@ extension DeviceInfoExtension on BaseDeviceInfo {
161163
}
162164
}
163165
}
166+
167+
DeviceOrientation? parseDeviceOrientation(String? value,
168+
[DeviceOrientation? defaultValue]) {
169+
if (value == null) return defaultValue;
170+
return DeviceOrientation.values.firstWhereOrNull(
171+
(e) => e.name.toLowerCase() == value.toLowerCase()) ??
172+
defaultValue;
173+
}

packages/flet/lib/src/widgets/page_media.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,12 @@ class _PageMediaState extends State<PageMedia> {
5555
}
5656

5757
var newMedia = PageMediaData(
58-
padding: PaddingData(MediaQuery.paddingOf(context)),
59-
viewPadding: PaddingData(MediaQuery.viewPaddingOf(context)),
60-
viewInsets: PaddingData(MediaQuery.viewInsetsOf(context)),
61-
devicePixelRatio: MediaQuery.devicePixelRatioOf(context));
58+
padding: PaddingData(MediaQuery.paddingOf(context)),
59+
viewPadding: PaddingData(MediaQuery.viewPaddingOf(context)),
60+
viewInsets: PaddingData(MediaQuery.viewInsetsOf(context)),
61+
devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
62+
orientation: MediaQuery.orientationOf(context),
63+
);
6264

6365
if (newMedia != backend.media || !pageSizeUpdated) {
6466
_onMediaChanged(newMedia);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import flet as ft
2+
3+
4+
def main(page: ft.Page) -> None:
5+
page.title = "Device orientation lock"
6+
page.appbar = ft.AppBar(
7+
title=ft.Text("Device orientation Playground"),
8+
center_title=True,
9+
bgcolor=ft.Colors.BLUE,
10+
)
11+
12+
def handle_media_change(e: ft.PageMediaData) -> None:
13+
page.show_dialog(
14+
ft.SnackBar(
15+
f"I see you rotated the device to {e.orientation.name} orientation. 👀",
16+
action="Haha!",
17+
duration=ft.Duration(seconds=3),
18+
)
19+
)
20+
21+
page.on_media_change = handle_media_change
22+
23+
async def on_checkbox_change(e: ft.Event[ft.Checkbox]) -> None:
24+
# get selection
25+
selected = [o for o, checkbox in checkboxes.items() if checkbox.value]
26+
# apply selection
27+
await page.set_allowed_device_orientations(selected)
28+
29+
checkboxes: dict[ft.DeviceOrientation, ft.Checkbox] = {
30+
orientation: ft.Checkbox(
31+
label=orientation.name,
32+
value=True,
33+
on_change=on_checkbox_change,
34+
disabled=not page.platform.is_mobile(), # disabled on non-mobile platforms
35+
)
36+
for orientation in list(ft.DeviceOrientation)
37+
}
38+
39+
page.add(
40+
ft.Text(
41+
spans=[
42+
# shown only on mobile platforms
43+
ft.TextSpan(
44+
"Select the orientations that should remain enabled for the app. "
45+
"If no orientation is selected, the system defaults will be used.",
46+
visible=page.platform.is_mobile(),
47+
),
48+
# shown only on non-mobile platforms
49+
ft.TextSpan(
50+
"Please open this example on a mobile device instead.",
51+
visible=not page.platform.is_mobile(),
52+
style=ft.TextStyle(weight=ft.FontWeight.BOLD),
53+
),
54+
],
55+
),
56+
ft.Column(controls=list(checkboxes.values())),
57+
)
58+
59+
60+
if __name__ == "__main__":
61+
ft.run(main)
Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
11
---
22
class_name: flet.Page
3+
examples: ../../examples/controls/page
4+
example_images: ../examples/controls/page/media
35
---
46

5-
{{ class_all_options(class_name) }}
7+
{{ class_summary(class_name) }}
8+
9+
## Examples
10+
11+
### Listening to keyboard events
12+
13+
```python
14+
--8<-- "{{ examples }}/keyboard_events.py"
15+
```
16+
17+
### Mobile device orientation configuration
18+
19+
Shows how to lock your app to specific device orientations
20+
(e.g., portrait up, landscape right) and listen for orientation changes on mobile devices.
21+
22+
```python
23+
--8<-- "{{ examples }}/device_orientation.py"
24+
```
25+
26+
### App exit confirmation
27+
28+
```python
29+
--8<-- "{{ examples }}/app_exit_confirm_dialog.py"
30+
```
31+
32+
### Hidden app window on startup
33+
34+
```python
35+
--8<-- "{{ examples }}/window_hidden_on_start.py"
36+
```
37+
38+
{{ class_members(class_name) }}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{{ class_all_options("flet.DeviceOrientation", separate_signature=False) }}

0 commit comments

Comments
 (0)