Flutter 使用TextEdit和DirectionBuilder解决iPad问题

Flutter 使用TextEdit和DirectionBuilder解决iPad问题,flutter,ipad,orientation,textedit,Flutter,Ipad,Orientation,Textedit,我有两个文本编辑字段的登录屏幕。 在纵向模式下,有1列包含所有小部件,在横向模式下有2列(左侧为徽标,右侧为其余小部件) 我只在iPad Pro 4 gen iOS 14(设备和模拟器)上体验到在肖像模式下的奇怪行为 当TextEdit get focus键盘开始显示(底部可见的几个像素)时,下一个横向视图(在设备上以纵向方向)显示第二个和下一个屏幕显示纵向视图(无键盘) 在Android和iPhone XS和5s设备上没有问题 import 'dart:convert'; import 'p

我有两个文本编辑字段的登录屏幕。 在纵向模式下,有1列包含所有小部件,在横向模式下有2列(左侧为徽标,右侧为其余小部件)

我只在iPad Pro 4 gen iOS 14(设备和模拟器)上体验到在肖像模式下的奇怪行为

当TextEdit get focus键盘开始显示(底部可见的几个像素)时,下一个横向视图(在设备上以纵向方向)显示第二个和下一个屏幕显示纵向视图(无键盘)

在Android和iPhone XS和5s设备上没有问题

import 'dart:convert';

import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:moja_plytoteka/API/api.dart';
import 'package:moja_plytoteka/AppUtils/globals.dart';
import 'package:moja_plytoteka/AppUtils/gradientButton.dart';
import 'package:moja_plytoteka/AppUtils/sharedPreferences.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
import 'package:email_validator/email_validator.dart';

class LoginScreen extends StatefulWidget {
  LoginScreen({Key key}) : super(key: key);

  @override
  _LoginScreenState createState() => _LoginScreenState();
}

class _LoginScreenState extends State<LoginScreen> {

