Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reenable rotation to and from landscape mode on Android #470

Merged
merged 11 commits into from
Jan 27, 2022
3 changes: 3 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion frontend/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
Expand Down
27 changes: 13 additions & 14 deletions frontend/lib/entry_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import 'home/home_page.dart';
import 'intro_slides/intro_screen.dart';
import 'themes.dart';

const introRouteName = "/intro";
const homeRouteName = "/home";

class EntryWidget extends StatelessWidget {
const EntryWidget({Key? key}) : super(key: key);

Expand All @@ -26,24 +29,22 @@ class EntryWidget extends StatelessWidget {
if (snapshot.hasError && error != null) {
return ErrorMessage(error.toString());
} else if (snapshot.hasData && settings != null) {
final routes = <String, WidgetBuilder>{};
String? initialRoute;
final routes = <String, WidgetBuilder>{
introRouteName: (context) => IntroScreen(
onFinishedCallback: () => settings.setFirstStart(enabled: false),
),
homeRouteName: (context) => HomePage(
showVerification: configuration.showVerification,
)
};

if (settings.firstStart) {
routes.addAll(<String, WidgetBuilder>{
'/intro': (context) => IntroScreen(
onFinishedCallback: () => settings.setFirstStart(enabled: false),
),
});
initialRoute = '/intro';
}
final String initialRoute = settings.firstStart ? introRouteName : homeRouteName;

return MaterialApp(
title: 'Ehrenamtskarte',
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ThemeMode.system,
initialRoute: initialRoute,
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
Expand All @@ -53,9 +54,7 @@ class EntryWidget extends StatelessWidget {
],
supportedLocales: const [Locale('de')],
locale: const Locale('de'),
home: HomePage(
showVerification: configuration.showVerification,
),
initialRoute: initialRoute,
routes: routes,
);
} else {
Expand Down
3 changes: 2 additions & 1 deletion frontend/lib/intro_slides/intro_screen.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:ehrenamtskarte/entry_widget.dart';
import 'package:flutter/material.dart';
import 'package:intro_slider/intro_slider.dart';
import 'package:intro_slider/slide_object.dart';
Expand Down Expand Up @@ -82,7 +83,7 @@ class IntroScreenState extends State<IntroScreen> {
if (onFinishedCallback != null) {
onFinishedCallback();
}
Navigator.of(context).pop();
Navigator.of(context).pushReplacementNamed(homeRouteName);
}

@override
Expand Down
29 changes: 25 additions & 4 deletions frontend/lib/map/map/map.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import 'dart:io';
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:maplibre_gl/mapbox_gl.dart';

import '../../configuration/configuration.dart';
import 'attribution_dialog.dart';
import 'map_controller.dart';
import 'screen_parent_resizer.dart';

typedef OnFeatureClickCallback = void Function(dynamic feature);
typedef OnNoFeatureClickCallback = void Function();
Expand Down Expand Up @@ -43,8 +45,9 @@ class _MapState extends State<Map> implements MapController {
MaplibreMapController? _controller;
Symbol? _symbol;
bool _permissionGiven = false;
Stack? _mapboxView;
Widget? _currentMapLibreView;
bool _isAnimating = false;
bool _isMapInitialized = false;

@override
void didChangeDependencies() {
Expand All @@ -59,13 +62,16 @@ class _MapState extends State<Map> implements MapController {
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
final compassMargin = Platform.isIOS ? statusBarHeight / pixelRatio : statusBarHeight * pixelRatio;

if (_mapboxView == null || !_isAnimating) {
final Widget? currentMapLibreView = _currentMapLibreView;
final Widget maplibreView;

if (currentMapLibreView == null || !_isAnimating) {
final userLocation = widget.userLocation;
final cameraPosition = userLocation != null
? CameraPosition(target: userLocation, zoom: Map.userLocationZoomLevel)
: const CameraPosition(target: Map.centerOfBavaria, zoom: Map.bavariaZoomLevel);

_mapboxView = Stack(
maplibreView = Stack(
children: [
MaplibreMap(
initialCameraPosition: cameraPosition,
Expand Down Expand Up @@ -106,15 +112,30 @@ class _MapState extends State<Map> implements MapController {
),
],
);
} else {
maplibreView = currentMapLibreView;
}
return _mapboxView ?? Container();

// Apply ScreenParentResizer only on android
// https://github.com/flutter-mapbox-gl/maps/issues/195
return defaultTargetPlatform == TargetPlatform.android
? ScreenParentResizer(
childInitialized: _isMapInitialized,
child: maplibreView,
)
: maplibreView;
}

void _onMapCreated(MaplibreMapController controller) {
_controller = controller;
if (widget.locationAvailable) {
_controller?.updateMyLocationTrackingMode(MyLocationTrackingMode.Tracking);
}

setState(() {
_isMapInitialized = true;
});

final onMapCreated = widget.onMapCreated;
if (onMapCreated != null) {
onMapCreated(this);
Expand Down
98 changes: 98 additions & 0 deletions frontend/lib/map/map/screen_parent_resizer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import 'dart:math';
import 'package:flutter/material.dart';

/// A widget which resizes the child widget twice when the orientation changes.
///
/// * Firstly, the child put in a container with the following constraints:
/// `width = height = max(parentMaxWidth, parentMaxHeight)`. This creates a square.
/// * Secondly, the child is resized to
/// `width = parentMaxWidth, height = parentMaxHeight. This sizes the child according to the parent constraints. This
/// resize only happens if the child is already initialized. If it is not yet initialized then the resize is delayed.
class ScreenParentResizer extends StatelessWidget {
final Widget child;
final bool childInitialized;

const ScreenParentResizer({
Key? key,
required this.child,
required this.childInitialized,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return _InnerScreenParentResizer(
childInitialized: childInitialized,
constraints: constraints,
child: child,
);
},
);
}
}

class _InnerScreenParentResizer extends StatefulWidget {
final Widget? child;
final bool childInitialized;
final BoxConstraints constraints;

const _InnerScreenParentResizer({
Key? key,
required Widget this.child,
required this.childInitialized,
required this.constraints,
}) : super(key: key);

@override
State createState() => _InnerScreenParentResizerState();
}

class _InnerScreenParentResizerState extends State<_InnerScreenParentResizer> {
Orientation? _lastBuildOrientation;
Orientation? _initialOrientationUpdate;

@override
void didChangeDependencies() {
super.didChangeDependencies();

// As soon as the child is initialized we trigger the orientation update
if (widget.childInitialized) {
setState(() {
_lastBuildOrientation = _initialOrientationUpdate;
});
}
}

@override
Widget build(BuildContext context) {
final maximum = max(
widget.constraints.maxWidth,
widget.constraints.maxHeight,
);

final currentOrientation = MediaQuery.of(context).orientation;
final isOverDrawingSquare = currentOrientation != _lastBuildOrientation;

if (isOverDrawingSquare) {
// We only trigger a resize if the child component finished initializing
if (widget.childInitialized) {
setState(() {
_lastBuildOrientation = currentOrientation;
});
} else {
_initialOrientationUpdate = currentOrientation;
}
}

final width = isOverDrawingSquare ? maximum : null;
final height = isOverDrawingSquare ? maximum : null;

return OverflowBox(
alignment: Alignment.topLeft,
maxHeight: height,
maxWidth: width,
child: widget.child,
);
}
}