Commit fad0912954bce4f2efa4b3e4ae9637c55f4a8476

Authored by wdn127929
1 parent 19a76fcf

f

Showing 53 changed files with 1220 additions and 3305 deletions

Too many changes to show.

To preserve performance only 53 of 53+ files are displayed.

... ... @@ -12,14 +12,14 @@ if (flutterRoot == null) {
}
def flutterVersionCode = "3580"
def flutterVersionCode = "3581"
if (flutterVersionCode == null) {
flutterVersionCode = '3580'
flutterVersionCode = '3581'
}
def flutterVersionName = "3.5.8.0"
def flutterVersionName = "3.5.8.1"
if (flutterVersionName == null) {
flutterVersionName = '3.5.8.0'
flutterVersionName = '3.5.8.1'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
... ... @@ -28,7 +28,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
apply plugin: 'com.huawei.agconnect'
android {
compileSdkVersion 28
compileSdkVersion 31
sourceSets {
... ... @@ -43,7 +43,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.naji.one_key"
minSdkVersion 21
targetSdkVersion 28
targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
... ...
... ... @@ -21,7 +21,7 @@
<uses-permission android:name="com.huawei.android.launcher.permission.CHANGE_BADGE"/>
<application
android:name="io.flutter.app.FlutterApplication"
android:label="一键订"
tools:replace="android:label"
android:icon="@mipmap/ic_launcher">
... ... @@ -44,6 +44,10 @@
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background" />
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
... ...
buildscript {
ext.kotlin_version = '1.3.50'
// ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.6.20'
repositories {
google()
jcenter()
... ...
# <<<<<<< HEAD
# #Mon Dec 20 13:10:07 CST 2021
# distributionBase=GRADLE_USER_HOME
# distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
# =======
#Thu Dec 23 01:26:57 CST 2021
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
... ...
... ... @@ -66,7 +66,7 @@ Future<void> main() async {
FluroRouter router = FluroRouter();
Routes.configureRoutes(router);
App.router = router;
await App.initSp();
// await App.initSp();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]).then((_) {
... ... @@ -79,7 +79,7 @@ Future<void> main() async {
create: (_) => UserModel(),
),
ChangeNotifierProvider<MessageDialogModel>(
create: (_) => MessageDialogModel()..init(),
create: (_) => MessageDialogModel(),
),
// ChangeNotifierProvider<OnlineCheckEditProvider>(
// create: (_) => OnlineCheckEditProvider(),
... ... @@ -181,7 +181,7 @@ class MyAppState extends State<MyApp> {
//第一次打开应用
_isFirst() async {
isFirst = App.sp.getBool("isFirst") ?? true;
// isFirst = App.sp.getBool("isFirst") ?? true;
isFirst = true;
if (!isFirst) {
await _ontherInit();
... ...
... ... @@ -228,28 +228,28 @@ class DioUtil {
} catch (e) {
Loading.hideLoading(context);
return new Future.error(new DioError(
// requestOptions: response.requestOptions,
requestOptions: response.requestOptions,
response: response,
error: "data parsing exception...",
type: DioErrorType.RESPONSE,
type: DioErrorType.response,
));
}
}
Loading.hideLoading(context);
return new Future.error(new DioError(
// requestOptions: response.requestOptions,
requestOptions: response.requestOptions,
response: response,
error: "statusCode: $response.statusCode, service error",
type: DioErrorType.RESPONSE,
type: DioErrorType.response,
));
} on DioError catch (error) {
if (showLoading) {
Loading.hideLoading(context);
}
if (error is DioError && error.type == DioErrorType.CONNECT_TIMEOUT) {
if (error is DioError && error.type == DioErrorType.connectTimeout) {
// Utils.showToast("网络链接超时,请稍后重试");
}
if (error.type != DioErrorType.CANCEL) {
if (error.type != DioErrorType.cancel) {
throw MyError(type: 2);
}
}
... ... @@ -298,18 +298,18 @@ class DioUtil {
return new BaseRespR(_status, _code, _msg, _data, response);
} catch (e) {
return new Future.error(new DioError(
// requestOptions: response.requestOptions,
requestOptions: response.requestOptions,
response: response,
error: "data parsing exception...",
type: DioErrorType.RESPONSE,
type: DioErrorType.response,
));
}
}
return new Future.error(new DioError(
// requestOptions: response.requestOptions,
requestOptions: response.requestOptions,
response: response,
error: "statusCode: $response.statusCode, service error",
type: DioErrorType.RESPONSE,
type: DioErrorType.response,
));
}
... ...
... ... @@ -206,7 +206,7 @@ class _MapChoicePointPageState extends State<MapChoicePointPage> {
// }
},
onMapMoveEnd: (MapMove move) async {
_loadData(move.latLng);
_loadData(move.coordinate);
},
),
Center(
... ...
... ... @@ -2385,7 +2385,7 @@ class _ButtonState extends State<CustomButton> {
inputFormatters: [
LengthLimitingTextInputFormatter(3),
WhitelistingTextInputFormatter.digitsOnly
FilteringTextInputFormatter.digitsOnly
],
//光标颜色
cursorColor: widget.isSelect
... ...
... ... @@ -885,7 +885,7 @@ class _ButtonState extends State<CustomButton> {
inputFormatters: [
LengthLimitingTextInputFormatter(3),
WhitelistingTextInputFormatter.digitsOnly
FilteringTextInputFormatter.digitsOnly
],
//光标颜色
cursorColor: widget.isSelect
... ...
... ... @@ -65,96 +65,101 @@ class HomePageState extends State<HomePage> with WidgetsBindingObserver {
@override
void initState() {
// TODO: implement initState
super.initState();
LogUtil.v("home_initState");
AzhonAppUpdate.listener(
// () {
// needUpdate = false;
// // print(map['type']);
// }
);
WidgetsBinding.instance.addObserver(this);
_currIndex = widget.initialIndex;
_pageController = PageController(initialPage: _currIndex);
pages
// ..add(ChangeNotifierProvider<HomeEnterModel>(
// create: (_) => HomeEnterModel(),
// child: ChangeNotifierProvider<RemarkManagerModel>(
// create: (_) => RemarkManagerModel(),
// child: SafeArea(child: HomeEnterPage()),
// )))
..add(SafeArea(
child: MessagePage(),
))
// ..add(ChangeNotifierProvider<EnterModel>(
// create: (_) => EnterModel(),
// child: SafeArea(child: EnterPage(detailEntity:NotebookDetail.def(context),rid: "-1",)),
// ))
..add(ChangeNotifierProvider<NoteBookModel>(
create: (_) => NoteBookModel(),
child: SafeArea(child: NoteBookPage()),
))
..add(ChangeNotifierProvider<TableManagerModel>(
create: (_) => TableManagerModel(),
child: TableManagerPage(),
))
..add(ChangeNotifierProvider<MyModel>(
create: (_) => MyModel(),
child: MyPage(),
));
WidgetsBinding.instance.addPostFrameCallback((_) {
initPlatformState();
List<PullMarketingByUidSunModel> data =
Provider.of<UserModel>(context, listen: false)
.pullMarketingByUidSunModel;
if (data != null) {
if (data.length > 0) {
Future.delayed(Duration(milliseconds: 300), () {
DialogUtil.cupertinoAlertDialog(context,
title: "全店任务",
content: "管理员发布了新的任务,请立即查看并完成任务",
lTitle: "稍后提醒",
rTitle: "立即查看", lCallback: () {
print("稍后提醒");
// 30分钟后重新弹窗
_startTimerSeller();
}, rCallback: () {
_endTimerSeller();
if (data.length == 1) {
//进入营销详情
NavigatorUtil.goSellerMyMarketingRecordsDetailPage(
context, data[0].mid,
fromNotify: true);
} else {
//进入我的客情首页
NavigatorUtil.goMyCustomerPage(context);
}
});
});
}
}
if (widget.initialIndex == 0) return;
_getIntent();
Future.delayed(Duration(milliseconds: 30000), () {
ContactModel.getInstance().uploadLocalContact(context);
if (needUpdate) {
UpdateModelMy updateModel = new UpdateModelMy();
updateModel.checkUpdate(context, (version) {
updateModel.showUpdateDialog(context, version);
}, showToast: false);
}
});
timer = Timer.periodic(Duration(minutes: 3), (_) {
_getAllUnMessages();
});
_getUnMsgNum();
});
}
// @override
// void initState() {
// // TODO: implement initState
// super.initState();
// LogUtil.v("home_initState");
// AzhonAppUpdate.listener(
// // () {
// // needUpdate = false;
// // // print(map['type']);
// // }
// );
// WidgetsBinding.instance.addObserver(this);
// _currIndex = widget.initialIndex;
// _pageController = PageController(initialPage: _currIndex);
// pages
// // ..add(ChangeNotifierProvider<HomeEnterModel>(
// // create: (_) => HomeEnterModel(),
// // child: ChangeNotifierProvider<RemarkManagerModel>(
// // create: (_) => RemarkManagerModel(),
// // child: SafeArea(child: HomeEnterPage()),
// // )))
// ..add(SafeArea(
// child: MessagePage(),
// ))
// // ..add(ChangeNotifierProvider<EnterModel>(
// // create: (_) => EnterModel(),
// // child: SafeArea(child: EnterPage(detailEntity:NotebookDetail.def(context),rid: "-1",)),
// // ))
// ..add(ChangeNotifierProvider<NoteBookModel>(
// create: (_) => NoteBookModel(),
// child: SafeArea(child: NoteBookPage()),
// ))
// ..add(ChangeNotifierProvider<TableManagerModel>(
// create: (_) => TableManagerModel(),
// child: TableManagerPage(),
// ))
// ..add(ChangeNotifierProvider<MyModel>(
// create: (_) => MyModel(),
// child: MyPage(),
// ));
// WidgetsBinding.instance.addPostFrameCallback((_) {
// initPlatformState();
// List<PullMarketingByUidSunModel> data =
// Provider.of<UserModel>(context, listen: false)
// .pullMarketingByUidSunModel;
// if (data != null) {
// if (data.length > 0) {
// Future.delayed(Duration(milliseconds: 300), () {
// DialogUtil.cupertinoAlertDialog(context,
// title: "全店任务",
// content: "管理员发布了新的任务,请立即查看并完成任务",
// lTitle: "稍后提醒",
// rTitle: "立即查看", lCallback: () {
// print("稍后提醒");
// // 30分钟后重新弹窗
// _startTimerSeller();
// }, rCallback: () {
// _endTimerSeller();
// if (data.length == 1) {
// //进入营销详情
// NavigatorUtil.goSellerMyMarketingRecordsDetailPage(
// context, data[0].mid,
// fromNotify: true);
// } else {
// //进入我的客情首页
// NavigatorUtil.goMyCustomerPage(context);
// }
// });
// });
// }
// }
// if (widget.initialIndex == 0) return;
// _getIntent();
// Future.delayed(Duration(milliseconds: 30000), () {
// ContactModel.getInstance().uploadLocalContact(context);
// if (needUpdate) {
// UpdateModelMy updateModel = new UpdateModelMy();
// updateModel.checkUpdate(context, (version) {
// updateModel.showUpdateDialog(context, version);
// }, showToast: false);
// }
// });
// timer = Timer.periodic(Duration(minutes: 3), (_) {
// _getAllUnMessages();
// });
// _getUnMsgNum();
// });
// }
_endTimerSeller() {
if (timerSeller != null) {
timerSeller.cancel();
... ... @@ -599,6 +604,7 @@ class HomePageState extends State<HomePage> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
return Container(width: 300, height: 300, color: Colors.pink);
// TODO: implement build
ScreenUtil.init(context, width: 750, height: 1334);
return new WillPopScope(
... ... @@ -674,12 +680,13 @@ class HomePageState extends State<HomePage> with WidgetsBindingObserver {
)
],
),
title: Text(
"消息中心",
style: TextStyle(
color: _currIndex == 0 ? _activeColor : _defaultColor,
fontSize: ScreenUtil().setSp(22)),
),
label:
// Text(
"消息中心",
// style: TextStyle(
// color: _currIndex == 0 ? _activeColor : _defaultColor,
// fontSize: ScreenUtil().setSp(22)),
// ),
),
// BottomNavigationBarItem(
// icon: Image(
... ... @@ -710,12 +717,13 @@ class HomePageState extends State<HomePage> with WidgetsBindingObserver {
width: ScreenUtil().setWidth(48),
height: ScreenUtil().setWidth(48),
),
title: Text(
"已预订",
style: TextStyle(
color: _currIndex == 1 ? _activeColor : _defaultColor,
fontSize: ScreenUtil().setSp(22)),
),
label:
// Text(
"已预订",
// style: TextStyle(
// color: _currIndex == 1 ? _activeColor : _defaultColor,
// fontSize: ScreenUtil().setSp(22)),
// ),
),
BottomNavigationBarItem(
icon: Image(
... ... @@ -728,12 +736,13 @@ class HomePageState extends State<HomePage> with WidgetsBindingObserver {
width: ScreenUtil().setWidth(48),
height: ScreenUtil().setWidth(48),
),
title: Text(
"桌位",
style: TextStyle(
color: _currIndex == 2 ? _activeColor : _defaultColor,
fontSize: ScreenUtil().setSp(22)),
),
label:
// Text(
"桌位",
// style: TextStyle(
// color: _currIndex == 2 ? _activeColor : _defaultColor,
// fontSize: ScreenUtil().setSp(22)),
// ),
),
BottomNavigationBarItem(
icon: Image(
... ... @@ -746,12 +755,13 @@ class HomePageState extends State<HomePage> with WidgetsBindingObserver {
width: ScreenUtil().setWidth(48),
height: ScreenUtil().setWidth(48),
),
title: Text(
"我的",
style: TextStyle(
color: _currIndex == 3 ? _activeColor : _defaultColor,
fontSize: ScreenUtil().setSp(22)),
),
label:
// Text(
"我的",
// style: TextStyle(
// color: _currIndex == 3 ? _activeColor : _defaultColor,
// fontSize: ScreenUtil().setSp(22)),
// ),
),
],
type: BottomNavigationBarType.fixed,
... ...
... ... @@ -1554,7 +1554,7 @@ class _ButtonState extends State<CustomButton> {
inputFormatters: [
LengthLimitingTextInputFormatter(3),
WhitelistingTextInputFormatter.digitsOnly
FilteringTextInputFormatter.digitsOnly
],
//光标颜色
cursorColor: widget.isSelect
... ...
... ... @@ -174,7 +174,7 @@ class LoginState extends State<LoginPage> with WidgetsBindingObserver {
});
} else {
_checkWechat();
_getWxCode();
_getWxCode();
}
}
... ... @@ -413,510 +413,504 @@ class LoginState extends State<LoginPage> with WidgetsBindingObserver {
final double prefixIconWidth = ScreenUtil().setWidth(24);
return Scaffold(
backgroundColor: MyColours.color_ffffff,
body: Container(
width: double.infinity,
height: double.infinity,
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
backgroundColor: MyColours.color_ffffff,
body: Container(
width: double.infinity,
height: double.infinity,
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// VEmptyView(218 - App.statusBarHeight),
VEmptyView(80 + App.statusBarHeight),
Padding(
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(40)),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.asset(
"assets/logo.png",
width: ScreenUtil().setWidth(40 * 2),
height: ScreenUtil().setWidth(40 * 2),
)
],
)),
VEmptyView(120),
Image.asset(
Utils.getImgPath('login_bg'),
),
// Container(
// height: ScreenUtil().setHeight(84),
// width: ScreenUtil().setWidth(611),
// padding: EdgeInsets.only(left: ScreenUtil().setWidth(20)),
// alignment: Alignment.center,
// decoration: BoxDecoration(
// border: Border.all(
// color: MyColours.color_bff6f7,
// width: ScreenUtil().setWidth(2)),
// borderRadius:
// BorderRadius.circular(ScreenUtil().setWidth(8))),
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// Image.asset(
// "assets/icon_sellercode.png",
// width: prefixIconWidth,
// ),
// HEmptyView(26),
// Center(
// child: Text(
// "商户编号",
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28)),
// )),
// Expanded(
// child: Container(
// height: ScreenUtil().setHeight(84),
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(20),
// right: ScreenUtil().setWidth(20),
// top: ScreenUtil().setWidth(8)),
// child: TextField(
// controller: _sellerCodeController,
// focusNode: _sellerCodeFocusNode,
// // autofocus: _sellerCodeController.text.isEmpty,
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28),
// textBaseline: TextBaseline.alphabetic),
// // textAlignVertical: TextAlignVertical.center,
// decoration: InputDecoration(
// hintText: "请输入6位商户编码",
// hintStyle: TextStyle(
// color: MyColours.color_c1c1c1,
// fontSize: ScreenUtil().setSp(28),
// ),
// border: OutlineInputBorder(
// borderSide: BorderSide.none),
// contentPadding: EdgeInsets.zero,
// ),
// onChanged: (_) {
// setActivate();
// },
// keyboardType: TextInputType.number,
// )),
// )
// ],
// )),
// Container(
// height: ScreenUtil().setHeight(84),
// width: ScreenUtil().setWidth(611),
// padding: EdgeInsets.only(left: ScreenUtil().setWidth(20)),
// margin: EdgeInsets.only(top: ScreenUtil().setHeight(8)),
// alignment: Alignment.center,
// decoration: BoxDecoration(
// border: Border.all(
// color: MyColours.color_bff6f7,
// width: ScreenUtil().setWidth(2)),
// borderRadius:
// BorderRadius.circular(ScreenUtil().setWidth(8))),
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// Image.asset(
// "assets/icon_account.png",
// width: prefixIconWidth,
// ),
// HEmptyView(26),
// Text(
// "工 号",
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28)),
// ),
// Expanded(
// child: Container(
// height: ScreenUtil().setHeight(84),
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(20),
// right: ScreenUtil().setWidth(20),
// top: ScreenUtil().setWidth(8)),
// child: TextField(
// controller: _accountController,
// focusNode: _accountFocusNode,
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28)),
// decoration: InputDecoration(
// hintText: "请输入4位工号",
// hintStyle: TextStyle(
// color: MyColours.color_c1c1c1,
// fontSize: ScreenUtil().setSp(28)),
// border: OutlineInputBorder(
// borderSide: BorderSide.none),
// contentPadding: EdgeInsets.zero,
// ),
// onChanged: (_) {
// setActivate();
// },
// keyboardType: TextInputType.number,
// ),
// ))
// ],
// )),
// Container(
// height: ScreenUtil().setHeight(84),
// width: ScreenUtil().setWidth(611),
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(20),
// right: ScreenUtil().setWidth(27)),
// margin: EdgeInsets.only(top: ScreenUtil().setHeight(8)),
// alignment: Alignment.center,
// decoration: BoxDecoration(
// border: Border.all(
// color: MyColours.color_bff6f7,
// width: ScreenUtil().setWidth(2)),
// borderRadius:
// BorderRadius.circular(ScreenUtil().setWidth(8))),
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// Image.asset(
// "assets/icon_pwd.png",
// width: prefixIconWidth,
// ),
// HEmptyView(26),
// Text(
// "密 码",
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28)),
// ),
// Expanded(
// child: Container(
// height: ScreenUtil().setHeight(84),
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(20),
// right: ScreenUtil().setWidth(20),
// top: ScreenUtil().setWidth(8)),
// child: TextField(
// controller: _pwdController,
// focusNode: _pwdFocusNode,
// // autofocus: _sellerCodeController.text.isNotEmpty &&
// // _accountController.text.isNotEmpty &&
// // _pwdController.text.isEmpty,
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28)),
// decoration: InputDecoration(
// hintText: "请输入密码",
// hintStyle: TextStyle(
// color: MyColours.color_c1c1c1,
// fontSize: ScreenUtil().setSp(28)),
// border: OutlineInputBorder(
// borderSide: BorderSide.none),
// contentPadding: EdgeInsets.zero,
// ),
// onChanged: (_) {
// setActivate();
// },
// obscureText: obscureText,
// keyboardType: TextInputType.text,
// )),
// ),
// GestureDetector(
// child: Padding(
// padding: EdgeInsets.all(ScreenUtil().setWidth(10)),
// child: Image.asset(
// obscureText
// ? "assets/icon_pwd_show.png"
// : "assets/icon_pwd_hide.png",
// width: ScreenUtil().setWidth(35),
// height: ScreenUtil().setHeight(24),
// ),
// ),
// onTap: () {
// setState(() {
// obscureText = !obscureText;
// });
// },
// )
// ],
// )),
// Container(
// width: ScreenUtil().setWidth(611),
// margin: EdgeInsets.only(top: ScreenUtil().setHeight(16)),
// alignment: Alignment.centerLeft,
// child: Text(
// errorText,
// // 'hfhfhf',
// style: TextStyle(
// color: MyColours.color_d93535,
// fontSize: ScreenUtil().setSp(16)),
// ),
// ),
VEmptyView(60),
// Consumer<UserModel>(
// builder:
// (BuildContext context, UserModel value, Widget child) {
// return GestureDetector(
// onTap: () {
// if (!isActivate) return;
// if (!_check) {
// Utils.showToast('请先仔细阅读并同意协议');
// return;
// }
// if (App.sp.containsKey('unionid')) {
// //微信登陆的时候,退出清空数据
// App.sp.remove('openid');
// App.sp.remove("unionid");
// }
// Map<String, String> params = {
// "seller_code": "${_sellerCodeController.text}",
// "alias": "ssahdahgdafgahjkhakjgh",
// "account": "${_accountController.text}",
// "password": "${_pwdController.text}"
// };
// value.login(context, params).then((value) {
// setState(() {
// errorText = value ?? "";
// });
// });
// },
// child: Container(
// height: ScreenUtil().setWidth(84),
// width: ScreenUtil().setWidth(611),
// alignment: Alignment.center,
// decoration: BoxDecoration(
// color: isActivate
// ? MyColours.color_2bb5b7
// : MyColours.color_2bb5b7,
// boxShadow: [
// BoxShadow(
// color: MyColours.color_26000000,
// offset: Offset(0, ScreenUtil().setWidth(4)),
// )
// ],
// borderRadius: BorderRadius.all(
// Radius.circular(ScreenUtil().setWidth(8)))),
// child: Text(
// "登录",
// style: TextStyle(
// color: MyColours.color_ffffff,
// fontSize: ScreenUtil().setSp(34)),
// ),
// ),
// );
// },
// ),
Row(
// mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// VEmptyView(218 - App.statusBarHeight),
VEmptyView(80 + App.statusBarHeight),
Padding(
padding: EdgeInsets.symmetric(
horizontal: ScreenUtil().setWidth(40)),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.asset(
"assets/logo.png",
width: ScreenUtil().setWidth(40 * 2),
height: ScreenUtil().setWidth(40 * 2),
)
],
InkWell(
onTap: () {
setState(() {
_check = !_check;
});
},
child: Container(
alignment: Alignment.topCenter,
// color: Colors.red,
height: ScreenUtil().setWidth(110),
padding: EdgeInsets.only(
right: ScreenUtil().setWidth(30),
left: ScreenUtil().setWidth(102),
bottom: ScreenUtil().setWidth(10)),
child: Image.asset(
_check
? "assets/mine_radio_sel.png"
: "assets/mine_radio_nor.png",
width: ScreenUtil().setWidth(26),
height: ScreenUtil().setWidth(26)),
)),
VEmptyView(120),
Image.asset(
Utils.getImgPath('login_bg'),
// HEmptyView(30),
Padding(
padding: EdgeInsets.only(top: ScreenUtil().setWidth(2)),
child: Text(
'已阅读并同意',
style: TextStyle(
fontSize: ScreenUtil().setSp(24),
color: MyColours.color_999999),
),
),
// Container(
// height: ScreenUtil().setHeight(84),
// width: ScreenUtil().setWidth(611),
// padding: EdgeInsets.only(left: ScreenUtil().setWidth(20)),
// alignment: Alignment.center,
// decoration: BoxDecoration(
// border: Border.all(
// color: MyColours.color_bff6f7,
// width: ScreenUtil().setWidth(2)),
// borderRadius:
// BorderRadius.circular(ScreenUtil().setWidth(8))),
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// Image.asset(
// "assets/icon_sellercode.png",
// width: prefixIconWidth,
// ),
// HEmptyView(26),
// Center(
// child: Text(
// "商户编号",
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28)),
// )),
// Expanded(
// child: Container(
// height: ScreenUtil().setHeight(84),
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(20),
// right: ScreenUtil().setWidth(20),
// top: ScreenUtil().setWidth(8)),
// child: TextField(
// controller: _sellerCodeController,
// focusNode: _sellerCodeFocusNode,
// // autofocus: _sellerCodeController.text.isEmpty,
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28),
// textBaseline: TextBaseline.alphabetic),
// // textAlignVertical: TextAlignVertical.center,
// decoration: InputDecoration(
// hintText: "请输入6位商户编码",
// hintStyle: TextStyle(
// color: MyColours.color_c1c1c1,
// fontSize: ScreenUtil().setSp(28),
// ),
// border: OutlineInputBorder(
// borderSide: BorderSide.none),
// contentPadding: EdgeInsets.zero,
// ),
// onChanged: (_) {
// setActivate();
// },
// keyboardType: TextInputType.number,
// )),
// )
// ],
// )),
// Container(
// height: ScreenUtil().setHeight(84),
// width: ScreenUtil().setWidth(611),
// padding: EdgeInsets.only(left: ScreenUtil().setWidth(20)),
// margin: EdgeInsets.only(top: ScreenUtil().setHeight(8)),
// alignment: Alignment.center,
// decoration: BoxDecoration(
// border: Border.all(
// color: MyColours.color_bff6f7,
// width: ScreenUtil().setWidth(2)),
// borderRadius:
// BorderRadius.circular(ScreenUtil().setWidth(8))),
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// Image.asset(
// "assets/icon_account.png",
// width: prefixIconWidth,
// ),
// HEmptyView(26),
// Text(
// "工 号",
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28)),
// ),
// Expanded(
// child: Container(
// height: ScreenUtil().setHeight(84),
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(20),
// right: ScreenUtil().setWidth(20),
// top: ScreenUtil().setWidth(8)),
// child: TextField(
// controller: _accountController,
// focusNode: _accountFocusNode,
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28)),
// decoration: InputDecoration(
// hintText: "请输入4位工号",
// hintStyle: TextStyle(
// color: MyColours.color_c1c1c1,
// fontSize: ScreenUtil().setSp(28)),
// border: OutlineInputBorder(
// borderSide: BorderSide.none),
// contentPadding: EdgeInsets.zero,
// ),
// onChanged: (_) {
// setActivate();
// },
// keyboardType: TextInputType.number,
// ),
// ))
// ],
// )),
// Container(
// height: ScreenUtil().setHeight(84),
// width: ScreenUtil().setWidth(611),
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(20),
// right: ScreenUtil().setWidth(27)),
// margin: EdgeInsets.only(top: ScreenUtil().setHeight(8)),
// alignment: Alignment.center,
// decoration: BoxDecoration(
// border: Border.all(
// color: MyColours.color_bff6f7,
// width: ScreenUtil().setWidth(2)),
// borderRadius:
// BorderRadius.circular(ScreenUtil().setWidth(8))),
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// Image.asset(
// "assets/icon_pwd.png",
// width: prefixIconWidth,
// ),
// HEmptyView(26),
// Text(
// "密 码",
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28)),
// ),
// Expanded(
// child: Container(
// height: ScreenUtil().setHeight(84),
// padding: EdgeInsets.only(
// left: ScreenUtil().setWidth(20),
// right: ScreenUtil().setWidth(20),
// top: ScreenUtil().setWidth(8)),
// child: TextField(
// controller: _pwdController,
// focusNode: _pwdFocusNode,
// // autofocus: _sellerCodeController.text.isNotEmpty &&
// // _accountController.text.isNotEmpty &&
// // _pwdController.text.isEmpty,
// style: TextStyle(
// color: MyColours.color_353535,
// fontSize: ScreenUtil().setSp(28)),
// decoration: InputDecoration(
// hintText: "请输入密码",
// hintStyle: TextStyle(
// color: MyColours.color_c1c1c1,
// fontSize: ScreenUtil().setSp(28)),
// border: OutlineInputBorder(
// borderSide: BorderSide.none),
// contentPadding: EdgeInsets.zero,
// ),
// onChanged: (_) {
// setActivate();
// },
// obscureText: obscureText,
// keyboardType: TextInputType.text,
// )),
// ),
// GestureDetector(
// child: Padding(
// padding: EdgeInsets.all(ScreenUtil().setWidth(10)),
// child: Image.asset(
// obscureText
// ? "assets/icon_pwd_show.png"
// : "assets/icon_pwd_hide.png",
// width: ScreenUtil().setWidth(35),
// height: ScreenUtil().setHeight(24),
// ),
// ),
// onTap: () {
// setState(() {
// obscureText = !obscureText;
// });
// },
// )
// ],
// )),
// Container(
// width: ScreenUtil().setWidth(611),
// margin: EdgeInsets.only(top: ScreenUtil().setHeight(16)),
// alignment: Alignment.centerLeft,
// child: Text(
// errorText,
// // 'hfhfhf',
// style: TextStyle(
// color: MyColours.color_d93535,
// fontSize: ScreenUtil().setSp(16)),
// ),
// ),
VEmptyView(60),
// Consumer<UserModel>(
// builder:
// (BuildContext context, UserModel value, Widget child) {
// return GestureDetector(
// onTap: () {
// if (!isActivate) return;
// if (!_check) {
// Utils.showToast('请先仔细阅读并同意协议');
// return;
// }
// if (App.sp.containsKey('unionid')) {
// //微信登陆的时候,退出清空数据
// App.sp.remove('openid');
// App.sp.remove("unionid");
// }
// Map<String, String> params = {
// "seller_code": "${_sellerCodeController.text}",
// "alias": "ssahdahgdafgahjkhakjgh",
// "account": "${_accountController.text}",
// "password": "${_pwdController.text}"
// };
// value.login(context, params).then((value) {
// setState(() {
// errorText = value ?? "";
// });
// });
// },
// child: Container(
// height: ScreenUtil().setWidth(84),
// width: ScreenUtil().setWidth(611),
// alignment: Alignment.center,
// decoration: BoxDecoration(
// color: isActivate
// ? MyColours.color_2bb5b7
// : MyColours.color_2bb5b7,
// boxShadow: [
// BoxShadow(
// color: MyColours.color_26000000,
// offset: Offset(0, ScreenUtil().setWidth(4)),
// )
// ],
// borderRadius: BorderRadius.all(
// Radius.circular(ScreenUtil().setWidth(8)))),
// child: Text(
// "登录",
// style: TextStyle(
// color: MyColours.color_ffffff,
// fontSize: ScreenUtil().setSp(34)),
// ),
// ),
// );
// },
// ),
Row(
// mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
InkWell(
onTap: () {
setState(() {
_check = !_check;
});
},
child: Container(
alignment: Alignment.topCenter,
// color: Colors.red,
height: ScreenUtil().setWidth(110),
padding: EdgeInsets.only(
right: ScreenUtil().setWidth(30),
left: ScreenUtil().setWidth(102),
bottom: ScreenUtil().setWidth(10)),
child: Image.asset(
_check
? "assets/mine_radio_sel.png"
: "assets/mine_radio_nor.png",
width: ScreenUtil().setWidth(26),
height: ScreenUtil().setWidth(26)),
)),
// HEmptyView(30),
Padding(
InkWell(
onTap: () {
NavigatorUtil.goWebPage(context, '软件服务协议', "xy");
},
child: Padding(
padding:
EdgeInsets.only(top: ScreenUtil().setWidth(2)),
child: Text(
'已阅读并同意',
'《软件服务协议》',
style: TextStyle(
fontSize: ScreenUtil().setSp(24),
color: MyColours.color_999999),
),
),
InkWell(
onTap: () {
NavigatorUtil.goWebPage(context, '软件服务协议', "xy");
},
child: Padding(
padding: EdgeInsets.only(
top: ScreenUtil().setWidth(2)),
child: Text(
'《软件服务协议》',
style: TextStyle(
fontSize: ScreenUtil().setSp(24),
color: MyColours.color_333333),
)),
),
Padding(
padding:
EdgeInsets.only(top: ScreenUtil().setWidth(2)),
child: Text('及',
style: TextStyle(
fontSize: ScreenUtil().setSp(24),
color: MyColours.color_999999))),
InkWell(
onTap: () {
NavigatorUtil.goWebPage(context, '隐私政策', "zc");
},
child: Padding(
padding: EdgeInsets.only(
top: ScreenUtil().setWidth(2)),
child: Text('《隐私政策》',
style: TextStyle(
fontSize: ScreenUtil().setSp(24),
color: MyColours.color_333333))),
)
],
color: MyColours.color_333333),
)),
),
Padding(
padding: EdgeInsets.only(top: ScreenUtil().setWidth(2)),
child: Text('及',
style: TextStyle(
fontSize: ScreenUtil().setSp(24),
color: MyColours.color_999999))),
InkWell(
onTap: () {
NavigatorUtil.goWebPage(context, '隐私政策', "zc");
},
child: Padding(
padding:
EdgeInsets.only(top: ScreenUtil().setWidth(2)),
child: Text('《隐私政策》',
style: TextStyle(
fontSize: ScreenUtil().setSp(24),
color: MyColours.color_333333))),
)
],
),
//微信登录
Offstage(
offstage: !App.isInstallWx,
child: InkWell(
child: Container(
width: double.infinity,
height: ScreenUtil().setWidth(90),
margin: EdgeInsets.only(
bottom: ScreenUtil().setWidth(30),
right: ScreenUtil().setWidth(80),
left: ScreenUtil().setWidth(80)),
decoration: BoxDecoration(
color: MyColours.color_55AA67,
borderRadius: BorderRadius.circular(
ScreenUtil().setWidth(8))),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
Utils.getImgPath('icon_wechat_login'),
width: ScreenUtil().setWidth(46)),
HEmptyView(20),
Text('微信登录',
style: TextStyle(
color: MyColours.color_ffffff,
fontSize: ScreenUtil().setSp(28),
fontWeight: FontWeight.bold)),
],
)),
onTap: () {
if (!_check) {
DialogUtil.openAlertDialogLogin(context, 500,
onTap: () {
Navigator.pop(context);
_wxLogin();
});
return;
}
//微信登录
Offstage(
offstage: !App.isInstallWx,
child: InkWell(
child: Container(
width: double.infinity,
height: ScreenUtil().setWidth(90),
margin: EdgeInsets.only(
bottom: ScreenUtil().setWidth(30),
right: ScreenUtil().setWidth(80),
left: ScreenUtil().setWidth(80)),
decoration: BoxDecoration(
color: MyColours.color_55AA67,
borderRadius: BorderRadius.circular(
ScreenUtil().setWidth(8))),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(Utils.getImgPath('icon_wechat_login'),
width: ScreenUtil().setWidth(46)),
HEmptyView(20),
Text('微信登录',
style: TextStyle(
color: MyColours.color_ffffff,
fontSize: ScreenUtil().setSp(28),
fontWeight: FontWeight.bold)),
],
)),
onTap: () {
if (!_check) {
DialogUtil.openAlertDialogLogin(context, 500,
onTap: () {
Navigator.pop(context);
_wxLogin();
},
)),
//账号登录
Consumer<UserModel>(builder:
(BuildContext context, UserModel value, Widget child) {
return InkWell(
child: Container(
width: double.infinity,
height: ScreenUtil().setWidth(90),
margin: EdgeInsets.only(
bottom: ScreenUtil().setWidth(30),
right: ScreenUtil().setWidth(80),
left: ScreenUtil().setWidth(80)),
decoration: BoxDecoration(
color: MyColours.color_41444B,
borderRadius: BorderRadius.circular(
ScreenUtil().setWidth(8))),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('一键订账号登录',
style: TextStyle(
color: MyColours.color_ffffff,
fontSize: ScreenUtil().setSp(28),
fontWeight: FontWeight.bold)),
],
)),
onTap: () {
if (!_check) {
DialogUtil.openAlertDialogLogin(context, 500,
onTap: () {
Navigator.pop(context);
NavigatorUtil.goAccountLoginPage(context);
});
return;
}
});
return;
}
_wxLogin();
},
)),
//账号登录
Consumer<UserModel>(builder:
(BuildContext context, UserModel value, Widget child) {
return InkWell(
child: Container(
width: double.infinity,
height: ScreenUtil().setWidth(90),
margin: EdgeInsets.only(
bottom: ScreenUtil().setWidth(30),
right: ScreenUtil().setWidth(80),
left: ScreenUtil().setWidth(80)),
decoration: BoxDecoration(
color: MyColours.color_41444B,
borderRadius: BorderRadius.circular(
ScreenUtil().setWidth(8))),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('一键订账号登录',
style: TextStyle(
color: MyColours.color_ffffff,
fontSize: ScreenUtil().setSp(28),
fontWeight: FontWeight.bold)),
],
)),
onTap: () {
if (!_check) {
DialogUtil.openAlertDialogLogin(context, 500,
onTap: () {
Navigator.pop(context);
NavigatorUtil.goAccountLoginPage(context);
});
}),
// ios登录
Platform.isAndroid
? Text('')
: Consumer<UserModel>(builder: (BuildContext context,
UserModel value, Widget child) {
return InkWell(
child: Container(
width: double.infinity,
height: ScreenUtil().setWidth(90),
margin: EdgeInsets.only(
bottom: ScreenUtil().setWidth(30),
right: ScreenUtil().setWidth(80),
left: ScreenUtil().setWidth(80)),
decoration: BoxDecoration(
color: MyColours.color_ffffff,
border: Border.all(
color: MyColours.color_E4E4E4,
width: ScreenUtil().setWidth(2)),
borderRadius: BorderRadius.circular(
ScreenUtil().setWidth(8))),
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Image.asset(
Utils.getImgPath(
'icon_apple_login'),
width: ScreenUtil().setWidth(46)),
HEmptyView(20),
Text('通过 Apple 登录',
style: TextStyle(
color: MyColours.color_41444B,
fontSize:
ScreenUtil().setSp(28),
fontWeight: FontWeight.bold)),
],
)),
onTap: () {
if (!_check) {
DialogUtil.openAlertDialogLogin(
context, 500, onTap: () {
Navigator.pop(context);
_iosLogin(value);
});
return;
}
return;
}
NavigatorUtil.goAccountLoginPage(context);
});
}),
// ios登录
Platform.isAndroid
? Text('')
: Consumer<UserModel>(builder:
(BuildContext context, UserModel value, Widget child) {
return InkWell(
child: Container(
width: double.infinity,
height: ScreenUtil().setWidth(90),
margin: EdgeInsets.only(
bottom: ScreenUtil().setWidth(30),
right: ScreenUtil().setWidth(80),
left: ScreenUtil().setWidth(80)),
decoration: BoxDecoration(
color: MyColours.color_ffffff,
border: Border.all(
color: MyColours.color_E4E4E4,
width: ScreenUtil().setWidth(2)),
borderRadius: BorderRadius.circular(
ScreenUtil().setWidth(8))),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
Utils.getImgPath('icon_apple_login'),
width: ScreenUtil().setWidth(46)),
HEmptyView(20),
Text('通过 Apple 登录',
style: TextStyle(
color: MyColours.color_41444B,
fontSize: ScreenUtil().setSp(28),
fontWeight: FontWeight.bold)),
],
)),
onTap: () {
if (!_check) {
DialogUtil.openAlertDialogLogin(context, 500,
onTap: () {
Navigator.pop(context);
_iosLogin(value);
});
}),
InkWell(
onTap: () {
Utils.goPhone("400-000-9690");
},
child: Text('联系客服',
style: TextStyle(
color: MyColours.color_333333,
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(26),
decoration: TextDecoration.underline))),
],
),
),
));
return;
}
_iosLogin(value);
});
}),
InkWell(
onTap: () {
Utils.goPhone("400-000-9690");
},
child: Text('联系客服',
style: TextStyle(
color: MyColours.color_333333,
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(26),
decoration: TextDecoration.underline))),
],
),
),
));
}
}
... ...
import 'dart:async';
import 'dart:convert';
import 'dart:io';
// class WebPage extends StatefulWidget {
// final String title;
// final String url;
// final bool need;
// const WebPage({Key key, this.title, this.url, this.need = false})
// : super(key: key);
// @override
// _WebPageState createState() => _WebPageState();
// }
// class _WebPageState extends State<WebPage> {
// final Completer<WebViewController> _controller =
// Completer<WebViewController>();
// @override
// void initState() {
// super.initState();
// // Enable hybrid composition.
// if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
// }
// @override
// Widget build(BuildContext context) {
// return Text('''引言
// 欢迎您使用由纳极(北京)科技有限公司(本文简称纳极)提供的“一键订”!我们非常重视用户的隐私和个人信息保护。您在使用我们的产品与/或服务时,我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明我们在您使用我们的产品与/或服务时如何收集、使用、保存、共享和转让这些信息,以及我们为您提供的访问、更新、控制和保护这些信息的方式。
// 本隐私政策将帮助您了解以下内容:
// 一、我们如何收集和使用您的个人信息
// 二、我们如何共享、转让、公开披露您的个人信息
// 三、我们如何保护和保存您的个人信息
// 四、您如何管理个人信息
// 五、未成年人的个人信息保护
// 六、通知和修订
// 七、如何联系我们
// 本隐私政策与您所使用的“一键订”服务以及该服务所包括的各种业务功能(以下统称“我们的产品与/或服务”)息息相关,希望您在使用我们的产品与/或服务前仔细阅读并确认您已经充分理解本隐私政策所写明的内容,并让您可以按照本隐私政策的指引做出您认为适当的选择。本隐私政策中涉及的相关术语,我们尽量以简明扼要的表述,以便您更好地理解。
// 一、我们如何收集和使用您的个人信息
// 个人信息是指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。个人敏感信息是指一旦泄露、非法提供或滥用可能危害人身和财产安全,极易导致个人名誉、身心健康受到损害或歧视性待遇等的个人信息(我们将在本政策中对涉及到的个人敏感信息以粗体进行显著标识)。
// 我们会遵循正当、合法、必要的原则,出于本政策所述的以下目的,收集和使用您在使用产品与/或服务过程中主动提供或因使用产品与/或服务而产生的个人信息,以及从第三方获取您的相关个人信息。如果我们要将您的个人信息用于本政策未载明的其它用途,或将基于特定目的收集而来的信息用于其他目的,我们将以合理的方式向您告知,并在使用前再次征得您的同意。
// (一)您须授权我们收集和使用您个人信息的情形
// 我们的产品与/或服务包括一些核心功能,这些功能包含了实现预订管理所必须的功能。我们可能会收集、保存和使用下列与您有关的信息才能实现上述这些功能。如果您不提供相关信息,您将无法享受我们提供的产品与/或服务。这些功能包括:
// (1)账户注册、登录与验证
// 当您注册“一键订”账户时,您至少需要向我们提供您准备使用的您本人的手机号码,我们将通过发送短信验证码的方式来验证您的身份是否有效。您可以修改和补充您的姓名、照片、联系电话,这些信息均属于您的“账户信息”。您补充的账户信息将有助于我们为您的客户提供更优的预订体验。
// (2)店铺服务信息设置
// 为了让您的客户快速地找到您的店铺,我们会收集您店铺的名称、所在位置相关信息(您授权的GPS位置信息,我们仅收集您当时所处的地理位置,但不会将您各时段的位置信息进行结合以判断您的行踪轨迹)来判断您所处的地点。
// (3)预订信息录入
// 为了方便您快速录入客户的姓名、联系电话,在您同意开启通讯录权限后您在录入预订订单时不需再手动输入您通讯录中联系人的信息,用于订单快速生成。
// (4)预订通知管理
// 当您准备通知您的客户预订结果时,“一键订”需要调用您的微信给客户发送预订安排。您需要在通知内至少填写您的联系电话;
// 您还可以填写您的姓名、职务和微信二维码以方便客户更好的跟您建立联系,但是不填写这些信息不影响您的预订通知。
// 当您设置您的通知信息时,“一键订”需要调用基于图片/视频上传的扩展功能:我们会使用您所上传的图片,作为您的形象照片和微信二维码展示,方便客户添加您的微信。
// (5)订单结账通知功能
// 当您传送订单水单给客户时,“一键订”需要调用基于摄像头(相机)的扩展功能:您可以使用这项扩展功能完成拍照上传水单传送给客户。
// (6)基于剪切板的扩展功能:当您复制个人信息、短信验证码,分享或接收被分享信息时,您的剪切板功能可能会被调用。剪切板信息仅会在您的设备上进行处理,我们不会存储您的剪切板信息用于任何其他途径。
// 上述功能可能需要您在您的设备中向我们开启您的地理位置(位置信息)、相机(摄像头)、相册(图片库)、通讯录,以实现这些功能所涉及的信息的收集和使用。您可以在“一键订”APP或设备系统中逐项查看您上述权限的开启状态,并可以决定将这些权限随时的开启或关闭。请您注意,您开启这些权限即代表您授权我们可以收集和使用这些个人信息来实现上述的功能,您关闭权限即代表您取消了这些授权,则我们将不再继续收集和使用您的这些个人信息,也无法为您提供上述与这些授权所对应的功能。您关闭权限的决定不会影响此前基于您的授权所进行的个人信息的处理。除此以外,我们在相关业务功能中可能还需要您开启设备的其他访问权限,详细权限和使用目的如下:
// (1)android.permission.ACCESS_COARSE_LOCATION(访问位置权限):用于应用创建店铺供用户选择地图位置、定位
// (2)android.permission.ACCESS_FINE_LOCATION(访问位置权限):用于应用创建店铺供用户选择地图位置、定位
// (3)android.permission.READ_CONTACTS/android.permission.WRITE_CONTACTS(访问联系人权限):获取用户通讯录以便快速录入用户联系电话,方便发送短信
// (4)com.huawei.android.launcher.permission.CHANGE_BADGE(设置桌面图标角标权限):应于消息推送设置桌面角标,推送客户预订、提前点餐、买单需求、取消预订等需要推送通知给商家的消息数
// (二)您充分知晓,以下情形中,我们收集、使用个人信息无需征得您的授权同意: 
// 1、与国家安全、国防安全有关的;
// 2、与公共安全、公共卫生、重大公共利益有关的;
// 3、与犯罪侦查、起诉、审判和判决执行等有关的;
// 4、出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
// 5、所收集的个人信息是个人信息主体或监护人自行向社会公众公开的;
// 6、从合法公开披露的信息中收集的您的个人信息的,如合法的新闻报道、政府信息公开等渠道;
// 7、根据您的要求签订合同所必需的;
// 8、用于维护所提供的产品与/或服务的安全稳定运行所必需的,例如发现、处置产品与/或服务的故障;
// 9、为合法的新闻报道所必需的;
// 10、学术研究机构基于公共利益开展统计或学术研究所必要,且对外提供学术研究或描述的结果时,对结果中所包含的个人信息进行去标识化处理的;
// 11、法律法规规定的其他情形。
// (三)我们从第三方获得您个人信息的情形
// 我们可能从第三方获取您授权共享的账户信息(头像、昵称及授权页面提示的其他信息),并在您同意本隐私政策后将您的第三方账户与您的纳极账户绑定,使您可以通过第三方账户直接登录并使用我们的产品与/或服务。我们会将依据与第三方的约定、对个人信息来源的合法性进行确认后,在符合相关法律和法规规定的前提下,使用您的这些个人信息。您也可以使用本机号码来快捷登录,我们会将您的网络信息提供给运营商完成注册或登录。
// (四)“一键订”APP涉及嵌入第三方代码、插件传输个人信息的情形逐项列举:
// 高德地图sdk:用于应用创建店铺供用户选择地图位置、定位
// 获取通讯录:获取用户通讯录以便快速录入用户联系电话,方便发送短信
// 华为推送sdk:应于消息推送,推送客户预订、提前点餐、买单需求、取消预订等需要推送通知给商家的消息
// 腾讯Bugly:用于收集应用使用中报出的错误信息、日志
// 微信sdk:用于商家经理微信登录“一键订”app 以及微信分享预订通知、点餐清单等操作
// 七牛云sdk:用于上传、获取商家图片、视频等资源
// (五)您个人信息使用的规则
// 1、我们会根据本隐私政策的约定并为实现我们的产品与/或服务功能对所收集的个人信息进行使用。
// 2、在收集您的个人信息后,我们将通过技术手段对数据进行去标识化处理,去标识化处理的信息将无法识别主体。请您了解并同意,在此情况下我们有权使用已经去标识化的信息;并在不透露您个人信息的前提下,我们有权对用户数据库进行分析并予以商业化的利用。
// 3、请您了解并同意,您在使用我们的产品与/或服务时所提供的所有个人信息,除非您删除或通过系统设置拒绝我们收集,否则将在您使用我们的产品与/或服务期间持续授权我们使用。
// 4、我们会对我们的产品与/或服务使用情况进行统计,并可能会与公众或第三方共享这些统计信息,以展示我们的产品与/或服务的整体使用趋势。但这些统计信息不包含您的任何身份识别信息。
// 5、当我们展示您的个人信息时,我们会采用包括内容替换、匿名处理方式对您的信息进行脱敏,以保护您的信息安全。
// 6、当我们要将您的个人信息用于本隐私政策未载明的其它用途时,或基于特定目的收集而来的信息用于其他目的时,会实现征求您的明示同意。请您理解,我们向您提供的产品与/或服务将不断更新变化。如果您选择使用本隐私政策中尚未列明的其他产品与/或服务时,我们会在收集您的个人信息前通过协议、页面提示等方式向您详细说明信息收集的目的、方式、范围并征求您的同意。若您不同意提供前述信息,您可能无法使用该项产品与/或服务,但不影响您使用现有产品与/或服务。
// 二、我们如何共享、转让、公开披露您的个人信息
// (一)共享
// 1、我们不会与纳极以外的任何公司、组织和个人共享您的个人信息,但以下情况除外:
// (1)事先获得您明确的同意或授权;
// (2)根据适用的法律法规、法律程序的要求、强制性的行政或司法要求所必须的情况下进行提供;
// (3)在法律法规允许的范围内,为维护纳极、纳极的关联方或合作伙伴、您或其他纳极用户或社会公众利益、财产或安全免遭损害而有必要提供;
// (4) 只有共享您的个人信息,才能实现我们的产品与/或服务的核心功能或提供您需要的服务;
// (5)应您需求为您处理您与他人的纠纷或争议;
// (6)符合与您签署的相关协议(包括在线签署的电子协议以及相应的平台规则)或其他的法律文件约定所提供;
// (7)基于学术研究而使用;
// (8)基于符合法律法规的社会公共利益而使用。
// 2、对我们与之共享个人信息的公司、组织和个人,我们会与其签署严格的保密协定,要求他们按照我们的说明、本隐私政策以及其他任何相关的保密和安全措施来处理个人信息。为了保障数据安全可控,我们在云端提供安全可靠的数据使用和存储环境,确保用户数据的安全性。
// 3、为了遵守法律、执行或适用我们的使用条件和其他协议,或者为了保护纳极、您或其他纳极客户的权利及其财产或安全,比如为防止欺诈等违法活动和减少信用风险,而与其他公司和组织交换信息。不过,这并不包括违反本隐私政策中所作的承诺而为获利目的出售、出租、共享或以其它方式披露的个人信息。
// (二)转让
// 我们不会将您的个人信息转让给任何公司、组织和个人,但以下情况除外:
// 1、事先获得您明确的同意或授权;
// 2、根据适用的法律法规、法律程序的要求、强制性的行政或司法要求所必须的情况进行提供;
// 3、符合与您签署的相关协议(包括在线签署的电子协议以及相应的平台规则)或其他的法律文件约定所提供;
// 4、在涉及合并、收购、资产转让或类似的交易时,如涉及到个人信息转让,我们会要求新的持有您个人信息的公司、组织继续受本隐私政策的约束,否则,我们将要求该公司、组织重新向您征求授权同意。
// (三)公开披露
// 我们仅会在以下情况下,且采取符合业界标准的安全防护措施的前提下,才会公开披露您的个人信息:
// 1、根据您的需求,在您明确同意的披露方式下披露您所指定的个人信息;
// 2、根据法律、法规的要求、强制性的行政执法或司法要求所必须提供您个人信息的情况下,我们可能会依据所要求的个人信息类型和披露方式公开披露您的个人信息。在符合法律法规的前提下,当我们收到上述披露信息的请求时,我们会要求必须出具与之相应的法律文件,如传票或调查函。我们坚信,对于要求我们提供的信息,应该在法律允许的范围内尽可能保持透明。我们对所有的请求都进行了慎重的审查,以确保其具备合法依据,且仅限于执法部门因特定调查目的且有合法权利获取的数据。在法律法规许可的前提下,我们披露的文件均在加密密钥的保护之下。
// 三、我们如何保护和保存您的个人信息
// (一)我们保护您个人信息的技术与措施
// 我们非常重视个人信息安全,并采取一切合理可行的措施,保护您的个人信息:
// 1、数据安全技术措施
// 我们会采用符合业界标准的安全防护措施,包括建立合理的制度规范、安全技术来防止您的个人信息遭到未经授权的访问使用、修改,避免数据的损坏或丢失。
// 纳极的网络服务采取了传输层安全协议等加密技术,通过https等方式提供浏览服务,确保用户数据在传输过程中的安全。
// 纳极采取加密技术对用户个人信息进行加密保存,并通过隔离技术进行隔离。
// 在个人信息使用时,例如个人信息展示、个人信息关联计算,我们会采用包括内容替换、SHA256在内多种数据脱敏技术增强个人信息在使用中安全性。
// 纳极采用严格的数据访问权限控制和多重身份认证技术保护个人信息,避免数据被违规使用。
// 纳极采用代码安全自动检查、数据访问日志分析技术进行个人信息安全审计。
// 2、纳极为保护个人信息采取的其他安全措施
// 纳极通过建立数据分类分级制度、数据安全管理规范、数据安全开发规范来管理规范个人信息的存储和使用。
// 纳极通过信息接触者保密协议、监控和审计机制来对数据进行全面安全控制。
// 加强安全意识。我们还会举办安全和隐私保护培训课程,加强员工对于保护个人信息重要性的认识。
// 3、我们仅允许有必要知晓这些信息的纳极及纳极关联方的员工、合作伙伴访问个人信息,并为此设置了严格的访问权限控制和监控机制。我们同时要求可能接触到您个人信息的所有人员履行相应的保密义务。如果未能履行这些义务,可能会被追究法律责任或被中止与纳极的合作关系。
// 4、我们会采取一切合理可行的措施,确保未收集无关的个人信息。
// 5、如果您对我们的个人信息保护有任何疑问,可通过本隐私政策中约定的联系方式联系我们。如您发现自己的个人信息泄密,尤其是您的账户及密码发生泄露,请您立即通过本隐私政策【七、如何联系我们】中约定的联系方式联络我们,以便我们采取相应措施。
// (二)您个人信息的保存
// 1、您的个人信息将存储于中华人民共和国境内。
// 2、在您使用我们的产品与/或服务期间,您的个人信息将在为了实现本政策所述目的之期限内保存,同时将结合法律有强制的留存要求期限的规定确定,如《中华人民共和国电子商务法》要求商品和服务信息、交易信息保存时间自交易完成之日起不少于三年。在超出保存期间后,我们会根据适用法律的要求删除您的个人信息,或进行匿名化处理。
// 3、请您注意,当您或我们协助您删除相关信息后,因为适用的法律和安全技术限制,我们可能无法立即从备份系统中删除相应的信息,我们将安全地存储您的个人信息并限制对其的任何进一步的处理,直到备份可以删除或实现匿名化。
// 4、如果我们终止服务或运营,我们会至少提前三十日向您通知,并在终止服务或运营后对您的个人信息进行删除或匿名化处理。
// 四、您如何管理您的个人信息
// 纳极非常重视您对个人信息的关注,并尽全力保护您对于您个人信息访问、更正、删除以及撤回同意的权利,以使您拥有充分的能力保障您的隐私和安全。您的权利包括:
// 1、访问和更正您的个人信息
// 除法律法规规定外,您有权随时访问和更正您的个人信息,具体包括:
// (1)您的账户信息:您可以在“一键订”APP “我的”页面的“编辑”菜单中查阅您提交给纳极的所有个人信息,你也可通过上述途径更新除个人信息。
// (2)您的店铺配置信息:您可以在“一键订”APP “我的”页面进行相关预订操作的设置。
// 2、删除您的个人信息
// 您在我们的产品与/或服务页面中可以直接清除或删除的信息,包括姓名、联系电话、微信二维码信息。
// 在以下情形中,您可以向我们提出删除个人信息的请求:
// (1)如果我们处理个人信息的行为违反法律法规;
// (2)如果我们收集、使用您的个人信息,却未征得您的同意;
// (3)如果我们处理个人信息的行为违反了与您的约定;
// (4)如果您不再使用我们的产品或服务;
// (5)如果我们终止服务及运营。若我们决定响应您的删除请求,我们还将同时通知从我们获得您的个人信息的实体,要求其及时删除,除非法律法规另有规定,或这些实体获得您的独立授权。若我们响应您的删除请求,我们不能保证立即从备份系统中删除相应的信息,但会在备份系统更新时删除这些信息。
// 3、改变您授权同意的范围或撤回您的授权
// 您可以通过删除信息、关闭设备功能、在“一键订”软件中进行隐私设置等方式改变您授权我们继续收集个人信息的范围或撤回您的授权。
// 请您理解,每个业务功能需要一些基本的个人信息才能得以完成,当您撤回同意或授权后,我们无法继续为您提供撤回同意或授权所对应的服务,也不再处理您相应的个人信息。但您撤回同意或授权的决定,不会影响此前基于您的授权而开展的个人信息处理。
// 4、获取您的个人信息副本
// 您有权获取您的个人信息副本。如您需要获取我们收集的您的个人信息副本,您随时联系我们。在符合相关法律规定且技术可行的前提下,我们将根据您的要求向您提供您的个人信息副本。
// 6、响应您的请求
// 如果您无法通过上述方式访问、更正或删除您的个人信息,或您需要访问、更正或删除您在使用我们产品与/或服务时所产生的其他个人信息或者获取个人信息副本,或您认为“一键订”存在任何违反法律法规或与您关于个人信息的收集或使用的约定,您均可以通过本隐私政策中的指定方式与我们联系。为了保障安全,我们可能需要您提供书面请求,或提供您的身份证明文件,我们将在收到您反馈并验证您的身份后的15天内答复您的请求。对于您合理的请求,我们原则上不收取费用,但对多次重复、超出合理限度的请求,我们将视情收取一定成本费用。对于那些无端重复、需要过多技术手段(例如,需要开发新系统或从根本上改变现行惯例)、给他人合法权益带来风险或者非常不切实际(例如,涉及备份磁带上存放的信息)的请求,我们可能会予以拒绝。
// 在以下情形中,按照法律法规要求,我们将无法响应您的请求:
// (1)与个人信息控制者履行法律法规规定的义务相关的;
// (2)与国家安全、国防安全有关的;
// (3)与公共安全、公共卫生、重大公共利益有关的;
// (4)与犯罪侦查、起诉和审判和执行判决等有关的;
// (5)有充分证据表明您存在主观恶意或滥用权利的;
// (6)出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的;
// (7)响应您的请求将导致您或其他个人、组织的合法权益受到严重损害的;
// (8)涉及商业秘密的。
// 五、未成年人的个人信息保护
// 1、“一键订”非常重视对未成年人个人信息的保护。若您是18周岁以下的未成年人,在使用我们的产品与/或服务前,应事先取得您监护人的同意。“一键订”根据国家相关法律法规的规定保护未成年人的个人信息。
// 2、我们不会主动直接向未成年人收集其个人信息。对于经监护人同意而收集未成年人个人信息的情况,我们只会在受到法律允许、监护人同意或者保护未成年人所必要的情况下使用、共享、转让或披露此信息。
// 3、如果有事实证明未成年人在未取得监护人同意的情况下注册使用了我们的产品与/或服务,我们会与相关监护人协商,并设法尽快删除相关个人信息。
// 4、对于可能涉及的不满14周岁的儿童个人信息,我们进一步采取以下措施予以保障:
// (1)对于收集到的儿童个人信息,我们除遵守本隐私政策关于用户个人信息的约定外,还会秉持正当必要、知情同意、目的明确、安全保障、依法利用的原则,严格遵循《儿童个人信息网络保护规定》等法律法规的要求进行存储、使用、披露,且不会超过实现收集、使用目的所必须的期限,到期后我们会对儿童个人信息进行删除或匿名化处理。
// (2)当您作为监护人为被监护的儿童选择使用“一键订”相关服务时,我们可能需要向您收集被监护的儿童个人信息,用于向您履行相关服务之必要。在具体服务中需要向您收集儿童个人信息的,我们会事先取得您的授权同意,并告知您收集的目的和用途。如果您不提供前述信息,您将无法享受我们提供的相关服务。您作为监护人应当正确履行监护职责,保护儿童个人信息安全。若儿童本人需要注册或使用我们的产品与/或服务,您应正确引导并予以监护。
// (3)儿童或其监护人有权随时访问和更正儿童个人信息,还可以向我们提出更正和删除的请求。如您对儿童个人信息相关事宜有任何意见、建议或投诉、举报,请联系我们。我们会随时为您提供帮助。
// 六、通知和修订
// 1、为给您提供更好的服务以及随着纳极业务的发展,本隐私政策也会随之更新。但未经您明确同意,我们不会削减您依据本隐私政策所应享有的权利。我们会通过在纳极网站、一键订移动端上发出更新版本并在生效前通过网站公告或以其他适当方式提醒您相关内容的更新,也请您访问纳极以便及时了解最新的隐私政策。
// 2、对于重大变更,我们还会提供更为显著的通知(我们会通过包括但不限于邮件、短信或在浏览页面做特别提示等方式,说明隐私政策的具体变更内容)。
// 本隐私政策所指的重大变更包括但不限于:
// (1)我们的服务模式发生重大变化。如处理个人信息的目的、处理的个人信息类型、个人信息的使用方式等;
// (2)我们在所有权结构、组织架构等方面发生重大变化。如业务调整、破产并购等引起的所有者变更等;
// (3)个人信息共享、转让或公开披露的主要对象发生变化;
// (4)您参与个人信息处理方面的权利及其行使方式发生重大变化;
// (5)我们负责处理个人信息安全的责任部门、联络方式及投诉渠道发生变化时;
// (6)个人信息安全影响评估报告表明存在高风险时。
// 3、为了您能及时接收到通知,建议您在联系方式更新时及时通知我们。如您在本政策更新生效后继续使用我们的服务,即代表您已充分阅读、理解并接受更新后的政策并愿意受更新后的政策约束。我们鼓励您在每次使用我们服务时都查阅本政策。
// 七、如何联系我们
// 1、如您对本隐私政策或您个人信息的相关事宜或者投诉、举报、有任何问题、意见或建议,请通过以下方式与我们联系。
// 联系电话:400-000-9690
// 2、一般情况下,我们将在15天内回复。如果您对我们的回复不满意,特别是我们的个人信息处理行为损害了您的合法权益,您还可以向网信、电信、公安及工商等监管部门进行投诉或举报。
// 注:本隐私政策版本更新日期为2021年【8】月【6】日,将于2021年【8】月【7】日正式生效。
// ''');
// return Scaffold(
// // We're using a Builder here so we have a context that is below the Scaffold
// // to allow calling Scaffold.of(context) so we can show a snackbar.
// appBar: SystemAppBar(
// widget.title,
// onTap: () {
// if (widget.need) {
// Utils.changeStatusColor(color: MyColours.color_199095);
// }
// Navigator.of(context).pop();
// },
// ),
// body: WillPopScope(
// onWillPop: () async {
// if (widget.need) {
// Utils.changeStatusColor(color: MyColours.color_199095);
// }
// return true;
// },
// child: Column(children: [
// Builder(builder: (BuildContext context) {
// return Expanded(
// child: WebView(
// initialUrl: "http://colorcun.com/privacy1.html",
// javascriptMode: JavascriptMode.unrestricted,
// onWebViewCreated: (WebViewController webViewController) {
// _controller.complete(webViewController);
// },
// // TODO(iskakaushik): Remove this when collection literals makes it to stable.
// // ignore: prefer_collection_literals
// javascriptChannels: <JavascriptChannel>[
// _toasterJavascriptChannel(context),
// ].toSet(),
// navigationDelegate: (NavigationRequest request) {
// if (request.url.startsWith('https://www.youtube.com/')) {
// print('blocking navigation to $request}');
// return NavigationDecision.prevent;
// }
// print('allowing navigation to $request');
// return NavigationDecision.navigate;
// },
// onPageStarted: (String url) {
// print('Page started loading: $url');
// },
// onPageFinished: (String url) {
// print('Page finished loading: $url');
// },
// gestureNavigationEnabled: true,
// ));
// }),
// ])));
// }
// JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
// return JavascriptChannel(
// name: 'Toaster',
// onMessageReceived: (JavascriptMessage message) {
// Scaffold.of(context).showSnackBar(
// SnackBar(content: Text(message.message)),
// );
// });
// }
// }
import 'package:flutter/material.dart';
import 'package:one_key/res/colors.dart';
import 'package:one_key/utils/event_bus.dart';
import 'package:one_key/utils/utils.dart';
import 'package:one_key/widgets/app_bar.dart';
import 'package:one_key/widgets/system_app_bar.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebPage extends StatefulWidget {
final String title;
... ... @@ -22,14 +262,104 @@ class WebPage extends StatefulWidget {
}
class _WebPageState extends State<WebPage> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
final GlobalKey webViewKey = GlobalKey();
InAppWebViewController webViewController;
InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
useOnDownloadStart: true,
mediaPlaybackRequiresUserGesture: false,
),
android: AndroidInAppWebViewOptions(
useHybridComposition: true,
),
ios: IOSInAppWebViewOptions(
allowsInlineMediaPlayback: true,
));
PullToRefreshController pullToRefreshController;
ContextMenu contextMenu;
String url = "";
double progress = 0;
final urlController = TextEditingController();
// Future<void> openFile(filePath) async {
// final _result = await OpenFile.open(filePath);
// print(_result.message);
// // setState(() {
// // _openResult = "type=${_result.type} message=${_result.message}";
// // });
// }
@override
void initState() {
super.initState();
// Enable hybrid composition.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
_init();
}
// Future<bool> _checkPermission() async {
// DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
// AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
// if (Platform.isAndroid && androidInfo.version.sdkInt <= 28) {
// final status = await Permission.storage.status;
// if (status != PermissionStatus.granted) {
// final result = await Permission.storage.request();
// if (result == PermissionStatus.granted) {
// return true;
// }
// } else {
// return true;
// }
// } else {
// return true;
// }
// return false;
// }
// Future<bool> checkPermissionStorage() async {
// var status = await Permission.storage.status;
// if (!status.isGranted) {
// status = await Permission.storage.request();
// if (status.isGranted) {
// return true;
// }
// } else {
// return true;
// }
// return false;
// }
_init() async {
// WidgetsFlutterBinding.ensureInitialized();
// await FlutterDownloader.initialize(debug: true);
if (Platform.isAndroid) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
var swAvailable = await AndroidWebViewFeature.isFeatureSupported(
AndroidWebViewFeature.SERVICE_WORKER_BASIC_USAGE);
var swInterceptAvailable = await AndroidWebViewFeature.isFeatureSupported(
AndroidWebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST);
if (swAvailable && swInterceptAvailable) {
AndroidServiceWorkerController serviceWorkerController =
AndroidServiceWorkerController.instance();
serviceWorkerController.serviceWorkerClient =
AndroidServiceWorkerClient(
shouldInterceptRequest: (request) async {
print(request);
return null;
},
);
}
}
// await FlutterDownloader.initialize(
// debug: true // optional: set false to disable printing logs to console
// );
// await Permission.storage.request();
}
@override
... ... @@ -53,52 +383,201 @@ class _WebPageState extends State<WebPage> {
}
return true;
},
child: Column(children: [
Builder(builder: (BuildContext context) {
return Expanded(
child: WebView(
initialUrl: "http://colorcun.com/privacy1.html",
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
child: Column(
children: [
Expanded(
child: InAppWebView(
key: webViewKey,
// contextMenu: contextMenu,
initialUrlRequest: URLRequest(
url: Uri.parse("http://colorcun.com/privacy1.html")),
// initialFile: "assets/index.html",
initialUserScripts: UnmodifiableListView<UserScript>([]),
initialOptions: options,
pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) {
webViewController = controller;
},
// TODO(iskakaushik): Remove this when collection literals makes it to stable.
// ignore: prefer_collection_literals
javascriptChannels: <JavascriptChannel>[
_toasterJavascriptChannel(context),
].toSet(),
navigationDelegate: (NavigationRequest request) {
if (request.url.startsWith('https://www.youtube.com/')) {
print('blocking navigation to $request}');
return NavigationDecision.prevent;
onLoadStart: (controller, url) {
// setState(() {
// this.url = url.toString();
// urlController.text = this.url;
// });
},
androidOnPermissionRequest:
(controller, origin, resources) async {
// return PermissionRequestResponse(
// resources: resources,
// action: PermissionRequestResponseAction.GRANT);
},
shouldOverrideUrlLoading:
(controller, navigationAction) async {
var uri = navigationAction.request.url;
if (![
"http",
"https",
"file",
"chrome",
"data",
"javascript",
"about"
].contains(uri.scheme)) {
if (await canLaunch(url)) {
// Launch the App
await launch(
url,
);
// and cancel the request
return NavigationActionPolicy.CANCEL;
}
}
print('allowing navigation to $request');
return NavigationDecision.navigate;
return NavigationActionPolicy.ALLOW;
},
onPageStarted: (String url) {
print('Page started loading: $url');
onLoadStop: (controller, url) async {
// pullToRefreshController.endRefreshing();
// setState(() {
// this.url = url.toString();
// urlController.text = this.url;
// });
},
onPageFinished: (String url) {
print('Page finished loading: $url');
onLoadError: (controller, url, code, message) {
pullToRefreshController.endRefreshing();
},
gestureNavigationEnabled: true,
));
}),
])));
}
onProgressChanged: (controller, progress) {
// if (progress == 100) {
pullToRefreshController.endRefreshing();
// }
// setState(() {
// this.progress = progress / 100;
// urlController.text = this.url;
// });
},
onUpdateVisitedHistory: (controller, url, androidIsReload) {
// setState(() {
// this.url = url.toString();
// urlController.text = this.url;
// });
},
onConsoleMessage: (controller, consoleMessage) {
print(consoleMessage);
},
onDownloadStart: (controller, url) async {
// print("onDownloadStart $url");
// Utils.showToast('开始下载...');
// // final taskId = await FlutterDownloader.enqueue(
// // url: url.toString(),
// // savedDir: (await getExternalStorageDirectory()).path,
// // showNotification:
// // true, // show download progress in status bar (for Android)
// // openFileFromNotification:
// // true, // click on notification to open downloaded file (for Android)
// // );
// final dir = await getExternalStorageDirectory();
// String savePath = dir.path + "/one_key";
JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
return JavascriptChannel(
name: 'Toaster',
onMessageReceived: (JavascriptMessage message) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(message.message)),
);
});
// Directory directory = new Directory(savePath);
// if (directory.existsSync()) {
// //删除历史apk
// final List<FileSystemEntity> files = directory.listSync();
// for (FileSystemEntity file in files) {
// file.deleteSync();
// }
// }
// DioUtil().download(url.toString(), savePath,
// onProgress: (received, total) {
// // if (_progress != 100) {
// // int progress = ((received / total) * 100).round();
// // _progress = progress;
// // _setState(() {});
// // }
// if (received == total) {
// print('object');
// Utils.showToast('文件已保存在${savePath}');
// // openFile(savePath);
// //下载完成
// // InstallPlugin.installApk(savePath, "com.naji.one_key");
// }
// });
// // },
// // DioUtil().download(url.toString(),
// // (await getExternalStorageDirectory()).path,
// // onProgress: (received, total) {
// // // if (_progress != 100) {
// // // int progress = ((received / total) * 100).round();
// // // _progress = progress;
// // // _setState(() {});
// // // }
// // if (received == total) {
// // //下载完成
// // // InstallPlugin.installApk(savePath, "com.naji.one_key");
// // }
// // });
},
))
],
)
// Builder(builder: (BuildContext context) {
// return WebView(
// initialUrl: "http://colorcun.com/privacy1.html",
// javascriptMode: JavascriptMode.unrestricted,
// onWebViewCreated: (WebViewController webViewController) {
// _controller.complete(webViewController);
// },
// // TODO(iskakaushik): Remove this when collection literals makes it to stable.
// // ignore: prefer_collection_literals
// javascriptChannels: <JavascriptChannel>[
// _toasterJavascriptChannel(context),
// ].toSet(),
// navigationDelegate: (NavigationRequest request) {
// if (request.url.startsWith('https://www.youtube.com/')) {
// print('blocking navigation to $request}');
// return NavigationDecision.prevent;
// }
// print('allowing navigation to $request');
// return NavigationDecision.navigate;
// },
// onPageStarted: (String url) {
// print('Page started loading: $url');
// },
// onPageFinished: (String url) async {
// print('Page finished loading: $url');
// // showDownloadProgress(context);
// final dir = await getExternalStorageDirectory();
// String savePath = dir.path + "/one_key";
// DioUtil().download(url, savePath,
// onProgress: (received, total) {
// // if (_progress != 100) {
// // int progress = ((received / total) * 100).round();
// // _progress = progress;
// // _setState(() {});
// // }
// if (received == total) {
// print('object');
// //下载完成
// // InstallPlugin.installApk(savePath, "com.naji.one_key");
// }
// });
// },
// gestureNavigationEnabled: true,
// );
// }),
));
}
// JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
// return JavascriptChannel(
// name: 'Toaster',
// onMessageReceived: (JavascriptMessage message) {
// Scaffold.of(context).showSnackBar(
// SnackBar(content: Text(message.message)),
// );
// });
// }
}
// class WebPage extends StatefulWidget {
// class WebPage extends StatelessWidget {
// final String title;
// final String url;
// final bool need;
... ... @@ -106,382 +585,55 @@ class _WebPageState extends State<WebPage> {
// const WebPage({Key key, this.title, this.url, this.need = false})
// : super(key: key);
// @override
// _WebPageState createState() => _WebPageState();
// }
// class _WebPageState extends State<WebPage> {
// final GlobalKey webViewKey = GlobalKey();
// InAppWebViewController webViewController;
// InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
// crossPlatform: InAppWebViewOptions(
// useShouldOverrideUrlLoading: true,
// useOnDownloadStart: true,
// mediaPlaybackRequiresUserGesture: false,
// ),
// android: AndroidInAppWebViewOptions(
// useHybridComposition: true,
// ),
// ios: IOSInAppWebViewOptions(
// allowsInlineMediaPlayback: true,
// ));
// PullToRefreshController pullToRefreshController;
// ContextMenu contextMenu;
// String url = "";
// double progress = 0;
// final urlController = TextEditingController();
// // Future<void> openFile(filePath) async {
// // final _result = await OpenFile.open(filePath);
// // print(_result.message);
// // // setState(() {
// // // _openResult = "type=${_result.type} message=${_result.message}";
// // // });
// // }
// @override
// void initState() {
// super.initState();
// // Enable hybrid composition.
// _init();
// }
// // Future<bool> _checkPermission() async {
// // DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
// // AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
// // if (Platform.isAndroid && androidInfo.version.sdkInt <= 28) {
// // final status = await Permission.storage.status;
// // if (status != PermissionStatus.granted) {
// // final result = await Permission.storage.request();
// // if (result == PermissionStatus.granted) {
// // return true;
// // }
// // } else {
// // return true;
// // }
// // } else {
// // return true;
// // }
// // return false;
// // }
// // Future<bool> checkPermissionStorage() async {
// // var status = await Permission.storage.status;
// // if (!status.isGranted) {
// // status = await Permission.storage.request();
// // if (status.isGranted) {
// // return true;
// // }
// // } else {
// // return true;
// // }
// // return false;
// // }
// _init() async {
// // WidgetsFlutterBinding.ensureInitialized();
// // await FlutterDownloader.initialize(debug: true);
// if (Platform.isAndroid) {
// await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
// var swAvailable = await AndroidWebViewFeature.isFeatureSupported(
// AndroidWebViewFeature.SERVICE_WORKER_BASIC_USAGE);
// var swInterceptAvailable = await AndroidWebViewFeature.isFeatureSupported(
// AndroidWebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST);
// if (swAvailable && swInterceptAvailable) {
// AndroidServiceWorkerController serviceWorkerController =
// AndroidServiceWorkerController.instance();
// serviceWorkerController.serviceWorkerClient =
// AndroidServiceWorkerClient(
// shouldInterceptRequest: (request) async {
// print(request);
// return null;
// },
// );
// }
// }
// // await FlutterDownloader.initialize(
// // debug: true // optional: set false to disable printing logs to console
// // );
// // await Permission.storage.request();
// }
// @override
// Widget build(BuildContext context) {
// final Completer<WebViewController> _controller =
// Completer<WebViewController>();
// return Scaffold(
// // We're using a Builder here so we have a context that is below the Scaffold
// // to allow calling Scaffold.of(context) so we can show a snackbar.
// appBar: SystemAppBar(
// widget.title,
// onTap: () {
// if (widget.need) {
// appBar: SystemAppBar(title, onTap: () {
// if (need) {
// Utils.changeStatusColor(color: MyColours.color_199095);
// }
// Navigator.of(context).pop();
// }),
// body: WillPopScope(
// onWillPop: () async {
// if (need) {
// Utils.changeStatusColor(color: MyColours.color_199095);
// }
// Navigator.of(context).pop();
// return true;
// },
// ),
// body: WillPopScope(
// onWillPop: () async {
// if (widget.need) {
// Utils.changeStatusColor(color: MyColours.color_199095);
// child: WebView(
// initialUrl: "http://colorcun.com/privacy1.html",
// javascriptMode: JavascriptMode.unrestricted,
// onWebViewCreated: (WebViewController webViewController) {
// // Loading.showLoading(context);
// _controller.complete(webViewController);
// },
// javascriptChannels: <JavascriptChannel>[
// _toasterJavascriptChannel(context),
// ].toSet(),
// navigationDelegate: (NavigationRequest request) {
// if (request.url.startsWith('https://www.youtube.com/')) {
// return NavigationDecision.prevent;
// }
// return true;
// return NavigationDecision.navigate;
// },
// child: Column(
// children: [
// Expanded(
// child: InAppWebView(
// key: webViewKey,
// // contextMenu: contextMenu,
// initialUrlRequest: URLRequest(
// url: Uri.parse("http://colorcun.com/privacy1.html")),
// // initialFile: "assets/index.html",
// initialUserScripts: UnmodifiableListView<UserScript>([]),
// initialOptions: options,
// pullToRefreshController: pullToRefreshController,
// onWebViewCreated: (controller) {
// webViewController = controller;
// },
// onLoadStart: (controller, url) {
// // setState(() {
// // this.url = url.toString();
// // urlController.text = this.url;
// // });
// },
// androidOnPermissionRequest:
// (controller, origin, resources) async {
// // return PermissionRequestResponse(
// // resources: resources,
// // action: PermissionRequestResponseAction.GRANT);
// },
// shouldOverrideUrlLoading:
// (controller, navigationAction) async {
// var uri = navigationAction.request.url;
// if (![
// "http",
// "https",
// "file",
// "chrome",
// "data",
// "javascript",
// "about"
// ].contains(uri.scheme)) {
// if (await canLaunch(url)) {
// // Launch the App
// await launch(
// url,
// );
// // and cancel the request
// return NavigationActionPolicy.CANCEL;
// }
// }
// return NavigationActionPolicy.ALLOW;
// },
// onLoadStop: (controller, url) async {
// // pullToRefreshController.endRefreshing();
// // setState(() {
// // this.url = url.toString();
// // urlController.text = this.url;
// // });
// },
// onLoadError: (controller, url, code, message) {
// pullToRefreshController.endRefreshing();
// },
// onProgressChanged: (controller, progress) {
// // if (progress == 100) {
// pullToRefreshController.endRefreshing();
// // }
// // setState(() {
// // this.progress = progress / 100;
// // urlController.text = this.url;
// // });
// },
// onUpdateVisitedHistory: (controller, url, androidIsReload) {
// // setState(() {
// // this.url = url.toString();
// // urlController.text = this.url;
// // });
// },
// onConsoleMessage: (controller, consoleMessage) {
// print(consoleMessage);
// },
// onDownloadStart: (controller, url) async {
// // print("onDownloadStart $url");
// // Utils.showToast('开始下载...');
// // // final taskId = await FlutterDownloader.enqueue(
// // // url: url.toString(),
// // // savedDir: (await getExternalStorageDirectory()).path,
// // // showNotification:
// // // true, // show download progress in status bar (for Android)
// // // openFileFromNotification:
// // // true, // click on notification to open downloaded file (for Android)
// // // );
// // final dir = await getExternalStorageDirectory();
// // String savePath = dir.path + "/one_key";
// // Directory directory = new Directory(savePath);
// // if (directory.existsSync()) {
// // //删除历史apk
// // final List<FileSystemEntity> files = directory.listSync();
// // for (FileSystemEntity file in files) {
// // file.deleteSync();
// // }
// // }
// // DioUtil().download(url.toString(), savePath,
// // onProgress: (received, total) {
// // // if (_progress != 100) {
// // // int progress = ((received / total) * 100).round();
// // // _progress = progress;
// // // _setState(() {});
// // // }
// // if (received == total) {
// // print('object');
// // Utils.showToast('文件已保存在${savePath}');
// // // openFile(savePath);
// // //下载完成
// // // InstallPlugin.installApk(savePath, "com.naji.one_key");
// // }
// // });
// // // },
// // // DioUtil().download(url.toString(),
// // // (await getExternalStorageDirectory()).path,
// // // onProgress: (received, total) {
// // // // if (_progress != 100) {
// // // // int progress = ((received / total) * 100).round();
// // // // _progress = progress;
// // // // _setState(() {});
// // // // }
// // // if (received == total) {
// // // //下载完成
// // // // InstallPlugin.installApk(savePath, "com.naji.one_key");
// // // }
// // // });
// },
// ))
// ],
// )
// // Builder(builder: (BuildContext context) {
// // return WebView(
// // initialUrl: "http://colorcun.com/privacy1.html",
// // javascriptMode: JavascriptMode.unrestricted,
// // onWebViewCreated: (WebViewController webViewController) {
// // _controller.complete(webViewController);
// // },
// // // TODO(iskakaushik): Remove this when collection literals makes it to stable.
// // // ignore: prefer_collection_literals
// // javascriptChannels: <JavascriptChannel>[
// // _toasterJavascriptChannel(context),
// // ].toSet(),
// // navigationDelegate: (NavigationRequest request) {
// // if (request.url.startsWith('https://www.youtube.com/')) {
// // print('blocking navigation to $request}');
// // return NavigationDecision.prevent;
// // }
// // print('allowing navigation to $request');
// // return NavigationDecision.navigate;
// // },
// // onPageStarted: (String url) {
// // print('Page started loading: $url');
// // },
// // onPageFinished: (String url) async {
// // print('Page finished loading: $url');
// // // showDownloadProgress(context);
// // final dir = await getExternalStorageDirectory();
// // String savePath = dir.path + "/one_key";
// // DioUtil().download(url, savePath,
// // onProgress: (received, total) {
// // // if (_progress != 100) {
// // // int progress = ((received / total) * 100).round();
// // // _progress = progress;
// // // _setState(() {});
// // // }
// // if (received == total) {
// // print('object');
// // //下载完成
// // // InstallPlugin.installApk(savePath, "com.naji.one_key");
// // }
// // });
// // },
// // gestureNavigationEnabled: true,
// // );
// // }),
// ));
// onPageStarted: (String url) {},
// onPageFinished: (String url) {
// // Loading.hideLoading(context);
// },
// gestureNavigationEnabled: true,
// )),
// );
// }
// // JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
// // return JavascriptChannel(
// // name: 'Toaster',
// // onMessageReceived: (JavascriptMessage message) {
// // Scaffold.of(context).showSnackBar(
// // SnackBar(content: Text(message.message)),
// // );
// // });
// // }
// JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
// return JavascriptChannel(
// name: 'Toaster',
// onMessageReceived: (JavascriptMessage message) {
// Scaffold.of(context).showSnackBar(
// SnackBar(content: Text(message.message)),
// );
// });
// }
// }
// // class WebPage extends StatelessWidget {
// // final String title;
// // final String url;
// // final bool need;
// // const WebPage({Key key, this.title, this.url, this.need = false})
// // : super(key: key);
// // @override
// // Widget build(BuildContext context) {
// // final Completer<WebViewController> _controller =
// // Completer<WebViewController>();
// // return Scaffold(
// // appBar: SystemAppBar(title, onTap: () {
// // if (need) {
// // Utils.changeStatusColor(color: MyColours.color_199095);
// // }
// // Navigator.of(context).pop();
// // }),
// // body: WillPopScope(
// // onWillPop: () async {
// // if (need) {
// // Utils.changeStatusColor(color: MyColours.color_199095);
// // }
// // return true;
// // },
// // child: WebView(
// // initialUrl: "http://colorcun.com/privacy1.html",
// // javascriptMode: JavascriptMode.unrestricted,
// // onWebViewCreated: (WebViewController webViewController) {
// // // Loading.showLoading(context);
// // _controller.complete(webViewController);
// // },
// // javascriptChannels: <JavascriptChannel>[
// // _toasterJavascriptChannel(context),
// // ].toSet(),
// // navigationDelegate: (NavigationRequest request) {
// // if (request.url.startsWith('https://www.youtube.com/')) {
// // return NavigationDecision.prevent;
// // }
// // return NavigationDecision.navigate;
// // },
// // onPageStarted: (String url) {},
// // onPageFinished: (String url) {
// // // Loading.hideLoading(context);
// // },
// // gestureNavigationEnabled: true,
// // )),
// // );
// // }
// // JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
// // return JavascriptChannel(
// // name: 'Toaster',
// // onMessageReceived: (JavascriptMessage message) {
// // Scaffold.of(context).showSnackBar(
// // SnackBar(content: Text(message.message)),
// // );
// // });
// // }
// // }
... ...
... ... @@ -41,7 +41,7 @@ class MyCustomerListPage extends StatefulWidget {
}
class _MyCustomerListPageState extends State<MyCustomerListPage>
with TickerProviderStateMixin, SingleTickerProviderStateMixin {
with TickerProviderStateMixin{
List _tabBarTitle;
TabController tabController;
... ...
... ... @@ -38,7 +38,7 @@ class MyShowCustomerListPage extends StatefulWidget {
}
class _MyShowCustomerListPageState extends State<MyShowCustomerListPage>
with TickerProviderStateMixin, SingleTickerProviderStateMixin {
with TickerProviderStateMixin {
List _tabBarTitle;
TabController tabController;
... ...
... ... @@ -43,7 +43,7 @@ class CustomerListPage extends StatefulWidget {
}
class _CustomerListPageState extends State<CustomerListPage>
with TickerProviderStateMixin, SingleTickerProviderStateMixin {
with TickerProviderStateMixin{
List _tabBarTitle;
TabController tabController;
... ...
... ... @@ -38,7 +38,7 @@ class ShowCustomerListPage extends StatefulWidget {
}
class _ShowCustomerListPageState extends State<ShowCustomerListPage>
with TickerProviderStateMixin, SingleTickerProviderStateMixin {
with TickerProviderStateMixin {
List _tabBarTitle;
TabController tabController;
... ... @@ -73,7 +73,6 @@ class _ShowCustomerListPageState extends State<ShowCustomerListPage>
"gid": widget.gid,
};
await customerProvider.getSellerGuestByGid(context, params);
}
/// 搜索框
... ...
import 'package:audioplayers/audioplayers.dart';
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:one_key/constant/constant.dart';
... ... @@ -7,12 +8,12 @@ import 'package:one_key/net/dio_util.dart';
import 'package:one_key/net/http_url.dart';
import 'package:one_key/utils/dialog_util.dart';
import 'package:audioplayers/audio_cache.dart';
// import 'package:audioplayers/audio_cache.dart';
// import 'package:audioplayers/audioplayers.dart';
import 'package:one_key/utils/utils.dart';
class MessageDialogModel extends ChangeNotifier {
final _player = AudioCache();
final _player = AudioPlayer();
bool showMessageDialog = false;
... ... @@ -20,9 +21,9 @@ class MessageDialogModel extends ChangeNotifier {
List<Message> unMessagesOnline = []; //未读在线预定
List<Message> unMessagesOrder = []; //未读提前点餐
void init() {
_player.load("tts_online.mp3");
}
// void init() {
// _player.load("tts_online.mp3");
// }
/// 播放
void play(String message_type) {
... ...
... ... @@ -104,10 +104,10 @@ class StoreBaseIndoProvider with ChangeNotifier {
} catch (e) {
Loading.hideLoading(context);
return new Future.error(new DioError(
// requestOptions: response.requestOptions,
requestOptions: response.requestOptions,
response: response,
error: "data parsing exception...",
type: DioErrorType.RESPONSE,
type: DioErrorType.response,
));
}
}
... ...
## 1.0.7
* Minor documentation update to indicate known issue on iOS 13.4 and 13.5.
* See: https://github.com/flutter/flutter/issues/53490
## 1.0.6
* Invoke the WebView.onWebResourceError on iOS when the webview content process crashes.
## 1.0.5
* Fix example in the readme.
## 1.0.4
* Suppress the `deprecated_member_use` warning in the example app for `ScaffoldMessenger.showSnackBar`.
## 1.0.3
* Update android compileSdkVersion to 29.
## 1.0.2
* Android Code Inspection and Clean up.
## 1.0.1
* Add documentation for `WebViewPlatformCreatedCallback`.
## 1.0.0 - Out of developer preview 🎉.
* Bumped the minimal Flutter SDK to 1.22 where platform views are out of developer preview, and
performing better on iOS. Flutter 1.22 no longer requires adding the
`io.flutter.embedded_views_preview` flag to `Info.plist`.
* Added support for Hybrid Composition on Android (see opt-in instructions in [README](https://github.com/flutter/plugins/blob/master/packages/webview_flutter/README.md#android))
* Lowered the required Android API to 19 (was previously 20): [#23728](https://github.com/flutter/flutter/issues/23728).
* Fixed the following issues:
* 🎹 Keyboard: [#41089](https://github.com/flutter/flutter/issues/41089), [#36478](https://github.com/flutter/flutter/issues/36478), [#51254](https://github.com/flutter/flutter/issues/51254), [#50716](https://github.com/flutter/flutter/issues/50716), [#55724](https://github.com/flutter/flutter/issues/55724), [#56513](https://github.com/flutter/flutter/issues/56513), [#56515](https://github.com/flutter/flutter/issues/56515), [#61085](https://github.com/flutter/flutter/issues/61085), [#62205](https://github.com/flutter/flutter/issues/62205), [#62547](https://github.com/flutter/flutter/issues/62547), [#58943](https://github.com/flutter/flutter/issues/58943), [#56361](https://github.com/flutter/flutter/issues/56361), [#56361](https://github.com/flutter/flutter/issues/42902), [#40716](https://github.com/flutter/flutter/issues/40716), [#37989](https://github.com/flutter/flutter/issues/37989), [#27924](https://github.com/flutter/flutter/issues/27924).
* ♿️ Accessibility: [#50716](https://github.com/flutter/flutter/issues/50716).
* ⚡️ Performance: [#61280](https://github.com/flutter/flutter/issues/61280), [#31243](https://github.com/flutter/flutter/issues/31243), [#52211](https://github.com/flutter/flutter/issues/52211).
* 📹 Video: [#5191](https://github.com/flutter/flutter/issues/5191).
## 0.3.24
* Keep handling deprecated Android v1 classes for backward compatibility.
## 0.3.23
* Handle WebView multi-window support.
## 0.3.22+2
* Update package:e2e reference to use the local version in the flutter/plugins
repository.
## 0.3.22+1
* Update the `setAndGetScrollPosition` to use hard coded values and add a `pumpAndSettle` call.
## 0.3.22
* Add support for passing a failing url.
## 0.3.21
* Enable programmatic scrolling using Android's WebView.scrollTo & iOS WKWebView.scrollView.contentOffset.
## 0.3.20+2
* Fix CocoaPods podspec lint warnings.
## 0.3.20+1
* OCMock module import -> #import, unit tests compile generated as library.
* Fix select drop down crash on old Android tablets (https://github.com/flutter/flutter/issues/54164).
## 0.3.20
* Added support for receiving web resource loading errors. See `WebView.onWebResourceError`.
## 0.3.19+10
* Replace deprecated `getFlutterEngine` call on Android.
## 0.3.19+9
* Remove example app's iOS workspace settings.
## 0.3.19+8
* Make the pedantic dev_dependency explicit.
## 0.3.19+7
* Remove the Flutter SDK constraint upper bound.
## 0.3.19+6
* Enable opening links that target the "_blank" window (links open in same window).
## 0.3.19+5
* On iOS, always keep contentInsets of the WebView to be 0.
* Fix XCTest case to follow XCTest naming convention.
## 0.3.19+4
* On iOS, fix the scroll view content inset is automatically adjusted. After the fix, the content position of the WebView is customizable by Flutter.
* Fix an iOS 13 bug where the scroll indicator shows at random location.
## 0.3.19+3
* Setup XCTests.
## 0.3.19+2
* Migrate from deprecated BinaryMessages to ServicesBinding.instance.defaultBinaryMessenger.
## 0.3.19+1
* Raise min Flutter SDK requirement to the latest stable. v2 embedding apps no
longer need to special case their Flutter SDK requirement like they have
since v0.3.15+3.
## 0.3.19
* Add setting for iOS to allow gesture based navigation.
## 0.3.18+1
* Be explicit that keyboard is not ready for production in README.md.
## 0.3.18
* Add support for onPageStarted event.
* Remove the deprecated `author:` field from pubspec.yaml
* Migrate to the new pubspec platforms manifest.
* Require Flutter SDK 1.10.0 or greater.
## 0.3.17
* Fix pedantic lint errors. Added missing documentation and awaited some futures
in tests and the example app.
## 0.3.16
* Add support for async NavigationDelegates. Synchronous NavigationDelegates
should still continue to function without any change in behavior.
## 0.3.15+3
* Re-land support for the v2 Android embedding. This correctly sets the minimum
SDK to the latest stable and avoid any compile errors. *WARNING:* the V2
embedding itself still requires the current Flutter master channel
(flutter/flutter@1d4d63a) for text input to work properly on all Android
versions.
## 0.3.15+2
* Remove AndroidX warnings.
## 0.3.15+1
* Revert the prior embedding support add since it requires an API that hasn't
rolled to stable.
## 0.3.15
* Add support for the v2 Android embedding. This shouldn't affect existing
functionality. Plugin authors who use the V2 embedding can now register the
plugin and expect that it correctly responds to app lifecycle changes.
## 0.3.14+2
* Define clang module for iOS.
## 0.3.14+1
* Allow underscores anywhere for Javascript Channel name.
## 0.3.14
* Added a getTitle getter to WebViewController.
## 0.3.13
* Add an optional `userAgent` property to set a custom User Agent.
## 0.3.12+1
* Temporarily revert getTitle (doing this as a patch bump shortly after publishing).
## 0.3.12
* Added a getTitle getter to WebViewController.
## 0.3.11+6
* Calling destroy on Android webview when flutter webview is getting disposed.
## 0.3.11+5
* Reduce compiler warnings regarding iOS9 compatibility by moving a single
method back into a `@available` block.
## 0.3.11+4
* Removed noisy log messages on iOS.
## 0.3.11+3
* Apply the display listeners workaround that was shipped in 0.3.11+1 on
all Android versions prior to P.
## 0.3.11+2
* Add fix for input connection being dropped after a screen resize on certain
Android devices.
## 0.3.11+1
* Work around a bug in old Android WebView versions that was causing a crash
when resizing the webview on old devices.
## 0.3.11
* Add an initialAutoMediaPlaybackPolicy setting for controlling how auto media
playback is restricted.
## 0.3.10+5
* Add dependency on `androidx.annotation:annotation:1.0.0`.
## 0.3.10+4
* Add keyboard text to README.
## 0.3.10+3
* Don't log an unknown setting key error for 'debuggingEnabled' on iOS.
## 0.3.10+2
* Fix InputConnection being lost when combined with route transitions.
## 0.3.10+1
* Add support for simultaenous Flutter `TextInput` and WebView text fields.
## 0.3.10
* Add partial WebView keyboard support for Android versions prior to N. Support
for UIs that also have Flutter `TextInput` fields is still pending. This basic
support currently only works with Flutter `master`. The keyboard will still
appear when it previously did not when run with older versions of Flutter. But
if the WebView is resized while showing the keyboard the text field will need
to be focused multiple times for any input to be registered.
## 0.3.9+2
* Update Dart code to conform to current Dart formatter.
## 0.3.9+1
* Add missing template type parameter to `invokeMethod` calls.
* Bump minimum Flutter version to 1.5.0.
* Replace invokeMethod with invokeMapMethod wherever necessary.
## 0.3.9
* Allow external packages to provide webview implementations for new platforms.
## 0.3.8+1
* Suppress deprecation warning for BinaryMessages. See: https://github.com/flutter/flutter/issues/33446
## 0.3.8
* Add `debuggingEnabled` property.
## 0.3.7+1
* Fix an issue where JavaScriptChannel messages weren't sent from the platform thread on Android.
## 0.3.7
* Fix loadUrlWithHeaders flaky test.
## 0.3.6+1
* Remove un-used method params in webview\_flutter
## 0.3.6
* Add an optional `headers` field to the controller.
## 0.3.5+5
* Fixed error in documentation of `javascriptChannels`.
## 0.3.5+4
* Fix bugs in the example app by updating it to use a `StatefulWidget`.
## 0.3.5+3
* Make sure to post javascript channel messages from the platform thread.
## 0.3.5+2
* Fix crash from `NavigationDelegate` on later versions of Android.
## 0.3.5+1
* Fix a bug where updates to onPageFinished were ignored.
## 0.3.5
* Added an onPageFinished callback.
## 0.3.4
* Support specifying navigation delegates that can prevent navigations from being executed.
## 0.3.3+2
* Exclude LongPress handler from semantics tree since it does nothing.
## 0.3.3+1
* Fixed a memory leak on Android - the WebView was not properly disposed.
## 0.3.3
* Add clearCache method to WebView controller.
## 0.3.2+1
* Log a more detailed warning at build time about the previous AndroidX
migration.
## 0.3.2
* Added CookieManager to interface with WebView cookies. Currently has the ability to clear cookies.
## 0.3.1
* Added JavaScript channels to facilitate message passing from JavaScript code running inside
the WebView to the Flutter app's Dart code.
## 0.3.0
* **Breaking change**. Migrate from the deprecated original Android Support
Library to AndroidX. This shouldn't result in any functional changes, but it
requires any Android apps using this plugin to [also
migrate](https://developer.android.com/jetpack/androidx/migrate) if they're
using the original support library.
## 0.2.0
* Added a evaluateJavascript method to WebView controller.
* (BREAKING CHANGE) Renamed the `JavaScriptMode` enum to `JavascriptMode`, and the WebView `javasScriptMode` parameter to `javascriptMode`.
## 0.1.2
* Added a reload method to the WebView controller.
## 0.1.1
* Added a `currentUrl` accessor for the WebView controller to look up what URL
is being displayed.
## 0.1.0+1
* Fix null crash when initialUrl is unset on iOS.
## 0.1.0
* Add goBack, goForward, canGoBack, and canGoForward methods to the WebView controller.
## 0.0.1+1
* Fix case for "FLTWebViewFlutterPlugin" (iOS was failing to buld on case-sensitive file systems).
## 0.0.1
* Initial release.
Copyright 2018 The Chromium Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# WebView for Flutter
[![pub package](https://img.shields.io/pub/v/webview_flutter.svg)](https://pub.dartlang.org/packages/webview_flutter)
A Flutter plugin that provides a WebView widget.
On iOS the WebView widget is backed by a [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview);
On Android the WebView widget is backed by a [WebView](https://developer.android.com/reference/android/webkit/WebView).
## Usage
Add `webview_flutter` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/).
You can now include a WebView widget in your widget tree. See the
[WebView](https://pub.dev/documentation/webview_flutter/latest/webview_flutter/WebView-class.html)
widget's Dartdoc for more details on how to use the widget.
## Android Platform Views
The WebView is relying on
[Platform Views](https://flutter.dev/docs/development/platform-integration/platform-views) to embed
the Android’s webview within the Flutter app. By default a Virtual Display based platform view
backend is used, this implementation has multiple
[keyboard](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Avd-only+label%3A%22p%3A+webview-keyboard%22).
When keyboard input is required we recommend using the Hybrid Composition based platform views
implementation. Note that on Android versions prior to Android 10 Hybrid Composition has some
[performance drawbacks](https://flutter.dev/docs/development/platform-integration/platform-views#performance).
### Using Hybrid Composition
To enable hybrid composition, set `WebView.platform = SurfaceAndroidWebView();` in `initState()`.
For example:
```dart
import 'dart:io';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewExample extends StatefulWidget {
@override
WebViewExampleState createState() => WebViewExampleState();
}
class WebViewExampleState extends State<WebViewExample> {
@override
void initState() {
super.initState();
// Enable hybrid composition.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
@override
Widget build(BuildContext context) {
return WebView(
initialUrl: 'https://flutter.dev',
);
}
}
```
`SurfaceAndroidWebView()` requires [API level 19](https://developer.android.com/studio/releases/platforms?hl=th#4.4). The plugin itself doesn't enforce the API level, so if you want to make the app available on devices running this API level or above, add the following to `<your-app>/android/app/build.gradle`:
```gradle
android {
defaultConfig {
// Required by the Flutter WebView plugin.
minSdkVersion 19
}
}
```
group 'io.flutter.plugins.webviewflutter'
version '1.0-SNAPSHOT'
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.0'
}
}
rootProject.allprojects {
repositories {
google()
jcenter()
}
}
apply plugin: 'com.android.library'
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 16
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
disable 'InvalidPackage'
}
dependencies {
implementation 'androidx.annotation:annotation:1.0.0'
implementation 'androidx.webkit:webkit:1.0.0'
}
}
<manifest package="io.flutter.plugins.webviewflutter">
</manifest>
package io.flutter.plugins.webviewflutter;
import static android.hardware.display.DisplayManager.DisplayListener;
import android.annotation.TargetApi;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.ArrayList;
/**
* Works around an Android WebView bug by filtering some DisplayListener invocations.
*
* <p>Older Android WebView versions had assumed that when {@link DisplayListener#onDisplayChanged}
* is invoked, the display ID it is provided is of a valid display. However it turns out that when a
* display is removed Android may call onDisplayChanged with the ID of the removed display, in this
* case the Android WebView code tries to fetch and use the display with this ID and crashes with an
* NPE.
*
* <p>This issue was fixed in the Android WebView code in
* https://chromium-review.googlesource.com/517913 which is available starting WebView version
* 58.0.3029.125 however older webviews in the wild still have this issue.
*
* <p>Since Flutter removes virtual displays whenever a platform view is resized the webview crash
* is more likely to happen than other apps. And users were reporting this issue see:
* https://github.com/flutter/flutter/issues/30420
*
* <p>This class works around the webview bug by unregistering the WebView's DisplayListener, and
* instead registering its own DisplayListener which delegates the callbacks to the WebView's
* listener unless it's a onDisplayChanged for an invalid display.
*
* <p>I did not find a clean way to get a handle of the WebView's DisplayListener so I'm using
* reflection to fetch all registered listeners before and after initializing a webview. In the
* first initialization of a webview within the process the difference between the lists is the
* webview's display listener.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
class DisplayListenerProxy {
private static final String TAG = "DisplayListenerProxy";
private ArrayList<DisplayListener> listenersBeforeWebView;
/** Should be called prior to the webview's initialization. */
void onPreWebViewInitialization(DisplayManager displayManager) {
listenersBeforeWebView = yoinkDisplayListeners(displayManager);
}
/** Should be called after the webview's initialization. */
void onPostWebViewInitialization(final DisplayManager displayManager) {
final ArrayList<DisplayListener> webViewListeners = yoinkDisplayListeners(displayManager);
// We recorded the list of listeners prior to initializing webview, any new listeners we see
// after initializing the webview are listeners added by the webview.
webViewListeners.removeAll(listenersBeforeWebView);
if (webViewListeners.isEmpty()) {
// The Android WebView registers a single display listener per process (even if there
// are multiple WebView instances) so this list is expected to be non-empty only the
// first time a webview is initialized.
// Note that in an add2app scenario if the application had instantiated a non Flutter
// WebView prior to instantiating the Flutter WebView we are not able to get a reference
// to the WebView's display listener and can't work around the bug.
//
// This means that webview resizes in add2app Flutter apps with a non Flutter WebView
// running on a system with a webview prior to 58.0.3029.125 may crash (the Android's
// behavior seems to be racy so it doesn't always happen).
return;
}
for (DisplayListener webViewListener : webViewListeners) {
// Note that while DisplayManager.unregisterDisplayListener throws when given an
// unregistered listener, this isn't an issue as the WebView code never calls
// unregisterDisplayListener.
displayManager.unregisterDisplayListener(webViewListener);
// We never explicitly unregister this listener as the webview's listener is never
// unregistered (it's released when the process is terminated).
displayManager.registerDisplayListener(
new DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
for (DisplayListener webViewListener : webViewListeners) {
webViewListener.onDisplayAdded(displayId);
}
}
@Override
public void onDisplayRemoved(int displayId) {
for (DisplayListener webViewListener : webViewListeners) {
webViewListener.onDisplayRemoved(displayId);
}
}
@Override
public void onDisplayChanged(int displayId) {
if (displayManager.getDisplay(displayId) == null) {
return;
}
for (DisplayListener webViewListener : webViewListeners) {
webViewListener.onDisplayChanged(displayId);
}
}
},
null);
}
}
@SuppressWarnings({"unchecked", "PrivateApi"})
private static ArrayList<DisplayListener> yoinkDisplayListeners(DisplayManager displayManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// We cannot use reflection on Android P, but it shouldn't matter as it shipped
// with WebView 66.0.3359.158 and the WebView version the bug this code is working around was
// fixed in 61.0.3116.0.
return new ArrayList<>();
}
try {
Field displayManagerGlobalField = DisplayManager.class.getDeclaredField("mGlobal");
displayManagerGlobalField.setAccessible(true);
Object displayManagerGlobal = displayManagerGlobalField.get(displayManager);
Field displayListenersField =
displayManagerGlobal.getClass().getDeclaredField("mDisplayListeners");
displayListenersField.setAccessible(true);
ArrayList<Object> delegates =
(ArrayList<Object>) displayListenersField.get(displayManagerGlobal);
Field listenerField = null;
ArrayList<DisplayManager.DisplayListener> listeners = new ArrayList<>();
for (Object delegate : delegates) {
if (listenerField == null) {
listenerField = delegate.getClass().getField("mListener");
listenerField.setAccessible(true);
}
DisplayManager.DisplayListener listener =
(DisplayManager.DisplayListener) listenerField.get(delegate);
listeners.add(listener);
}
return listeners;
} catch (NoSuchFieldException | IllegalAccessException e) {
Log.w(TAG, "Could not extract WebView's display listeners. " + e);
return new ArrayList<>();
}
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugins.webviewflutter;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.webkit.CookieManager;
import android.webkit.ValueCallback;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
class FlutterCookieManager implements MethodCallHandler {
private final MethodChannel methodChannel;
FlutterCookieManager(BinaryMessenger messenger) {
methodChannel = new MethodChannel(messenger, "plugins.flutter.io/cookie_manager");
methodChannel.setMethodCallHandler(this);
}
@Override
public void onMethodCall(MethodCall methodCall, Result result) {
switch (methodCall.method) {
case "clearCookies":
clearCookies(result);
break;
default:
result.notImplemented();
}
}
void dispose() {
methodChannel.setMethodCallHandler(null);
}
private static void clearCookies(final Result result) {
CookieManager cookieManager = CookieManager.getInstance();
final boolean hasCookies = cookieManager.hasCookies();
if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
cookieManager.removeAllCookies(
new ValueCallback<Boolean>() {
@Override
public void onReceiveValue(Boolean value) {
result.success(hasCookies);
}
});
} else {
cookieManager.removeAllCookie();
result.success(hasCookies);
}
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//package io.flutter.plugins.webviewflutter;
//
//import android.annotation.TargetApi;
//import android.content.Context;
//import android.hardware.display.DisplayManager;
//import android.os.Build;
//import android.os.Handler;
//import android.os.Message;
//import android.view.View;
//import android.webkit.WebChromeClient;
//import android.webkit.WebResourceRequest;
//import android.webkit.WebStorage;
//import android.webkit.WebView;
//import android.webkit.WebViewClient;
//import androidx.annotation.NonNull;
//import io.flutter.plugin.common.BinaryMessenger;
//import io.flutter.plugin.common.MethodCall;
//import io.flutter.plugin.common.MethodChannel;
//import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
//import io.flutter.plugin.common.MethodChannel.Result;
//import io.flutter.plugin.platform.PlatformView;
//import java.util.Collections;
//import java.util.List;
//import java.util.Map;
//import android.content.Intent;
//package io.flutter.plugins.webviewflutter;
package io.flutter.plugins.webviewflutter;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.platform.PlatformView;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import android.content.Intent;
//package io.flutter.plugins.webviewflutter;
public class FlutterWebView implements PlatformView, MethodCallHandler {
private static final String JS_CHANNEL_NAMES_FIELD = "javascriptChannelNames";
private final InputAwareWebView webView;
private final MethodChannel methodChannel;
private final FlutterWebViewClient flutterWebViewClient;
private final Handler platformThreadHandler;
// Verifies that a url opened by `Window.open` has a secure url.
private class FlutterWebChromeClient extends WebChromeClient {
@Override
public boolean onCreateWindow(
final WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
final WebViewClient webViewClient =
new WebViewClient() {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public boolean shouldOverrideUrlLoading(
@NonNull WebView view, @NonNull WebResourceRequest request) {
final String url = request.getUrl().toString();
if (!flutterWebViewClient.shouldOverrideUrlLoading(
FlutterWebView.this.webView, request)) {
webView.loadUrl(url);
}
return true;
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!flutterWebViewClient.shouldOverrideUrlLoading(
FlutterWebView.this.webView, url)) {
webView.loadUrl(url);
}
return true;
}
};
final WebView newWebView = new WebView(view.getContext());
newWebView.setWebViewClient(webViewClient);
final WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(newWebView);
resultMsg.sendToTarget();
return true;
}
// For Android < 3.0
public void openFileChooser(ValueCallback<Uri> valueCallback) {
Log.v(TAG, "openFileChooser Android < 3.0");
uploadMessage = valueCallback;
openImageChooserActivity();
}
// For Android >= 3.0
public void openFileChooser(ValueCallback valueCallback, String acceptType) {
Log.v(TAG, "openFileChooser Android >= 3.0");
uploadMessage = valueCallback;
openImageChooserActivity();
}
//For Android >= 4.1
public void openFileChooser(ValueCallback<Uri> valueCallback, String acceptType, String capture) {
Log.v(TAG, "openFileChooser Android >= 4.1");
uploadMessage = valueCallback;
openImageChooserActivity();
}
// For Android >= 5.0
@Override
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
Log.v(TAG, "openFileChooser Android >= 5.0");
uploadMessageAboveL = filePathCallback;
openImageChooserActivity();
return true;
}
}
private void openImageChooserActivity() {
Log.v(TAG, "openImageChooserActivity");
Intent intent1 = new Intent(Intent.ACTION_PICK, null);
intent1.setDataAndType(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
Intent chooser = new Intent(Intent.ACTION_CHOOSER);
chooser.putExtra(Intent.EXTRA_TITLE, "选择图片");
chooser.putExtra(Intent.EXTRA_INTENT,intent1);
if (WebViewFlutterPlugin.activity != null){
WebViewFlutterPlugin.activity.startActivityForResult(chooser, FILE_CHOOSER_RESULT_CODE);
} else {
Log.v(TAG, "activity is null");
}
}
public boolean activityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == FILE_CHOOSER_RESULT_CODE) {
if (null == uploadMessage && null == uploadMessageAboveL) {
return false;
}
Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
if (uploadMessageAboveL != null) {
onActivityResultAboveL(requestCode, resultCode, data);
} else if (uploadMessage != null && result != null) {
uploadMessage.onReceiveValue(result);
uploadMessage = null;
}
}
return false;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void onActivityResultAboveL(int requestCode, int resultCode, Intent intent) {
if (requestCode != FILE_CHOOSER_RESULT_CODE || uploadMessageAboveL == null)
{
return;
}
Uri[] results = null;
if (resultCode == Activity.RESULT_OK) {
if (intent != null) {
String dataString = intent.getDataString();
ClipData clipData = intent.getClipData();
if (clipData != null) {
results = new Uri[clipData.getItemCount()];
for (int i = 0; i < clipData.getItemCount(); i++) {
ClipData.Item item = clipData.getItemAt(i);
results[i] = item.getUri();
}
}
if (dataString != null)
{
results = new Uri[]{Uri.parse(dataString)};
}
}
}
uploadMessageAboveL.onReceiveValue(results);
uploadMessageAboveL = null;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@SuppressWarnings("unchecked")
FlutterWebView(
final Context context,
BinaryMessenger messenger,
int id,
Map<String, Object> params,
View containerView) {
DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy();
DisplayManager displayManager =
(DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
displayListenerProxy.onPreWebViewInitialization(displayManager);
webView = new InputAwareWebView(context, containerView);
displayListenerProxy.onPostWebViewInitialization(displayManager);
platformThreadHandler = new Handler(context.getMainLooper());
// Allow local storage.
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
// Multi windows is set with FlutterWebChromeClient by default to handle internal bug: b/159892679.
webView.getSettings().setSupportMultipleWindows(true);
webView.setWebChromeClient(new FlutterWebChromeClient());
methodChannel = new MethodChannel(messenger, "plugins.flutter.io/webview_" + id);
methodChannel.setMethodCallHandler(this);
flutterWebViewClient = new FlutterWebViewClient(methodChannel);
Map<String, Object> settings = (Map<String, Object>) params.get("settings");
if (settings != null) applySettings(settings);
if (params.containsKey(JS_CHANNEL_NAMES_FIELD)) {
List<String> names = (List<String>) params.get(JS_CHANNEL_NAMES_FIELD);
if (names != null) registerJavaScriptChannelNames(names);
}
Integer autoMediaPlaybackPolicy = (Integer) params.get("autoMediaPlaybackPolicy");
if (autoMediaPlaybackPolicy != null) updateAutoMediaPlaybackPolicy(autoMediaPlaybackPolicy);
if (params.containsKey("userAgent")) {
String userAgent = (String) params.get("userAgent");
updateUserAgent(userAgent);
}
if (params.containsKey("initialUrl")) {
String url = (String) params.get("initialUrl");
webView.loadUrl(url);
}
}
@Override
public View getView() {
return webView;
}
// @Override
// This is overriding a method that hasn't rolled into stable Flutter yet. Including the
// annotation would cause compile time failures in versions of Flutter too old to include the new
// method. However leaving it raw like this means that the method will be ignored in old versions
// of Flutter but used as an override anyway wherever it's actually defined.
// TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable.
public void onInputConnectionUnlocked() {
webView.unlockInputConnection();
}
// @Override
// This is overriding a method that hasn't rolled into stable Flutter yet. Including the
// annotation would cause compile time failures in versions of Flutter too old to include the new
// method. However leaving it raw like this means that the method will be ignored in old versions
// of Flutter but used as an override anyway wherever it's actually defined.
// TODO(mklim): Add the @Override annotation once flutter/engine#9727 rolls to stable.
public void onInputConnectionLocked() {
webView.lockInputConnection();
}
// @Override
// This is overriding a method that hasn't rolled into stable Flutter yet. Including the
// annotation would cause compile time failures in versions of Flutter too old to include the new
// method. However leaving it raw like this means that the method will be ignored in old versions
// of Flutter but used as an override anyway wherever it's actually defined.
// TODO(mklim): Add the @Override annotation once stable passes v1.10.9.
public void onFlutterViewAttached(View flutterView) {
webView.setContainerView(flutterView);
}
// @Override
// This is overriding a method that hasn't rolled into stable Flutter yet. Including the
// annotation would cause compile time failures in versions of Flutter too old to include the new
// method. However leaving it raw like this means that the method will be ignored in old versions
// of Flutter but used as an override anyway wherever it's actually defined.
// TODO(mklim): Add the @Override annotation once stable passes v1.10.9.
public void onFlutterViewDetached() {
webView.setContainerView(null);
}
@Override
public void onMethodCall(MethodCall methodCall, Result result) {
switch (methodCall.method) {
case "loadUrl":
loadUrl(methodCall, result);
break;
case "updateSettings":
updateSettings(methodCall, result);
break;
case "canGoBack":
canGoBack(result);
break;
case "canGoForward":
canGoForward(result);
break;
case "goBack":
goBack(result);
break;
case "goForward":
goForward(result);
break;
case "reload":
reload(result);
break;
case "currentUrl":
currentUrl(result);
break;
case "evaluateJavascript":
evaluateJavaScript(methodCall, result);
break;
case "addJavascriptChannels":
addJavaScriptChannels(methodCall, result);
break;
case "removeJavascriptChannels":
removeJavaScriptChannels(methodCall, result);
break;
case "clearCache":
clearCache(result);
break;
case "getTitle":
getTitle(result);
break;
case "scrollTo":
scrollTo(methodCall, result);
break;
case "scrollBy":
scrollBy(methodCall, result);
break;
case "getScrollX":
getScrollX(result);
break;
case "getScrollY":
getScrollY(result);
break;
default:
result.notImplemented();
}
}
@SuppressWarnings("unchecked")
private void loadUrl(MethodCall methodCall, Result result) {
Map<String, Object> request = (Map<String, Object>) methodCall.arguments;
String url = (String) request.get("url");
Map<String, String> headers = (Map<String, String>) request.get("headers");
if (headers == null) {
headers = Collections.emptyMap();
}
webView.loadUrl(url, headers);
result.success(null);
}
private void canGoBack(Result result) {
result.success(webView.canGoBack());
}
private void canGoForward(Result result) {
result.success(webView.canGoForward());
}
private void goBack(Result result) {
if (webView.canGoBack()) {
webView.goBack();
}
result.success(null);
}
private void goForward(Result result) {
if (webView.canGoForward()) {
webView.goForward();
}
result.success(null);
}
private void reload(Result result) {
webView.reload();
result.success(null);
}
private void currentUrl(Result result) {
result.success(webView.getUrl());
}
@SuppressWarnings("unchecked")
private void updateSettings(MethodCall methodCall, Result result) {
applySettings((Map<String, Object>) methodCall.arguments);
result.success(null);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private void evaluateJavaScript(MethodCall methodCall, final Result result) {
String jsString = (String) methodCall.arguments;
if (jsString == null) {
throw new UnsupportedOperationException("JavaScript string cannot be null");
}
webView.evaluateJavascript(
jsString,
new android.webkit.ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
result.success(value);
}
});
}
@SuppressWarnings("unchecked")
private void addJavaScriptChannels(MethodCall methodCall, Result result) {
List<String> channelNames = (List<String>) methodCall.arguments;
registerJavaScriptChannelNames(channelNames);
result.success(null);
}
@SuppressWarnings("unchecked")
private void removeJavaScriptChannels(MethodCall methodCall, Result result) {
List<String> channelNames = (List<String>) methodCall.arguments;
for (String channelName : channelNames) {
webView.removeJavascriptInterface(channelName);
}
result.success(null);
}
private void clearCache(Result result) {
webView.clearCache(true);
WebStorage.getInstance().deleteAllData();
result.success(null);
}
private void getTitle(Result result) {
result.success(webView.getTitle());
}
private void scrollTo(MethodCall methodCall, Result result) {
Map<String, Object> request = methodCall.arguments();
int x = (int) request.get("x");
int y = (int) request.get("y");
webView.scrollTo(x, y);
result.success(null);
}
private void scrollBy(MethodCall methodCall, Result result) {
Map<String, Object> request = methodCall.arguments();
int x = (int) request.get("x");
int y = (int) request.get("y");
webView.scrollBy(x, y);
result.success(null);
}
private void getScrollX(Result result) {
result.success(webView.getScrollX());
}
private void getScrollY(Result result) {
result.success(webView.getScrollY());
}
private void applySettings(Map<String, Object> settings) {
for (String key : settings.keySet()) {
switch (key) {
case "jsMode":
Integer mode = (Integer) settings.get(key);
if (mode != null) updateJsMode(mode);
break;
case "hasNavigationDelegate":
final boolean hasNavigationDelegate = (boolean) settings.get(key);
final WebViewClient webViewClient =
flutterWebViewClient.createWebViewClient(hasNavigationDelegate);
webView.setWebViewClient(webViewClient);
break;
case "debuggingEnabled":
final boolean debuggingEnabled = (boolean) settings.get(key);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
webView.setWebContentsDebuggingEnabled(debuggingEnabled);
}
break;
case "gestureNavigationEnabled":
break;
case "userAgent":
updateUserAgent((String) settings.get(key));
break;
default:
throw new IllegalArgumentException("Unknown WebView setting: " + key);
}
}
}
private void updateJsMode(int mode) {
switch (mode) {
case 0: // disabled
webView.getSettings().setJavaScriptEnabled(false);
break;
case 1: // unrestricted
webView.getSettings().setJavaScriptEnabled(true);
break;
default:
throw new IllegalArgumentException("Trying to set unknown JavaScript mode: " + mode);
}
}
private void updateAutoMediaPlaybackPolicy(int mode) {
// This is the index of the AutoMediaPlaybackPolicy enum, index 1 is always_allow, for all
// other values we require a user gesture.
boolean requireUserGesture = mode != 1;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
webView.getSettings().setMediaPlaybackRequiresUserGesture(requireUserGesture);
}
}
private void registerJavaScriptChannelNames(List<String> channelNames) {
for (String channelName : channelNames) {
webView.addJavascriptInterface(
new JavaScriptChannel(methodChannel, channelName, platformThreadHandler), channelName);
}
}
private void updateUserAgent(String userAgent) {
webView.getSettings().setUserAgentString(userAgent);
}
@Override
public void dispose() {
methodChannel.setMethodCallHandler(null);
webView.dispose();
webView.destroy();
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugins.webviewflutter;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.os.Build;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.RequiresApi;
import androidx.webkit.WebResourceErrorCompat;
import androidx.webkit.WebViewClientCompat;
import io.flutter.plugin.common.MethodChannel;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
// We need to use WebViewClientCompat to get
// shouldOverrideUrlLoading(WebView view, WebResourceRequest request)
// invoked by the webview on older Android devices, without it pages that use iframes will
// be broken when a navigationDelegate is set on Android version earlier than N.
class FlutterWebViewClient {
private static final String TAG = "FlutterWebViewClient";
private final MethodChannel methodChannel;
private boolean hasNavigationDelegate;
FlutterWebViewClient(MethodChannel methodChannel) {
this.methodChannel = methodChannel;
}
private static String errorCodeToString(int errorCode) {
switch (errorCode) {
case WebViewClient.ERROR_AUTHENTICATION:
return "authentication";
case WebViewClient.ERROR_BAD_URL:
return "badUrl";
case WebViewClient.ERROR_CONNECT:
return "connect";
case WebViewClient.ERROR_FAILED_SSL_HANDSHAKE:
return "failedSslHandshake";
case WebViewClient.ERROR_FILE:
return "file";
case WebViewClient.ERROR_FILE_NOT_FOUND:
return "fileNotFound";
case WebViewClient.ERROR_HOST_LOOKUP:
return "hostLookup";
case WebViewClient.ERROR_IO:
return "io";
case WebViewClient.ERROR_PROXY_AUTHENTICATION:
return "proxyAuthentication";
case WebViewClient.ERROR_REDIRECT_LOOP:
return "redirectLoop";
case WebViewClient.ERROR_TIMEOUT:
return "timeout";
case WebViewClient.ERROR_TOO_MANY_REQUESTS:
return "tooManyRequests";
case WebViewClient.ERROR_UNKNOWN:
return "unknown";
case WebViewClient.ERROR_UNSAFE_RESOURCE:
return "unsafeResource";
case WebViewClient.ERROR_UNSUPPORTED_AUTH_SCHEME:
return "unsupportedAuthScheme";
case WebViewClient.ERROR_UNSUPPORTED_SCHEME:
return "unsupportedScheme";
}
final String message =
String.format(Locale.getDefault(), "Could not find a string for errorCode: %d", errorCode);
throw new IllegalArgumentException(message);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
if (!hasNavigationDelegate) {
return false;
}
notifyOnNavigationRequest(
request.getUrl().toString(), request.getRequestHeaders(), view, request.isForMainFrame());
// We must make a synchronous decision here whether to allow the navigation or not,
// if the Dart code has set a navigation delegate we want that delegate to decide whether
// to navigate or not, and as we cannot get a response from the Dart delegate synchronously we
// return true here to block the navigation, if the Dart delegate decides to allow the
// navigation the plugin will later make an addition loadUrl call for this url.
//
// Since we cannot call loadUrl for a subframe, we currently only allow the delegate to stop
// navigations that target the main frame, if the request is not for the main frame
// we just return false to allow the navigation.
//
// For more details see: https://github.com/flutter/flutter/issues/25329#issuecomment-464863209
return request.isForMainFrame();
}
boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!hasNavigationDelegate) {
return false;
}
// This version of shouldOverrideUrlLoading is only invoked by the webview on devices with
// webview versions earlier than 67(it is also invoked when hasNavigationDelegate is false).
// On these devices we cannot tell whether the navigation is targeted to the main frame or not.
// We proceed assuming that the navigation is targeted to the main frame. If the page had any
// frames they will be loaded in the main frame instead.
Log.w(
TAG,
"Using a navigationDelegate with an old webview implementation, pages with frames or iframes will not work");
notifyOnNavigationRequest(url, null, view, true);
return true;
}
private void onPageStarted(WebView view, String url) {
Map<String, Object> args = new HashMap<>();
args.put("url", url);
methodChannel.invokeMethod("onPageStarted", args);
}
private void onPageFinished(WebView view, String url) {
Map<String, Object> args = new HashMap<>();
args.put("url", url);
methodChannel.invokeMethod("onPageFinished", args);
}
private void onWebResourceError(
final int errorCode, final String description, final String failingUrl) {
final Map<String, Object> args = new HashMap<>();
args.put("errorCode", errorCode);
args.put("description", description);
args.put("errorType", FlutterWebViewClient.errorCodeToString(errorCode));
args.put("failingUrl", failingUrl);
methodChannel.invokeMethod("onWebResourceError", args);
}
private void notifyOnNavigationRequest(
String url, Map<String, String> headers, WebView webview, boolean isMainFrame) {
HashMap<String, Object> args = new HashMap<>();
args.put("url", url);
args.put("isForMainFrame", isMainFrame);
if (isMainFrame) {
methodChannel.invokeMethod(
"navigationRequest", args, new OnNavigationRequestResult(url, headers, webview));
} else {
methodChannel.invokeMethod("navigationRequest", args);
}
}
// This method attempts to avoid using WebViewClientCompat due to bug
// https://bugs.chromium.org/p/chromium/issues/detail?id=925887. Also, see
// https://github.com/flutter/flutter/issues/29446.
WebViewClient createWebViewClient(boolean hasNavigationDelegate) {
this.hasNavigationDelegate = hasNavigationDelegate;
if (!hasNavigationDelegate || android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return internalCreateWebViewClient();
}
return internalCreateWebViewClientCompat();
}
private WebViewClient internalCreateWebViewClient() {
return new WebViewClient() {
@TargetApi(Build.VERSION_CODES.N)
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
FlutterWebViewClient.this.onPageStarted(view, url);
}
@Override
public void onPageFinished(WebView view, String url) {
FlutterWebViewClient.this.onPageFinished(view, url);
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void onReceivedError(
WebView view, WebResourceRequest request, WebResourceError error) {
FlutterWebViewClient.this.onWebResourceError(
error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString());
}
@Override
public void onReceivedError(
WebView view, int errorCode, String description, String failingUrl) {
FlutterWebViewClient.this.onWebResourceError(errorCode, description, failingUrl);
}
@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
// Deliberately empty. Occasionally the webview will mark events as having failed to be
// handled even though they were handled. We don't want to propagate those as they're not
// truly lost.
}
};
}
private WebViewClientCompat internalCreateWebViewClientCompat() {
return new WebViewClientCompat() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, url);
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
FlutterWebViewClient.this.onPageStarted(view, url);
}
@Override
public void onPageFinished(WebView view, String url) {
FlutterWebViewClient.this.onPageFinished(view, url);
}
// This method is only called when the WebViewFeature.RECEIVE_WEB_RESOURCE_ERROR feature is
// enabled. The deprecated method is called when a device doesn't support this.
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@SuppressLint("RequiresFeature")
@Override
public void onReceivedError(
WebView view, WebResourceRequest request, WebResourceErrorCompat error) {
FlutterWebViewClient.this.onWebResourceError(
error.getErrorCode(), error.getDescription().toString(), request.getUrl().toString());
}
@Override
public void onReceivedError(
WebView view, int errorCode, String description, String failingUrl) {
FlutterWebViewClient.this.onWebResourceError(errorCode, description, failingUrl);
}
@Override
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
// Deliberately empty. Occasionally the webview will mark events as having failed to be
// handled even though they were handled. We don't want to propagate those as they're not
// truly lost.
}
};
}
private static class OnNavigationRequestResult implements MethodChannel.Result {
private final String url;
private final Map<String, String> headers;
private final WebView webView;
private OnNavigationRequestResult(String url, Map<String, String> headers, WebView webView) {
this.url = url;
this.headers = headers;
this.webView = webView;
}
@Override
public void success(Object shouldLoad) {
Boolean typedShouldLoad = (Boolean) shouldLoad;
if (typedShouldLoad) {
loadUrl();
}
}
@Override
public void error(String errorCode, String s1, Object o) {
throw new IllegalStateException("navigationRequest calls must succeed");
}
@Override
public void notImplemented() {
throw new IllegalStateException(
"navigationRequest must be implemented by the webview method channel");
}
private void loadUrl() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.loadUrl(url, headers);
} else {
webView.loadUrl(url);
}
}
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugins.webviewflutter;
import static android.content.Context.INPUT_METHOD_SERVICE;
import android.content.Context;
import android.graphics.Rect;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.webkit.WebView;
import android.widget.ListPopupWindow;
/**
* A WebView subclass that mirrors the same implementation hacks that the system WebView does in
* order to correctly create an InputConnection.
*
* <p>These hacks are only needed in Android versions below N and exist to create an InputConnection
* on the WebView's dedicated input, or IME, thread. The majority of this proxying logic is in
* {@link #checkInputConnectionProxy}.
*
* <p>See also {@link ThreadedInputConnectionProxyAdapterView}.
*/
final class InputAwareWebView extends WebView {
private static final String TAG = "InputAwareWebView";
private View threadedInputConnectionProxyView;
private ThreadedInputConnectionProxyAdapterView proxyAdapterView;
private View containerView;
InputAwareWebView(Context context, View containerView) {
super(context);
this.containerView = containerView;
}
void setContainerView(View containerView) {
this.containerView = containerView;
if (proxyAdapterView == null) {
return;
}
Log.w(TAG, "The containerView has changed while the proxyAdapterView exists.");
if (containerView != null) {
setInputConnectionTarget(proxyAdapterView);
}
}
/**
* Set our proxy adapter view to use its cached input connection instead of creating new ones.
*
* <p>This is used to avoid losing our input connection when the virtual display is resized.
*/
void lockInputConnection() {
if (proxyAdapterView == null) {
return;
}
proxyAdapterView.setLocked(true);
}
/** Sets the proxy adapter view back to its default behavior. */
void unlockInputConnection() {
if (proxyAdapterView == null) {
return;
}
proxyAdapterView.setLocked(false);
}
/** Restore the original InputConnection, if needed. */
void dispose() {
resetInputConnection();
}
/**
* Creates an InputConnection from the IME thread when needed.
*
* <p>We only need to create a {@link ThreadedInputConnectionProxyAdapterView} and create an
* InputConnectionProxy on the IME thread when WebView is doing the same thing. So we rely on the
* system calling this method for WebView's proxy view in order to know when we need to create our
* own.
*
* <p>This method would normally be called for any View that used the InputMethodManager. We rely
* on flutter/engine filtering the calls we receive down to the ones in our hierarchy and the
* system WebView in order to know whether or not the system WebView expects an InputConnection on
* the IME thread.
*/
@Override
public boolean checkInputConnectionProxy(final View view) {
// Check to see if the view param is WebView's ThreadedInputConnectionProxyView.
View previousProxy = threadedInputConnectionProxyView;
threadedInputConnectionProxyView = view;
if (previousProxy == view) {
// This isn't a new ThreadedInputConnectionProxyView. Ignore it.
return super.checkInputConnectionProxy(view);
}
if (containerView == null) {
Log.e(
TAG,
"Can't create a proxy view because there's no container view. Text input may not work.");
return super.checkInputConnectionProxy(view);
}
// We've never seen this before, so we make the assumption that this is WebView's
// ThreadedInputConnectionProxyView. We are making the assumption that the only view that could
// possibly be interacting with the IMM here is WebView's ThreadedInputConnectionProxyView.
proxyAdapterView =
new ThreadedInputConnectionProxyAdapterView(
/*containerView=*/ containerView,
/*targetView=*/ view,
/*imeHandler=*/ view.getHandler());
setInputConnectionTarget(/*targetView=*/ proxyAdapterView);
return super.checkInputConnectionProxy(view);
}
/**
* Ensure that input creation happens back on {@link #containerView}'s thread once this view no
* longer has focus.
*
* <p>The logic in {@link #checkInputConnectionProxy} forces input creation to happen on Webview's
* thread for all connections. We undo it here so users will be able to go back to typing in
* Flutter UIs as expected.
*/
@Override
public void clearFocus() {
super.clearFocus();
resetInputConnection();
}
/**
* Ensure that input creation happens back on {@link #containerView}.
*
* <p>The logic in {@link #checkInputConnectionProxy} forces input creation to happen on Webview's
* thread for all connections. We undo it here so users will be able to go back to typing in
* Flutter UIs as expected.
*/
private void resetInputConnection() {
if (proxyAdapterView == null) {
// No need to reset the InputConnection to the default thread if we've never changed it.
return;
}
if (containerView == null) {
Log.e(TAG, "Can't reset the input connection to the container view because there is none.");
return;
}
setInputConnectionTarget(/*targetView=*/ containerView);
}
/**
* This is the crucial trick that gets the InputConnection creation to happen on the correct
* thread pre Android N.
* https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionFactory.java?l=169&rcl=f0698ee3e4483fad5b0c34159276f71cfaf81f3a
*
* <p>{@code targetView} should have a {@link View#getHandler} method with the thread that future
* InputConnections should be created on.
*/
private void setInputConnectionTarget(final View targetView) {
if (containerView == null) {
Log.e(
TAG,
"Can't set the input connection target because there is no containerView to use as a handler.");
return;
}
targetView.requestFocus();
containerView.post(
new Runnable() {
@Override
public void run() {
InputMethodManager imm =
(InputMethodManager) getContext().getSystemService(INPUT_METHOD_SERVICE);
// This is a hack to make InputMethodManager believe that the target view now has focus.
// As a result, InputMethodManager will think that targetView is focused, and will call
// getHandler() of the view when creating input connection.
// Step 1: Set targetView as InputMethodManager#mNextServedView. This does not affect
// the real window focus.
targetView.onWindowFocusChanged(true);
// Step 2: Have InputMethodManager focus in on targetView. As a result, IMM will call
// onCreateInputConnection() on targetView on the same thread as
// targetView.getHandler(). It will also call subsequent InputConnection methods on this
// thread. This is the IME thread in cases where targetView is our proxyAdapterView.
imm.isActive(containerView);
}
});
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
// This works around a crash when old (<67.0.3367.0) Chromium versions are used.
// Prior to Chromium 67.0.3367 the following sequence happens when a select drop down is shown
// on tablets:
//
// - WebView is calling ListPopupWindow#show
// - buildDropDown is invoked, which sets mDropDownList to a DropDownListView.
// - showAsDropDown is invoked - resulting in mDropDownList being added to the window and is
// also synchronously performing the following sequence:
// - WebView's focus change listener is loosing focus (as mDropDownList got it)
// - WebView is hiding all popups (as it lost focus)
// - WebView's SelectPopupDropDown#hide is invoked.
// - DropDownPopupWindow#dismiss is invoked setting mDropDownList to null.
// - mDropDownList#setSelection is invoked and is throwing a NullPointerException (as we just set mDropDownList to null).
//
// To workaround this, we drop the problematic focus lost call.
// See more details on: https://github.com/flutter/flutter/issues/54164
//
// We don't do this after Android P as it shipped with a new enough WebView version, and it's
// better to not do this on all future Android versions in case DropDownListView's code changes.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P
&& isCalledFromListPopupWindowShow()
&& !focused) {
return;
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
private boolean isCalledFromListPopupWindowShow() {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
for (StackTraceElement stackTraceElement : stackTraceElements) {
if (stackTraceElement.getClassName().equals(ListPopupWindow.class.getCanonicalName())
&& stackTraceElement.getMethodName().equals("show")) {
return true;
}
}
return false;
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugins.webviewflutter;
import android.os.Handler;
import android.os.Looper;
import android.webkit.JavascriptInterface;
import io.flutter.plugin.common.MethodChannel;
import java.util.HashMap;
/**
* Added as a JavaScript interface to the WebView for any JavaScript channel that the Dart code sets
* up.
*
* <p>Exposes a single method named `postMessage` to JavaScript, which sends a message over a method
* channel to the Dart code.
*/
class JavaScriptChannel {
private final MethodChannel methodChannel;
private final String javaScriptChannelName;
private final Handler platformThreadHandler;
/**
* @param methodChannel the Flutter WebView method channel to which JS messages are sent
* @param javaScriptChannelName the name of the JavaScript channel, this is sent over the method
* channel with each message to let the Dart code know which JavaScript channel the message
* was sent through
*/
JavaScriptChannel(
MethodChannel methodChannel, String javaScriptChannelName, Handler platformThreadHandler) {
this.methodChannel = methodChannel;
this.javaScriptChannelName = javaScriptChannelName;
this.platformThreadHandler = platformThreadHandler;
}
// Suppressing unused warning as this is invoked from JavaScript.
@SuppressWarnings("unused")
@JavascriptInterface
public void postMessage(final String message) {
Runnable postMessageRunnable =
new Runnable() {
@Override
public void run() {
HashMap<String, String> arguments = new HashMap<>();
arguments.put("channel", javaScriptChannelName);
arguments.put("message", message);
methodChannel.invokeMethod("javascriptChannelMessage", arguments);
}
};
if (platformThreadHandler.getLooper() == Looper.myLooper()) {
postMessageRunnable.run();
} else {
platformThreadHandler.post(postMessageRunnable);
}
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugins.webviewflutter;
import android.os.Handler;
import android.os.IBinder;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
/**
* A fake View only exposed to InputMethodManager.
*
* <p>This follows a similar flow to Chromium's WebView (see
* https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnectionProxyView.java).
* WebView itself bounces its InputConnection around several different threads. We follow its logic
* here to get the same working connection.
*
* <p>This exists solely to forward input creation to WebView's ThreadedInputConnectionProxyView on
* the IME thread. The way that this is created in {@link
* InputAwareWebView#checkInputConnectionProxy} guarantees that we have a handle to
* ThreadedInputConnectionProxyView and {@link #onCreateInputConnection} is always called on the IME
* thread. We delegate to ThreadedInputConnectionProxyView there to get WebView's input connection.
*/
final class ThreadedInputConnectionProxyAdapterView extends View {
final Handler imeHandler;
final IBinder windowToken;
final View containerView;
final View rootView;
final View targetView;
private boolean triggerDelayed = true;
private boolean isLocked = false;
private InputConnection cachedConnection;
ThreadedInputConnectionProxyAdapterView(View containerView, View targetView, Handler imeHandler) {
super(containerView.getContext());
this.imeHandler = imeHandler;
this.containerView = containerView;
this.targetView = targetView;
windowToken = containerView.getWindowToken();
rootView = containerView.getRootView();
setFocusable(true);
setFocusableInTouchMode(true);
setVisibility(VISIBLE);
}
/** Returns whether or not this is currently asynchronously acquiring an input connection. */
boolean isTriggerDelayed() {
return triggerDelayed;
}
/** Sets whether or not this should use its previously cached input connection. */
void setLocked(boolean locked) {
isLocked = locked;
}
/**
* This is expected to be called on the IME thread. See the setup required for this in {@link
* InputAwareWebView#checkInputConnectionProxy(View)}.
*
* <p>Delegates to ThreadedInputConnectionProxyView to get WebView's input connection.
*/
@Override
public InputConnection onCreateInputConnection(final EditorInfo outAttrs) {
triggerDelayed = false;
InputConnection inputConnection =
(isLocked) ? cachedConnection : targetView.onCreateInputConnection(outAttrs);
triggerDelayed = true;
cachedConnection = inputConnection;
return inputConnection;
}
@Override
public boolean checkInputConnectionProxy(View view) {
return true;
}
@Override
public boolean hasWindowFocus() {
// None of our views here correctly report they have window focus because of how we're embedding
// the platform view inside of a virtual display.
return true;
}
@Override
public View getRootView() {
return rootView;
}
@Override
public boolean onCheckIsTextEditor() {
return true;
}
@Override
public boolean isFocused() {
return true;
}
@Override
public IBinder getWindowToken() {
return windowToken;
}
@Override
public Handler getHandler() {
return imeHandler;
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugins.webviewflutter;
import android.content.Context;
import android.view.View;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;
import java.util.Map;
public final class WebViewFactory extends PlatformViewFactory {
private final BinaryMessenger messenger;
private final View containerView;
private FlutterWebView flutterWebView;
WebViewFactory(BinaryMessenger messenger, View containerView) {
super(StandardMessageCodec.INSTANCE);
this.messenger = messenger;
this.containerView = containerView;
}
@SuppressWarnings("unchecked")
@Override
public PlatformView create(Context context, int id, Object args) {
Map<String, Object> params = (Map<String, Object>) args;
return new FlutterWebView(context, messenger, id, params, containerView);
}
public FlutterWebView getFlutterWebView(){
return flutterWebView;
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugins.webviewflutter;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
import io.flutter.plugin.common.PluginRegistry;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener;
/**
* Java platform implementation of the webview_flutter plugin.
*
* <p>Register this in an add to app scenario to gracefully handle activity and context changes.
*
* <p>Call {@link #registerWith(Registrar)} to use the stable {@code io.flutter.plugin.common}
* package instead.
*/
public class WebViewFlutterPlugin implements FlutterPlugin, PluginRegistry.ActivityResultListener, ActivityAware {
private static final String TAG = "WebViewFlutterPlugin";
private FlutterCookieManager flutterCookieManager;
public static Activity activity;
private WebViewFactory factory;
/**
* Add an instance of this to {@link io.flutter.embedding.engine.plugins.PluginRegistry} to
* register it.
*
* <p>THIS PLUGIN CODE PATH DEPENDS ON A NEWER VERSION OF FLUTTER THAN THE ONE DEFINED IN THE
* PUBSPEC.YAML. Text input will fail on some Android devices unless this is used with at least
* flutter/flutter@1d4d63ace1f801a022ea9ec737bf8c15395588b9. Use the V1 embedding with {@link
* #registerWith(Registrar)} to use this plugin with older Flutter versions.
*
* <p>Registration should eventually be handled automatically by v2 of the
* GeneratedPluginRegistrant. https://github.com/flutter/flutter/issues/42694
*/
public WebViewFlutterPlugin() {
// Log.v(TAG,"WebViewFlutterPlugin");
}
/**
* Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common}
* package.
*
* <p>Calling this automatically initializes the plugin. However plugins initialized this way
* won't react to changes in activity or context, unlike {@link CameraPlugin}.
*/
@SuppressWarnings("deprecation")
public static void registerWith(Registrar registrar) {
// Log.v(TAG,"registerWith");
registrar
.platformViewRegistry()
.registerViewFactory(
"plugins.flutter.io/webview",
new WebViewFactory(registrar.messenger(), registrar.view()));
new FlutterCookieManager(registrar.messenger());
}
@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
// Log.v(TAG,"onAttachedToEngine");
BinaryMessenger messenger = binding.getBinaryMessenger();
factory = new WebViewFactory(messenger, null);
binding
.getFlutterEngine()
.getPlatformViewsController()
.getRegistry()
.registerViewFactory(
"plugins.flutter.io/webview", factory);
flutterCookieManager = new FlutterCookieManager(messenger);
Context appContext = binding.getApplicationContext();
if (appContext instanceof FlutterApplication) {
Activity currentActivity = ((FlutterApplication) appContext).getCurrentActivity();
if (currentActivity != null) {
activity = currentActivity;
}
}
}
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
// Log.v(TAG,"onDetachedFromEngine");
if (flutterCookieManager == null) {
return;
}
activity = null;
flutterCookieManager.dispose();
flutterCookieManager = null;
}
@Override
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
// Log.v(TAG,"onActivityResult");
if (factory != null && factory.getFlutterWebView() != null){
return factory.getFlutterWebView().activityResult(requestCode, resultCode, data);
}
return false;
}
@Override
public void onAttachedToActivity(ActivityPluginBinding binding) {
// Log.v(TAG,"onAttachedToActivity");
activity = binding.getActivity();
binding.addActivityResultListener(this);
}
@Override
public void onDetachedFromActivityForConfigChanges() {
// Log.v(TAG,"onDetachedFromActivityForConfigChanges");
}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
// Log.v(TAG,"onReattachedToActivityForConfigChanges");
}
@Override
public void onDetachedFromActivity() {
// Log.v(TAG,"onDetachedFromActivity");
}
}
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 1e5cb2d87f8542f9fbbd0f22d528823274be0acb
channel: master
# webview_flutter_example
Demonstrates how to use the webview_flutter plugin.
## Getting Started
For help getting started with Flutter, view our online
[documentation](https://flutter.dev/).
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "io.flutter.plugins.webviewflutterexample"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
package io.flutter.plugins.webviewflutterexample;
import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.integration_test.FlutterTestRunner;
import org.junit.Rule;
import org.junit.runner.RunWith;
@RunWith(FlutterTestRunner.class)
@SuppressWarnings("deprecation")
public class EmbeddingV1ActivityTest {
@Rule
public ActivityTestRule<EmbeddingV1Activity> rule =
new ActivityTestRule<>(EmbeddingV1Activity.class);
}
package io.flutter.plugins.webviewflutterexample;
import androidx.test.rule.ActivityTestRule;
import dev.flutter.plugins.integration_test.FlutterTestRunner;
import io.flutter.embedding.android.FlutterActivity;
import org.junit.Rule;
import org.junit.runner.RunWith;
@RunWith(FlutterTestRunner.class)
public class MainActivityTest {
@Rule
public ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(FlutterActivity.class);
}