  final inputEmailController = TextEditingController();
  final inputPasswordController = TextEditingController();
  final myHintStyle = TextStyle(fontSize: 14.0, color: Colors.grey);
  final myTextStyle = TextStyle(fontSize: 14.0, color: Colors.black);
  final myAlertTextStyle = AlertStyle(descStyle: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w400));
  OutlineInputBorder inputBorder = OutlineInputBorder(
          borderSide: const BorderSide(color: appColorPink_2, width: 1.5),
          borderRadius: BorderRadius.circular(inputRadius),
        );

  @override
  void initState() {
    _getName();
    inputEmailController.addListener(_nameChanged);
    super.initState();
  }

  @override
  void dispose() {
    inputEmailController.dispose();
    inputPasswordController.dispose();
    super.dispose();
  }

  void _getName() async {
    inputEmailController.text = await SharedPreferencesSF.getName();
  }

  void _nameChanged(){
    SharedPreferencesSF.setName(inputEmailController.text);
  }

  bool isLandscape() {
    return MediaQuery.of(context).orientation ==  Orientation.landscape ? true : false;
  }

  bool isTablet() {
    return MediaQuery.of(context).size.height > 1000.0 ? true : false;
  }

  @override
  Widget build(BuildContext context) {

    return WillPopScope(
      onWillPop: () async => false,
      child: Scaffold(
        backgroundColor: Colors.white,
        body: OrientationBuilder(
          builder: (context, orientation) {
            return GestureDetector(
              onTap: () {FocusScope.of(context).requestFocus(FocusNode());},
              child: Center(
                  child: orientation == Orientation.portrait 
                    ? _portraitView() 
                    : _landscapeView() 
                    
                    ),
            );
                }
              ),
      ),
    );
  }

  Widget _portraitView(){
    return SafeArea(
            child: Container(
              constraints: BoxConstraints(maxWidth: 600),
              child: Center(
                child: ListView(
                  shrinkWrap: true,
                  padding: EdgeInsets.only(left: 24.0, right: 24.0),
                  children: <Widget>[
                    SizedBox(height: 20.0),
                    Container(child: nameText()),
                    SizedBox(height: isTablet() ? 50.0 : 15.0),
                    logo(),
                    SizedBox(height: isTablet() ? 70.0 : 40.0),
                    loginText(),
                    SizedBox(height: isTablet() ? 20.0 : 0.0),
                    email(),
                    SizedBox(height: 15.0),
                    password(),
                    SizedBox(height: isTablet() ? 70.0 : 20.0),
                    loginButton(),
                    SizedBox(height: 40.0),
                    

                  ],
                ),
              ),
            ),
          );
    }

    Widget _landscapeView(){

    // // Return Your Widget View Here Which you want to Load on Landscape Orientation.
    return Row(
      children: [

        Expanded(
          flex: 1,
          child: SingleChildScrollView(
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        logo(),
                        SizedBox(height: 15.0),
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 30.0),
                          child: nameText(),
                        ),
                      ],
                    ),
          ),
          ),

        Expanded(
          flex: 1,
          child: Stack(
            children: [

              Container(
                color: appColorPink_2,
              ),
              
              Center(
                child: SingleChildScrollView(
                  child: Container(
                    //color: appColorPink_2,
                    child: Padding(
                      padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.height<321 ? 15.0 : 50.0),
                      child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            loginText(),
                            email(),
                            SizedBox(height: 15.0),
                            password(),
                            SizedBox(height: 15.0),
                            loginButton(),
                          ],
                        ),
                    ),
                  ),
                ),
              ),
            ],
          ),
          ),
        
      ]
    );
  }

  _loginProcess(context) async{

    bool loginDataOK = await _checkLoginData(context);
    if(!loginDataOK) return;

    _showLoaderDialog(context);
    LoginResult loginResult = await API.loginCheck(inputEmailController.text, passwordEncoded, deviceid, appToken, context);
    Navigator.pop(context);

    // Login symulation
    var result = await Navigator.of(context).pushNamed('/testAPILogin');
    loginResult = result as LoginResult;
    print('Login result: $loginResult');

    switch (loginResult) {

      case LoginResult.permitted:
        Navigator.of(context).pushNamed('/dashboardScreen');
        break;

      case LoginResult.rejected: {
          Alert(
                context: context,
                type: AlertType.error,
                title: "BŁĄD LOGOWANIA",
                desc: "Niepoprawne dane logowania. Spróbuj ponownie.",
                style: myAlertTextStyle,
                buttons: [
                  DialogButton(
                    child: Text(
                      "OK",
                      style: TextStyle(color: Colors.white, fontSize: 20),
                    ),
                    color: appColorPink_2,
                    onPressed: () => Navigator.pop(context),
                    width: 120,
                  )
                ],
              ).show();
                return ;
              }
        break;

      case LoginResult.tooManyDevices: {
          Alert(
                context: context,
                type: AlertType.warning,
                title: "NIEDOZWOLONE LOGOWANIE",
                desc: "Dla swojego konta masz zarejestrowane za dużo urządzeń. Usuń inne urządzenie żeby zalogować się na tym.",
                style: myAlertTextStyle,
                buttons: [
                  DialogButton(
                    child: Text(
                      "OK",
                      style: TextStyle(color: Colors.white, fontSize: 20),
                    ),
                    color: appColorPink_2,
                    onPressed: () => Navigator.pop(context),
                    width: 120,
                  )
                ],
              ).show();
                return ;
              }
        break;

      default: {
          Alert(
                context: context,
                type: AlertType.error,
                title: "NIEZNANY BŁĄD",
                desc: "Spróbuj zalogować się ponownie.",
                style: myAlertTextStyle,
                buttons: [
                  DialogButton(
                    child: Text(
                      "OK",
                      style: TextStyle(color: Colors.white, fontSize: 20),
                    ),
                    color: appColorPink_2,
                    onPressed: () => Navigator.pop(context),
                    width: 120,
                  )
                ],
              ).show();
                return ;
              }
    }

  }

  Future _checkLoginData(context) async {

    // Check e-mail
    final bool isValid = EmailValidator.validate(inputEmailController.text);

    if (!isValid) {
    Alert(
      context: context,
      type: AlertType.error,
      title: "BŁĄD",
      desc: "Wprowadź poprawny adres e-mail.",
      style: myAlertTextStyle,
      buttons: [
        DialogButton(
          child: Text(
            "OK",
            style: TextStyle(color: Colors.white, fontSize: 20),
          ),
          color: appColorPink_2,
          onPressed: () => Navigator.pop(context),
          width: 120,
        )
      ],
    ).show();
      return false;
    }

    // Check password
    if (inputPasswordController.text.length==0) {
    Alert(
      context: context,
      type: AlertType.error,
      title: "BŁĄD",
      desc: "Wprowadź hasło.",
      style: myAlertTextStyle,
      buttons: [
        DialogButton(
          child: Text(
            "OK",
            style: TextStyle(color: Colors.white, fontSize: 20),
          ),
          color: appColorPink_2,
          onPressed: () => Navigator.pop(context),
          width: 120,
        )
      ],
    ).show();
      return false;
    }

    // MD5 encode
    var bytes = utf8.encode(inputPasswordController.text);
    passwordEncoded = md5.convert(bytes).toString();

    return true;

  }

  _showLoaderDialog(BuildContext context){

      AlertDialog alert=AlertDialog(
        //backgroundColor: appColorPink_2,
        content: Row(
          children: [
            CircularProgressIndicator(
              backgroundColor: Colors.white,
              valueColor: AlwaysStoppedAnimation<Color>(appColorPink_1),
              strokeWidth: 3.0,
            ),
            SizedBox(width: 10.0),
            Container(margin: EdgeInsets.only(left: 7),child:Text("Logowanie..." , style: TextStyle(color: appColorPink_2, fontSize: 18.0, fontWeight: FontWeight.w500),)),
          ],),
      );

      showDialog(barrierDismissible: false,
        context:context,
        builder:(BuildContext context){
          return WillPopScope(
            onWillPop: () async => false,
            child: alert
            );
        },
      );
  }

  nameText() {
    return Padding(
      padding: const EdgeInsets.all(12.0),
      child: Image.asset('assets/images/napis_02.png'),
    );
  }

  logo() {
    return CircleAvatar(
      backgroundColor: appColorPink_2,
      radius: 50.0,
      child: Padding(
        padding: const EdgeInsets.all(12.0),
        child: Image.asset('assets/images/logo_white.png'),
      ),
    );
  }

  loginText() {
    return Padding(
      padding: const EdgeInsets.fromLTRB(0, 0, 0, 15),
      child: Center(child: Text('Logowanie do aplikacji', style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold, color: isLandscape() ? Colors.white : Colors.black),  )),
    );
  }

  email() {
    return TextField(
      controller: inputEmailController,
      keyboardType: TextInputType.emailAddress,
      autofocus: false,
      //initialValue: 'alucard@gmail.com',
      decoration: InputDecoration(
              filled: isLandscape(),
              fillColor: Colors.white,
        hintText: 'Wprowadź e-mail',
        hintStyle: myHintStyle,
        contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
        border: OutlineInputBorder(borderRadius: BorderRadius.circular(inputRadius)),
        focusedBorder: inputBorder,
        isDense: true,
      ),
      style: myTextStyle,
    );
  }

  password() {
    return TextField(
      controller: inputPasswordController,
      autofocus: false,
      //initialValue: 'some password',
      obscureText: true,
      decoration: InputDecoration(
              filled: isLandscape(),
              fillColor: Colors.white,
        hintText: 'Wprowadź hasło',
        contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
        border: OutlineInputBorder(borderRadius: BorderRadius.circular(inputRadius),),
        hintStyle: myHintStyle,
        focusedBorder: inputBorder,
        isDense: true,
      ),
      style: myTextStyle,
    );
  }

  loginButton() {
    return RaisedGradientButton(
      width: double.infinity,
      textPadding: isTablet() ? 20.0 : 15.0,
      text: 'Zaloguj',
      isBorder: isLandscape() ? true : false,
      borderColor: Colors.white,
      gradient: LinearGradient(
        colors: <Color>[appColorPink_1, appColorPink_2],
      ),
      onPressed: (){
        FocusScope.of(context).requestFocus(FocusNode());
        _loginProcess(context);
      }
    );
  }

}
导入'dart:convert';
导入“包:crypto/crypto.dart”;
进口“包装:颤振/材料.省道”;
进口“包装:moja_plytoteka/API/API.dart”;
进口“包装:moja_plytoteka/AppUtils/globals.dart”;
进口“包装:moja_plytoteka/AppUtils/gradientButton.dart”;
导入“包装:moja_plytoteka/AppUtils/SharedReferences.dart”;
导入“package:rflutter_alert/rflutter_alert.dart”;
导入“包:email_validator/email_validator.dart”;
类LoginScreen扩展StatefulWidget{
LoginScreen({Key}):超级(Key:Key);
@凌驾
_LoginsScreenState createState()=>\u LoginsScreenState();
}
类_LoginScreenState扩展状态{
最终输入EmailController=TextEditingController();
final inputPasswordController=TextEditingController();
最终myHintStyle=TextStyle(fontSize:14.0,颜色:Colors.grey);
最终myTextStyle=TextStyle(fontSize:14.0,颜色:Colors.black);
最终myAlertTextStyle=AlertStyle(descStyle:TextStyle(fontSize:14.0,颜色:Colors.black,fontWeight:fontWeight.w400));
OutlineInputBorder inputBorder=OutlineInputBorder(
borderSide:const borderSide(颜色:appColorPink_2,宽度:1.5),
边界半径:边界半径。圆形(inputRadius),
);
@凌驾
void initState(){
_getName();
inputEmailController.addListener(\u name已更改);
super.initState();
}
@凌驾
无效处置(){
inputEmailController.dispose();
inputPasswordController.dispose();
super.dispose();
}
void\u getName()异步{
inputEmailController.text=等待SharedPreferencesf.getName();
}
void _nameChanged(){
SharedPreferencesf.setName(inputEmailController.text);
}
布尔岛景观{
返回MediaQuery.of(context.orientation==orientation.scape?true:false;
}
bool-isTablet(){
返回MediaQuery.of(context).size.height>1000.0?true:false;
}
@凌驾
小部件构建(构建上下文){
返回式示波器(
onWillPop:()async=>false,
孩子:脚手架(
背景颜色:Colors.white,
body:OrientationBuilder(
生成器:(上下文、方向){
返回手势检测器(
onTap:({FocusScope.of(context).requestFocus(FocusNode());},
儿童:中心(
子对象:方向==方向。纵向
()
:_landscapeView()
),
);
}
),
),
);
}
Widget_/view(){
返回安全区(
子:容器(
约束:框约束(最大宽度:600),
儿童:中心(
子:ListView(
收缩膜:对,
填充:仅限边设置(左:24.0,右:24.0),
儿童:[
尺寸箱(高度:20.0),
容器(子项:nameText()),
SizedBox(高度:iTablet()?50.0:15.0),
logo(),
SizedBox(高度:iTablet()?70.0:40.0),
loginText(),
SizedBox(高度:iTablet()?20.0:0.0),
电子邮件(),
尺寸箱(高度:15.0),
密码(),
SizedBox(高度:iTablet()?70.0:20.0),
登录按钮(),
尺寸箱(高度:40.0),
],
),
),
),
);
}
小部件_landscapeView(){
////在此处返回要在横向加载的小部件视图。
返回行(
儿童:[
扩大(
弹性:1,
子:SingleChildScrollView(
子:列(
mainAxisAlignment:mainAxisAlignment.center,
儿童:[
logo(),
尺寸箱(高度:15.0),
填充物(
填充:常量边集。对称(水平:30.0),
子项:nameText(),
),
],
),
),
),
扩大(
弹性:1,
子:堆栈(
儿童:[
容器(
颜色:appColorPink_2,
),
居中(
子:SingleChildScrollView(
子:容器(
//颜色:appColorPink_2,
孩子:填充(
padding:EdgeInsets.symmetric(水平):MediaQuery.of(上下文).size.height Navigator.pop(上下文),
宽度:120,
)
],
).show();
返回;
}
打破
案例登录Result.tooManyDevices:{
警觉的(
上下文:上下文,
类型:AlertType.warning,
标题:“NIEDOZWOLONE LOGOWANIE”,
描述:“斯沃耶戈·孔塔·马斯茨·扎雷杰斯特罗瓦内·扎洛戈瓦奇·西西纳·季姆·乌尔兹涅·乌尔兹涅·乌尔兹涅。”,
样式:myAlertTextStyle,
按钮:[
对话框按钮(
子:文本(
"