1. مقدمة
تاريخ آخر تعديل: 19/10/2021
باستخدام مكوّن WebView Flutter الإضافي، يمكنك إضافة تطبيق WebView المصغّر إلى تطبيق Flutter لنظام التشغيل Android أو iOS. على نظام التشغيل iOS، يكون تطبيق WebView مدعومًا بـ WKWebView، في حين يتم دعم تطبيق WebView على نظام Android بواسطة WebView. يمكن أن يعرض المكوّن الإضافي تطبيقات Flutter المصغّرة على عرض الويب. لذلك من الممكن على سبيل المثال عرض قائمة منسدلة على طريقة عرض الويب.
ما الذي ستقوم ببنائه
في هذا الدرس التطبيقي حول الترميز، ستنشئ تطبيقًا للأجهزة الجوّالة خطوة بخطوة لإبراز مكوّن WebView باستخدام حزمة تطوير البرامج (SDK) Flutter. سينفّذ تطبيقك ما يلي:
- عرض محتوى الويب في
WebView
- عرض تطبيقات Flutter المصغّرة مجمّعة على
WebView
- التفاعل مع أحداث تقدّم تحميل الصفحة
- التحكّم في
WebView
من خلالWebViewController
- حظر المواقع الإلكترونية باستخدام
NavigationDelegate
- تقييم تعبيرات JavaScript
- التعامل مع عمليات معاودة الاتصال من JavaScript باستخدام
JavascriptChannels
- ضبط ملفات تعريف الارتباط أو إزالتها أو إضافتها أو عرضها
- تحميل ملف HTML وعرضه من مواد العرض أو الملفات أو السلاسل التي تحتوي على HTML
المعلومات التي ستطّلع عليها
في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية استخدام مكوّن webview_flutter
الإضافي بعدة طرق، بما في ذلك:
- كيفية إعداد المكوّن الإضافي
webview_flutter
- كيفية الاستماع إلى أحداث تقدّم تحميل الصفحة
- كيفية التحكّم في التنقّل في الصفحات
- كيفية توجيه جهاز "
WebView
" للانتقال إلى الخلف وإلى الأمام خلال السجلّ - كيفية تقييم JavaScript، بما في ذلك استخدام النتائج التي تم عرضها
- كيفية تسجيل عمليات الاستدعاء لاستدعاء رمز Dart من JavaScript
- كيفية إدارة ملفات تعريف الارتباط
- كيفية تحميل وعرض صفحات HTML من مواد عرض أو ملفات أو سلسلة تحتوي على HTML
المتطلبات
- الإصدار 4.1 من "استوديو Android" أو إصدار أحدث (لتطوير تطبيقات Android)
- Xcode 12 أو إصدار أحدث (لتطوير iOS)
- Flutter SDK
- أداة تعديل الرموز، مثل Android Studio أو Visual Studio Code أو Emacs
2. إعداد بيئة تطوير Flutter
لإكمال هذا التمرين، تحتاج إلى برنامجَين، وهما Flutter SDK ومحرِّر.
يمكنك تشغيل الدرس التطبيقي حول الترميز باستخدام أي من الأجهزة التالية:
- جهاز Android أو iOS فعلي متصل بجهاز الكمبيوتر وتم ضبطه على "وضع مطور البرامج".
- محاكي iOS (يتطلب تثبيت أدوات Xcode).
- محاكي Android (يتطلب عملية إعداد في "استوديو Android").
3- الخطوات الأولى
بدء استخدام Flutter
تتوفّر طرق متعددة لإنشاء مشروع جديد على Flutter، من خلال كل من Android Studio وVisual Studio Code يوفران الأدوات اللازمة لهذه المهمة. يمكنك إما اتّباع الإجراءات المرتبطة لإنشاء مشروع، أو تنفيذ الأوامر التالية في وحدة طرفية سهلة الاستخدام لسطر الأوامر.
$ flutter create --platforms=android,ios webview_in_flutter Creating project webview_in_flutter... Resolving dependencies in `webview_in_flutter`... Downloading packages... Got dependencies in `webview_in_flutter`. Wrote 74 files. All done! You can find general documentation for Flutter at: https://docs.flutter.dev/ Detailed API documentation is available at: https://api.flutter.dev/ If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev In order to run your application, type: $ cd webview_in_flutter $ flutter run Your application code is in webview_in_flutter/lib/main.dart.
إضافة مكوّن WebView Flutter الإضافي كعنصر تابع
من السهل إضافة المزيد من الميزات إلى تطبيق Flutter باستخدام حِزم Pub. في هذا الدرس التطبيقي حول الترميز، ستضيف المكوّن الإضافي webview_flutter
إلى مشروعك. شغِّل الأوامر التالية في الوحدة الطرفية.
$ cd webview_in_flutter $ flutter pub add webview_flutter Resolving dependencies... Downloading packages... leak_tracker 10.0.4 (10.0.5 available) leak_tracker_flutter_testing 3.0.3 (3.0.5 available) material_color_utilities 0.8.0 (0.11.1 available) meta 1.12.0 (1.14.0 available) + plugin_platform_interface 2.1.8 test_api 0.7.0 (0.7.1 available) + webview_flutter 4.7.0 + webview_flutter_android 3.16.0 + webview_flutter_platform_interface 2.10.0 + webview_flutter_wkwebview 3.13.0 Changed 5 dependencies! 5 packages have newer versions incompatible with dependency constraints. Try `flutter pub outdated` for more information.
في حال فحص pubspec.yaml، سيظهر لك سطر في قسم الملحقات للمكون الإضافي webview_flutter
.
ضبط حزمة minSDK لنظام التشغيل Android
لاستخدام المكوّن الإضافي webview_flutter
على Android، عليك ضبط minSDK
على 20
. عدِّل ملف android/app/build.gradle
على النحو التالي:
android/app/build.gradle
android {
//...
defaultConfig {
applicationId = "com.example.webview_in_flutter"
minSdk = 20 // Modify this line
targetSdk = flutter.targetSdkVersion
versionCode = flutterVersionCode.toInteger()
versionName = flutterVersionName
}
4. إضافة تطبيق WebView المصغّر إلى تطبيق Flutter
في هذه الخطوة، يجب إضافة WebView
إلى تطبيقك. وتكون مكوّنات WebView هي طرق عرض مدمجة مع المحتوى مستضافة، ويمكنك بصفتك مطوّر تطبيقات اختيار كيفية استضافة طرق العرض الأصلية هذه في تطبيقك. على أجهزة Android، يمكنك الاختيار بين الشاشات الافتراضية، التي تكون حاليًا الشاشة التلقائية لنظام Android، والإعداد المختلط. ومع ذلك، يستخدم نظام التشغيل iOS دائمًا طريقة التركيب المختلط.
للحصول على مناقشة معمّقة للاختلافات بين "الشاشات الافتراضية" و"الإنشاء المختلَط"، يُرجى الاطّلاع على المستندات حول استضافة طرق عرض Android وiOS الأصلية في تطبيق Flutter باستخدام طرق عرض النظام الأساسي.
عرض Webview على الشاشة
استبدِل محتوى lib/main.dart
بما يلي:
lib/main.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..loadRequest(
Uri.parse('https://flutter.dev'),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView'),
),
body: WebViewWidget(
controller: controller,
),
);
}
}
يؤدي تشغيل هذا على iOS أو Android إلى عرض WebView كنافذة متصفح لا تظهر بالكامل على جهازك، وهو ما يعني أن المتصفح يظهر على جهازك في وضع ملء الشاشة بدون أي شكل من أشكال الحدود أو الهامش. أثناء التمرير، ستلاحظ أجزاءً من الصفحة قد تبدو غريبة بعض الشيء. ويرجع ذلك إلى أنّ لغة JavaScript غير مفعّلة حاليًا، وبالتالي فإنّ عرض flutter.dev يتطلّب JavaScript.
تشغيل التطبيق
يمكنك تشغيل تطبيق Flutter على أجهزة iOS أو Android للاطّلاع على مكوّن Webview الذي يعرض موقع flutter.dev الإلكتروني. ويمكنك بدلاً من ذلك تشغيل التطبيق باستخدام محاكي Android أو محاكي iOS. لا تتردد في استبدال عنوان URL الأولي لـ WebView بموقعك الإلكتروني على سبيل المثال.
$ flutter run
على افتراض تشغيل المحاكي أو المحاكي المناسب، أو توصيل جهاز فعلي، وبعد تجميع التطبيق ونشره على جهازك، يُفترض أن يظهر لك شيء مثل التالي:
5- الاستماع إلى أحداث تحميل الصفحات
يوفّر التطبيق المصغّر WebView
العديد من أحداث تقدّم تحميل الصفحات التي يمكن لتطبيقك الاستماع إليها. أثناء دورة تحميل الصفحة WebView
، هناك ثلاثة أحداث مختلفة لتحميل الصفحة يتم تنشيطها: onPageStarted
وonProgress
وonPageFinished
. في هذه الخطوة، ستقوم بتنفيذ مؤشر تحميل الصفحة. بالإضافة إلى ذلك، سيُظهر هذا الإجراء أنّه يمكنك عرض محتوى Flutter في مساحة المحتوى الخاصة بـ WebView
.
إضافة أحداث تحميل الصفحات إلى تطبيقك
أنشئ ملف مصدر جديدًا على lib/src/web_view_stack.dart
واملأه بالمحتوى التالي:
lib/src/web_view_stack.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewStack extends StatefulWidget {
const WebViewStack({super.key});
@override
State<WebViewStack> createState() => _WebViewStackState();
}
class _WebViewStackState extends State<WebViewStack> {
var loadingPercentage = 0;
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..setNavigationDelegate(NavigationDelegate(
onPageStarted: (url) {
setState(() {
loadingPercentage = 0;
});
},
onProgress: (progress) {
setState(() {
loadingPercentage = progress;
});
},
onPageFinished: (url) {
setState(() {
loadingPercentage = 100;
});
},
))
..loadRequest(
Uri.parse('https://flutter.dev'),
);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: controller,
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
انتهى هذا الرمز من استخدام التطبيق المصغّر WebView
مع Stack
، ما يؤدي إلى تركيب WebView
بشكل مشروط مع LinearProgressIndicator
عندما تكون النسبة المئوية لتحميل الصفحة أقل من %100. بما أنّ ذلك يشمل حالة البرنامج التي تتغيّر بمرور الوقت، لقد خزّنت هذه الحالة في فئة State
مرتبطة بالسمة StatefulWidget
.
للاستفادة من تطبيق WebViewStack
المصغّر الجديد، عدِّل lib/main.dart كما يلي:
lib/main.dart
import 'package:flutter/material.dart';
import 'src/web_view_stack.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView'),
),
body: const WebViewStack(),
);
}
}
عند تشغيل التطبيق، واستنادًا إلى حالة الشبكة وما إذا كان المتصفّح قد خزّن الصفحة التي تنتقل إليها مؤقتًا أم لا، سيظهر لك مؤشر تحميل الصفحة متراكبًا فوق منطقة محتوى WebView
.
6- استخدام WebViewController
الوصول إلى WebViewController من تطبيق WebView المصغّر
يتيح تطبيق "WebView
" المصغّر إمكانية التحكّم الآلي من خلال WebViewController
. تصبح وحدة التحكّم هذه متاحة بعد إنشاء تطبيق "WebView
" المصغّر من خلال معاودة الاتصال. الطبيعة غير المتزامنة لمدى توفُّر وحدة التحكّم هذه تجعلها مرشحة رئيسيًا لفئة Completer<T>
غير المتزامنة في Dart.
عدِّل "lib/src/web_view_stack.dart
" على النحو التالي:
lib/src/web_view_stack.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewStack extends StatefulWidget {
const WebViewStack({required this.controller, super.key}); // MODIFY
final WebViewController controller; // ADD
@override
State<WebViewStack> createState() => _WebViewStackState();
}
class _WebViewStackState extends State<WebViewStack> {
var loadingPercentage = 0;
// REMOVE the controller that was here
@override
void initState() {
super.initState();
// Modify from here...
widget.controller.setNavigationDelegate(
NavigationDelegate(
onPageStarted: (url) {
setState(() {
loadingPercentage = 0;
});
},
onProgress: (progress) {
setState(() {
loadingPercentage = progress;
});
},
onPageFinished: (url) {
setState(() {
loadingPercentage = 100;
});
},
),
);
// ...to here.
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: widget.controller, // MODIFY
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
يستخدم التطبيق المصغّر "WebViewStack
" الآن وحدة تحكّم تم إنشاؤها في التطبيق المصغّر المحيط. سيتيح ذلك مشاركة وحدة التحكّم في "WebViewWidget
" مع الأجزاء الأخرى من التطبيق بسهولة.
صياغة عناصر التحكّم في التنقّل
وعلى الرغم من أنّ استخدام WebView
عامل مهم، إلا أنّ القدرة على الانتقال للأمام والخلف عبر سجلّ الصفحة وإعادة تحميل الصفحة ستكون مفيدة. ولكن لحسن الحظ، يمكنك إضافة هذه الوظيفة إلى تطبيقك باستخدام WebViewController
.
أنشئ ملف مصدر جديدًا على lib/src/navigation_controls.dart
واملأه بما يلي:
lib/src/navigation_controls.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class NavigationControls extends StatelessWidget {
const NavigationControls({required this.controller, super.key});
final WebViewController controller;
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () async {
final messenger = ScaffoldMessenger.of(context);
if (await controller.canGoBack()) {
await controller.goBack();
} else {
messenger.showSnackBar(
const SnackBar(content: Text('No back history item')),
);
return;
}
},
),
IconButton(
icon: const Icon(Icons.arrow_forward_ios),
onPressed: () async {
final messenger = ScaffoldMessenger.of(context);
if (await controller.canGoForward()) {
await controller.goForward();
} else {
messenger.showSnackBar(
const SnackBar(content: Text('No forward history item')),
);
return;
}
},
),
IconButton(
icon: const Icon(Icons.replay),
onPressed: () {
controller.reload();
},
),
],
);
}
}
تستخدم هذه الأداة WebViewController
التي تمت مشاركتها معها في وقت الإنشاء لتمكين المستخدم من التحكّم في WebView
من خلال سلسلة من IconButton
s.
إضافة عناصر التحكّم في التنقّل إلى AppBar
بعد توفّر الإصدار المُحدَّث من "WebViewStack
" و"NavigationControls
" المصمم حديثًا، حان الوقت الآن لتجميع كل ما هو جديد في WebViewApp
الجديد. وهذا هو المكان الذي نُنشئ فيه WebViewController
المشترك. مع اقتراب العلامة WebViewApp
من أعلى شجرة التطبيقات المصغّرة في هذا التطبيق، من المنطقي إنشاءها على هذا المستوى.
عدِّل ملف lib/main.dart
على النحو التالي:
lib/main.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart'; // ADD
import 'src/navigation_controls.dart'; // ADD
import 'src/web_view_stack.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
// Add from here...
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..loadRequest(
Uri.parse('https://flutter.dev'),
);
}
// ...to here.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView'),
// Add from here...
actions: [
NavigationControls(controller: controller),
],
// ...to here.
),
body: WebViewStack(controller: controller), // MODIFY
);
}
}
من المفترض أن يؤدي تشغيل التطبيق إلى إظهار صفحة ويب تتضمن عناصر التحكم:
7. تتبع التنقل باستخدام ميزة Navigationديف
يوفّر WebView
لتطبيقك NavigationDelegate,
الذي يتيح لتطبيقك تتبُّع مسار التنقّل في صفحة تطبيق WebView
المصغّر والتحكّم فيه. عند بدء عملية تنقُّل من خلال WebView,
، مثلاً عندما ينقر المستخدم على رابط، يتم استدعاء NavigationDelegate
. يمكن استخدام معاودة الاتصال NavigationDelegate
للتحكّم في ما إذا كان WebView
سيتابع عملية التنقّل.
تسجيل تفويض التنقل المخصص
في هذه الخطوة، سيتم تسجيل طلب معاودة الاتصال NavigationDelegate
لحظر التنقّل إلى YouTube.com. يُرجى العِلم أنّ هذا التنفيذ المبسّط يحظر أيضًا محتوى YouTube المضمّن، والذي يظهر في صفحات مستندات واجهة برمجة التطبيقات Flutter API.
عدِّل lib/src/web_view_stack.dart
على النحو التالي:
lib/src/web_view_stack.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebViewStack extends StatefulWidget {
const WebViewStack({required this.controller, super.key});
final WebViewController controller;
@override
State<WebViewStack> createState() => _WebViewStackState();
}
class _WebViewStackState extends State<WebViewStack> {
var loadingPercentage = 0;
@override
void initState() {
super.initState();
widget.controller.setNavigationDelegate(
NavigationDelegate(
onPageStarted: (url) {
setState(() {
loadingPercentage = 0;
});
},
onProgress: (progress) {
setState(() {
loadingPercentage = progress;
});
},
onPageFinished: (url) {
setState(() {
loadingPercentage = 100;
});
},
// Add from here...
onNavigationRequest: (navigation) {
final host = Uri.parse(navigation.url).host;
if (host.contains('youtube.com')) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Blocking navigation to $host',
),
),
);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
// ...to here.
),
);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: widget.controller,
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
في الخطوة التالية، عليك إضافة عنصر في القائمة لتفعيل اختبار NavigationDelegate
باستخدام الفئة WebViewController
. ويتم تركها تمرين للقارئ لتعزيز منطق معاودة الاتصال لحظر التنقل بالكامل في الصفحة إلى YouTube.com، مع الاستمرار في السماح بمحتوى YouTube المضمّن في مستندات واجهة برمجة التطبيقات.
8. إضافة زر قائمة إلى AppBar
في الخطوات القليلة التالية، ما عليك سوى إنشاء زر قائمة في تطبيق "AppBar
" المصغّر المستخدَم لتقييم JavaScript واستدعاء قنوات JavaScript وإدارة ملفات تعريف الارتباط. بشكل عام، قائمة مفيدة.
أنشئ ملف مصدر جديدًا على lib/src/menu.dart
واملأه بما يلي:
lib/src/menu.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
enum _MenuOptions {
navigationDelegate,
}
class Menu extends StatelessWidget {
const Menu({required this.controller, super.key});
final WebViewController controller;
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate:
await controller.loadRequest(Uri.parse('https://youtube.com'));
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
],
);
}
}
عندما يحدد المستخدم خيار القائمة الانتقال إلى YouTube، يتم تنفيذ طريقة WebViewController
loadRequest
. سيتم حظر التنقّل هذا من خلال معاودة الاتصال "navigationDelegate
" التي أنشأتها في الخطوة السابقة.
لإضافة القائمة إلى شاشة WebViewApp
، عدِّل lib/main.dart
على النحو التالي:
lib/main.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'src/menu.dart'; // ADD
import 'src/navigation_controls.dart';
import 'src/web_view_stack.dart';
void main() {
runApp(
MaterialApp(
theme: ThemeData(useMaterial3: true),
home: const WebViewApp(),
),
);
}
class WebViewApp extends StatefulWidget {
const WebViewApp({super.key});
@override
State<WebViewApp> createState() => _WebViewAppState();
}
class _WebViewAppState extends State<WebViewApp> {
late final WebViewController controller;
@override
void initState() {
super.initState();
controller = WebViewController()
..loadRequest(
Uri.parse('https://flutter.dev'),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter WebView'),
actions: [
NavigationControls(controller: controller),
Menu(controller: controller), // ADD
],
),
body: WebViewStack(controller: controller),
);
}
}
شغِّل التطبيق وانقر على عنصر القائمة الانتقال إلى YouTube. من المفترض أن تظهر رسالة SnackBar لإعلامك بأنّ وحدة التحكّم في التنقُّل حظرت الانتقال إلى YouTube.
9. تقييم JavaScript
بإمكان WebViewController
تقييم تعبيرات JavaScript في سياق الصفحة الحالية. هناك طريقتان مختلفتان لتقييم JavaScript: لاستخدام رمز JavaScript الذي لا يعرض قيمة، واستخدام runJavaScript
، وبالنسبة إلى رمز JavaScript الذي يعرض قيمة، استخدِم runJavaScriptReturningResult
.
لتفعيل JavaScript، يجب ضبط WebViewController
مع ضبط السمة javaScriptMode
على JavascriptMode.unrestricted
. بشكل تلقائي، يتم ضبط javascriptMode
على JavascriptMode.disabled
.
عدِّل فئة _WebViewStackState
من خلال إضافة الإعداد javascriptMode
على النحو التالي:
lib/src/web_view_stack.dart
class _WebViewStackState extends State<WebViewStack> {
var loadingPercentage = 0;
@override
void initState() {
super.initState();
widget.controller
..setNavigationDelegate( // Modify this line to use .. instead of .
NavigationDelegate(
onPageStarted: (url) {
setState(() {
loadingPercentage = 0;
});
},
onProgress: (progress) {
setState(() {
loadingPercentage = progress;
});
},
onPageFinished: (url) {
setState(() {
loadingPercentage = 100;
});
},
onNavigationRequest: (navigation) {
final host = Uri.parse(navigation.url).host;
if (host.contains('youtube.com')) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Blocking navigation to $host',
),
),
);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
..setJavaScriptMode(JavaScriptMode.unrestricted); // Add this line
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: widget.controller,
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
الآن بعد أن تمكّن WebViewWidget
من تنفيذ JavaScript، يمكنك إضافة خيار إلى القائمة لاستخدام طريقة runJavaScriptReturningResult
.
باستخدام أداة التحرير أو بعض أعمال لوحة المفاتيح، يمكنك تحويل فئة القائمة إلى StatefulWidget. تعديل lib/src/menu.dart
ليتوافق مع ما يلي:
lib/src/menu.dart
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
enum _MenuOptions {
navigationDelegate,
userAgent, // Add this line
}
class Menu extends StatefulWidget { // Convert to StatefulWidget
const Menu({required this.controller, super.key});
final WebViewController controller;
@override // Add from here
State<Menu> createState() => _MenuState();
}
class _MenuState extends State<Menu> { // To here.
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate: // Modify from here
await widget.controller
.loadRequest(Uri.parse('https://youtube.com'));
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
)); // To here.
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
const PopupMenuItem<_MenuOptions>( // Add from here
value: _MenuOptions.userAgent,
child: Text('Show user-agent'),
), // To here.
],
);
}
}
عند النقر على "إظهار وكيل المستخدم" قائمة الخيارات، يتم عرض نتيجة تنفيذ تعبير JavaScript navigator.userAgent
في Snackbar
. عند تشغيل التطبيق، قد تلاحظ أن صفحة Flutter.dev تبدو مختلفة. هذه هي نتيجة التشغيل مع تفعيل JavaScript.
10. العمل مع قنوات JavaScript
يتيح تطبيق "قنوات JavaScript" لتطبيقك تسجيل معالِجات معاودة الاتصال في سياق JavaScript الخاص بـ WebViewWidget
والتي يمكن استدعاؤها لنقل القيم مرة أخرى إلى رمز Dart الخاص بالتطبيق. في هذه الخطوة، ستسجِّل قناة SnackBar
سيتم الاتصال بها على أساس XMLHttpRequest
.
عدِّل فئة WebViewStack
على النحو التالي:
lib/src/web_view_stack.dart
class WebViewStack extends StatefulWidget {
const WebViewStack({required this.controller, super.key});
final WebViewController controller;
@override
State<WebViewStack> createState() => _WebViewStackState();
}
class _WebViewStackState extends State<WebViewStack> {
var loadingPercentage = 0;
@override
void initState() {
super.initState();
widget.controller
..setNavigationDelegate(
NavigationDelegate(
onPageStarted: (url) {
setState(() {
loadingPercentage = 0;
});
},
onProgress: (progress) {
setState(() {
loadingPercentage = progress;
});
},
onPageFinished: (url) {
setState(() {
loadingPercentage = 100;
});
},
onNavigationRequest: (navigation) {
final host = Uri.parse(navigation.url).host;
if (host.contains('youtube.com')) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Blocking navigation to $host',
),
),
);
return NavigationDecision.prevent;
}
return NavigationDecision.navigate;
},
),
)
// Modify from here...
..setJavaScriptMode(JavaScriptMode.unrestricted)
..addJavaScriptChannel(
'SnackBar',
onMessageReceived: (message) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(message.message)));
},
);
// ...to here.
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
WebViewWidget(
controller: widget.controller,
),
if (loadingPercentage < 100)
LinearProgressIndicator(
value: loadingPercentage / 100.0,
),
],
);
}
}
لكل قناة JavaScript في Set
، يتم توفير عنصر قناة في سياق JavaScript على أنّه سمة نافذة تحمل الاسم نفسه لقناة JavaScript name
. يتضمّن استخدام هذا من سياق JavaScript استدعاء postMessage
على قناة JavaScript لإرسال رسالة يتم تمريرها إلى معالج معاودة الاتصال onMessageReceived
المسمى JavascriptChannel
المسمى.
للاستفادة من قناة JavaScript التي تمت إضافتها أعلاه، أضِف عنصر قائمة آخر ينفّذ XMLHttpRequest
في سياق JavaScript ويُعيد النتائج باستخدام قناة JavaScript SnackBar
.
والآن بعد أن تعرّفت WebViewWidget
على قنوات JavaScript,
، ستتم إضافة مثال لتوسيع نطاق التطبيق. لإجراء ذلك، يجب إضافة المزيد من PopupMenuItem
إلى الصف Menu
وإضافة الوظائف الإضافية.
عدِّل _MenuOptions
باستخدام خيار القائمة الإضافية من خلال إضافة قيمة التعداد javascriptChannel
، وأضِف عملية تنفيذ إلى الفئة Menu
على النحو التالي:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel, // Add this option
}
class Menu extends StatefulWidget {
const Menu({required this.controller, super.key});
final WebViewController controller;
@override
State<Menu> createState() => _MenuState();
}
class _MenuState extends State<Menu> {
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate:
await widget.controller
.loadRequest(Uri.parse('https://youtube.com'));
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
case _MenuOptions.javascriptChannel: // Add from here
await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
if (req.status == 200) {
let response = JSON.parse(req.responseText);
SnackBar.postMessage("IP Address: " + response.ip);
} else {
SnackBar.postMessage("Error: " + req.status);
}
}
req.send();'''); // To here.
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.userAgent,
child: Text('Show user-agent'),
),
const PopupMenuItem<_MenuOptions>( // Add from here
value: _MenuOptions.javascriptChannel,
child: Text('Lookup IP Address'),
), // To here.
],
);
}
}
يتم تنفيذ JavaScript هذا عندما يختار المستخدم خيار القائمة مثال على قناة JavaScript.
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
if (req.status == 200) {
SnackBar.postMessage(req.responseText);
} else {
SnackBar.postMessage("Error: " + req.status);
}
}
req.send();
يرسل هذا الرمز طلب GET
إلى واجهة برمجة تطبيقات لعنوان IP العلني، مع عرض عنوان IP للجهاز. تظهر هذه النتيجة في SnackBar
من خلال استدعاء postMessage
على SnackBar
JavascriptChannel
.
11. إدارة ملفات تعريف الارتباط
يمكن لتطبيقك إدارة ملفات تعريف الارتباط في WebView
باستخدام الفئة CookieManager
. في هذه الخطوة، ستظهر قائمة بملفات تعريف الارتباط، وتمحو قائمة ملفات تعريف الارتباط، وتحذف ملفات تعريف الارتباط، وتعيِّن ملفات تعريف ارتباط جديدة. أضِف إدخالات إلى _MenuOptions
لكل حالة من حالات استخدام ملفات تعريف الارتباط على النحو التالي:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
// Add from here ...
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// ... to here.
}
ستركّز بقية التغييرات في هذه الخطوة على الفئة Menu
، بما في ذلك تحويل الفئة Menu
من فئة "عديمة الحالة" إلى حالة "تسجيل هوية المستخدم". هذا التغيير مهمّ لأنّ Menu
يجب أن يمتلك CookieManager
، وتُعدّ الحالة القابلة للتغيّر في التطبيقات المصغّرة التي لا تتضمّن حالة تركيبة سيئة.
أضِف CookieManager إلى فئة الحالة الناتجة على النحو التالي:
lib/src/menu.dart
class Menu extends StatefulWidget {
const Menu({required this.controller, super.key});
final WebViewController controller;
@override
State<Menu> createState() => _MenuState();
}
class _MenuState extends State<Menu> {
final cookieManager = WebViewCookieManager(); // Add this line
@override
Widget build(BuildContext context) {
// ...
ستحتوي الفئة _MenuState
على الرمز الذي تمّت إضافته سابقًا في الفئة Menu
، إلى جانب CookieManager
التي تمّت إضافتها مؤخرًا. في سلسلة الأقسام التالية، ستضيف دوالّ مساعدة إلى _MenuState
والتي سيتم استدعاؤها بدورها من خلال عناصر القائمة التي لم تتم إضافتها بعد.
الحصول على قائمة بجميع ملفات تعريف الارتباط
ستستخدم JavaScript للحصول على قائمة بجميع ملفات تعريف الارتباط. لتحقيق ذلك، أضِف طريقة مساعدة إلى نهاية فئة _MenuState
، تُسمى _onListCookies
. باستخدام طريقة runJavaScriptReturningResult
، تنفّذ الطريقة المساعدة document.cookie
في سياق JavaScript، وتعرض قائمة بجميع ملفات تعريف الارتباط.
أضِف ما يلي إلى الفئة _MenuState
:
lib/src/menu.dart
Future<void> _onListCookies(WebViewController controller) async {
final String cookies = await controller
.runJavaScriptReturningResult('document.cookie') as String;
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(cookies.isNotEmpty ? cookies : 'There are no cookies.'),
),
);
}
محو جميع ملفات تعريف الارتباط
لمحو جميع ملفات تعريف الارتباط في WebView، استخدِم طريقة clearCookies
للفئة CookieManager
. تعرض الطريقة علامة Future<bool>
تتم مطابقتها إلى true
إذا محا CookieManager
ملفات تعريف الارتباط، وfalse
إذا لم تكن هناك ملفات تعريف ارتباط لمحوها.
أضِف ما يلي إلى الفئة _MenuState
:
lib/src/menu.dart
Future<void> _onClearCookies() async {
final hadCookies = await cookieManager.clearCookies();
String message = 'There were cookies. Now, they are gone!';
if (!hadCookies) {
message = 'There were no cookies to clear.';
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
),
);
}
إضافة ملف تعريف ارتباط
يمكن إضافة ملف تعريف ارتباط من خلال استدعاء JavaScript. إنّ واجهة برمجة التطبيقات المستخدَمة لإضافة ملف تعريف ارتباط إلى مستند JavaScript موثَّقة بالتفصيل في MDN.
أضِف ما يلي إلى الفئة _MenuState
:
lib/src/menu.dart
Future<void> _onAddCookie(WebViewController controller) async {
await controller.runJavaScript('''var date = new Date();
date.setTime(date.getTime()+(30*24*60*60*1000));
document.cookie = "FirstName=John; expires=" + date.toGMTString();''');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Custom cookie added.'),
),
);
}
ضبط ملف تعريف ارتباط باستخدام CookieManager
يمكن أيضًا ضبط ملفات تعريف الارتباط باستخدام CookieManager على النحو التالي.
أضِف ما يلي إلى الفئة _MenuState
:
lib/src/menu.dart
Future<void> _onSetCookie(WebViewController controller) async {
await cookieManager.setCookie(
const WebViewCookie(name: 'foo', value: 'bar', domain: 'flutter.dev'),
);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Custom cookie is set.'),
),
);
}
إزالة ملف تعريف ارتباط
تتضمّن إزالة ملف تعريف ارتباط إضافة ملف تعريف ارتباط، يكون تاريخ انتهاء صلاحيته محدَّدًا في الماضي.
أضِف ما يلي إلى الفئة _MenuState
:
lib/src/menu.dart
Future<void> _onRemoveCookie(WebViewController controller) async {
await controller.runJavaScript(
'document.cookie="FirstName=John; expires=Thu, 01 Jan 1970 00:00:00 UTC" ');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Custom cookie removed.'),
),
);
}
إضافة عناصر قائمة CookieManager
كل ما يتبقى هو إضافة خيارات القائمة، وربطها بالطرق المساعدة التي أضفتها للتو. عدِّل فئة _MenuState
على النحو التالي:
lib/src/menu.dart
class _MenuState extends State<Menu> {
final cookieManager = WebViewCookieManager();
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate:
await widget.controller
.loadRequest(Uri.parse('https://youtube.com'));
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
case _MenuOptions.javascriptChannel:
await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
if (req.status == 200) {
let response = JSON.parse(req.responseText);
SnackBar.postMessage("IP Address: " + response.ip);
} else {
SnackBar.postMessage("Error: " + req.status);
}
}
req.send();''');
case _MenuOptions.clearCookies: // Add from here
await _onClearCookies();
case _MenuOptions.listCookies:
await _onListCookies(widget.controller);
case _MenuOptions.addCookie:
await _onAddCookie(widget.controller);
case _MenuOptions.setCookie:
await _onSetCookie(widget.controller);
case _MenuOptions.removeCookie:
await _onRemoveCookie(widget.controller); // To here.
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.userAgent,
child: Text('Show user-agent'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.javascriptChannel,
child: Text('Lookup IP Address'),
),
const PopupMenuItem<_MenuOptions>( // Add from here
value: _MenuOptions.clearCookies,
child: Text('Clear cookies'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.listCookies,
child: Text('List cookies'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.addCookie,
child: Text('Add cookie'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.setCookie,
child: Text('Set cookie'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.removeCookie,
child: Text('Remove cookie'),
), // To here.
],
);
}
ممارسة "مدير ملفات تعريف الارتباط"
لاستخدام جميع الوظائف التي أضفتها للتو إلى التطبيق، جرِّب الخطوات التالية:
- اختَر إدراج ملفات تعريف الارتباط. يجب أن يسرد هذا العنصر ملفات تعريف الارتباط في "إحصاءات Google" التي حدّدها flutter.dev.
- انقر على محو ملفات تعريف الارتباط. من المفترض أن يتم الإبلاغ عن أن ملفات تعريف الارتباط قد تم محوها بالفعل.
- اختَر محو ملفات تعريف الارتباط مرة أخرى. من المفترض أن يتم الإبلاغ عن عدم توفر ملفات تعريف ارتباط لمحوها.
- اختَر إدراج ملفات تعريف الارتباط. يجب أن يبلغك بعدم وجود ملفات تعريف ارتباط.
- انقر على إضافة ملف تعريف الارتباط. من المفترض أن يُبلغ عن ملف تعريف الارتباط كمضاف.
- اختَر ضبط ملف تعريف الارتباط. من المفترض أن يُبلغ عن ملف تعريف الارتباط على أنّه تم ضبطه.
- اختَر إدراج ملفات تعريف الارتباط، ثم اختَر إزالة ملف تعريف الارتباط كعنصر نهائي.
12. تحميل مواد العرض والملفات وسلاسل HTML في Flutter في WebView
يمكن لتطبيقك تحميل ملفات HTML باستخدام طرق مختلفة وعرضها في WebView. في هذه الخطوة، سيتم تحميل مادة عرض في Flutter محدَّدة في ملف pubspec.yaml
، وتحميل ملف في المسار المحدَّد وتحميل صفحة باستخدام سلسلة HTML.
إذا أردت تحميل ملف يقع في مسار محدّد، عليك إضافة السمة path_provider
إلى pubspec.yaml
. هذا هو مكوّن Flutter الإضافي للعثور على المواقع الجغرافية الشائعة الاستخدام على نظام الملفات.
في سطر الأوامر، شغِّل الأمر التالي:
$ flutter pub add path_provider
لتحميل مادة العرض، علينا تحديد المسار الذي يؤدي إلى مادة العرض في pubspec.yaml
. في pubspec.yaml
، أضِف السطور التالية:
pubspec.yaml
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# Add from here
assets:
- assets/www/index.html
- assets/www/styles/style.css
# ... to here.
لإضافة مواد العرض إلى مشروعك، اتّبِع الخطوات التالية:
- أنشِئ دليلاً جديدًا باسم "
assets
" في المجلد الجذر لمشروعك. - أنشِئ دليلاً جديدًا باسم "
www
" في مجلد "assets
". - أنشِئ دليلاً جديدًا باسم "
styles
" في مجلد "www
". - أنشئ ملفًا جديدًا بالاسم
index.html
في المجلدwww
. - أنشئ ملفًا جديدًا بالاسم
style.css
في المجلدstyles
.
انسخ الرمز التالي والصقه في ملف index.html
:
<!DOCTYPE html>
<!-- Copyright 2013 The Flutter Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<html lang="en">
<head>
<title>Load file or HTML string example</title>
<link rel="stylesheet" href="styles/style.css" />
</head>
<body>
<h1>Local demo page</h1>
<p>
This is an example page used to demonstrate how to load a local file or HTML
string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
webview</a> plugin.
</p>
</body>
</html>
بالنسبة إلى style.css، استخدم الأسطر القليلة التالية لتعيين نمط رأس HTML:
h1 {
color: blue;
}
بعد أن تم ضبط مواد العرض وأصبحت جاهزة للاستخدام، يمكنك تنفيذ الطرق اللازمة لتحميل مواد العرض أو الملفات أو سلاسل HTML في Flutter وعرضها.
تحميل مادة عرض Flutter
لتحميل مادة العرض التي أنشأتها للتو، ما عليك سوى استدعاء طريقة loadFlutterAsset
باستخدام WebViewController
وتحديد المسار إلى مادة العرض كمَعلمة. أضف الطريقة التالية في نهاية التعليمة البرمجية:
lib/src/menu.dart
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
تحميل ملف على الجهاز
لتحميل ملف على جهازك، يمكنك إضافة طريقة تستخدم طريقة loadFile
، ومرة أخرى باستخدام WebViewController
التي تأخذ String
التي تحتوي على المسار إلى الملف.
تحتاج أولاً إلى إنشاء ملف يحتوي على رمز HTML. ويمكنك إجراء ذلك ببساطة من خلال إضافة رمز HTML كسلسلة أعلى الرمز في ملف menu.dart
أسفل عمليات الاستيراد مباشرةً.
import 'dart:io'; // Add this line,
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart'; // And this one.
import 'package:webview_flutter/webview_flutter.dart';
// Add from here ...
const String kExamplePage = '''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Load file or HTML string example</title>
</head>
<body>
<h1>Local demo page</h1>
<p>
This is an example page used to demonstrate how to load a local file or HTML
string using the <a href="https://pub.dev/packages/webview_flutter">Flutter
webview</a> plugin.
</p>
</body>
</html>
''';
// ... to here.
لإنشاء File
وكتابة سلسلة HTML إلى الملف، عليك إضافة طريقتَين. ستحمِّل _onLoadLocalFileExample
الملف من خلال توفير المسار كسلسلة يتم عرضها باستخدام طريقة _prepareLocalFile()
. أضف الطرق التالية إلى التعليمات البرمجية:
Future<void> _onLoadLocalFileExample(
WebViewController controller, BuildContext context) async {
final String pathToIndex = await _prepareLocalFile();
await controller.loadFile(pathToIndex);
}
static Future<String> _prepareLocalFile() async {
final String tmpDir = (await getTemporaryDirectory()).path;
final File indexFile = File('$tmpDir/www/index.html');
await Directory('$tmpDir/www').create(recursive: true);
await indexFile.writeAsString(kExamplePage);
return indexFile.path;
}
تحميل سلسلة HTML
يعد عرض صفحة عن طريق توفير سلسلة HTML أمرًا مباشرًا إلى حد ما. تتضمن WebViewController
طريقة يمكنك استخدامها تُسمى loadHtmlString
حيث يمكنك منح سلسلة HTML كوسيطة. بعد ذلك، سيعرض WebView
صفحة HTML المقدّمة. أضف الطريقة التالية إلى التعليمة البرمجية:
Future<void> _onLoadFlutterAssetExample(
WebViewController controller, BuildContext context) async {
await controller.loadFlutterAsset('assets/www/index.html');
}
Future<void> _onLoadLocalFileExample(
WebViewController controller, BuildContext context) async {
final String pathToIndex = await _prepareLocalFile();
await controller.loadFile(pathToIndex);
}
static Future<String> _prepareLocalFile() async {
final String tmpDir = (await getTemporaryDirectory()).path;
final File indexFile = File('$tmpDir/www/index.html');
await Directory('$tmpDir/www').create(recursive: true);
await indexFile.writeAsString(kExamplePage);
return indexFile.path;
}
// Add here ...
Future<void> _onLoadHtmlStringExample(
WebViewController controller, BuildContext context) async {
await controller.loadHtmlString(kExamplePage);
}
// ... to here.
إضافة الأصناف في القائمة
الآن بعد أن تم إعداد الأصول وأصبحت جاهزة للاستخدام، وتم إنشاء الطرق مع جميع الوظائف، يمكن تحديث القائمة. أضِف الإدخالات التالية إلى تعداد _MenuOptions
:
lib/src/menu.dart
enum _MenuOptions {
navigationDelegate,
userAgent,
javascriptChannel,
listCookies,
clearCookies,
addCookie,
setCookie,
removeCookie,
// Add from here ...
loadFlutterAsset,
loadLocalFile,
loadHtmlString,
// ... to here.
}
الآن بعد أن تم تحديث التعداد، يمكنك إضافة خيارات القائمة وربطها بالطرق المساعدة التي أضفتها للتو. عدِّل فئة _MenuState
على النحو التالي:
lib/src/menu.dart
class _MenuState extends State<Menu> {
final cookieManager = WebViewCookieManager();
@override
Widget build(BuildContext context) {
return PopupMenuButton<_MenuOptions>(
onSelected: (value) async {
switch (value) {
case _MenuOptions.navigationDelegate:
await widget.controller
.loadRequest(Uri.parse('https://youtube.com'));
case _MenuOptions.userAgent:
final userAgent = await widget.controller
.runJavaScriptReturningResult('navigator.userAgent');
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('$userAgent'),
));
case _MenuOptions.javascriptChannel:
await widget.controller.runJavaScript('''
var req = new XMLHttpRequest();
req.open('GET', "https://api.ipify.org/?format=json");
req.onload = function() {
if (req.status == 200) {
let response = JSON.parse(req.responseText);
SnackBar.postMessage("IP Address: " + response.ip);
} else {
SnackBar.postMessage("Error: " + req.status);
}
}
req.send();''');
case _MenuOptions.clearCookies:
await _onClearCookies();
case _MenuOptions.listCookies:
await _onListCookies(widget.controller);
case _MenuOptions.addCookie:
await _onAddCookie(widget.controller);
case _MenuOptions.setCookie:
await _onSetCookie(widget.controller);
case _MenuOptions.removeCookie:
await _onRemoveCookie(widget.controller);
case _MenuOptions.loadFlutterAsset: // Add from here
if (!mounted) return;
await _onLoadFlutterAssetExample(widget.controller, context);
case _MenuOptions.loadLocalFile:
if (!mounted) return;
await _onLoadLocalFileExample(widget.controller, context);
case _MenuOptions.loadHtmlString:
if (!mounted) return;
await _onLoadHtmlStringExample(widget.controller, context);
// To here.
}
},
itemBuilder: (context) => [
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.navigationDelegate,
child: Text('Navigate to YouTube'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.userAgent,
child: Text('Show user-agent'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.javascriptChannel,
child: Text('Lookup IP Address'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.clearCookies,
child: Text('Clear cookies'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.listCookies,
child: Text('List cookies'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.addCookie,
child: Text('Add cookie'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.setCookie,
child: Text('Set cookie'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.removeCookie,
child: Text('Remove cookie'),
),
const PopupMenuItem<_MenuOptions>( // Add from here
value: _MenuOptions.loadFlutterAsset,
child: Text('Load Flutter Asset'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.loadHtmlString,
child: Text('Load HTML string'),
),
const PopupMenuItem<_MenuOptions>(
value: _MenuOptions.loadLocalFile,
child: Text('Load local file'),
), // To here.
],
);
}
اختبار مواد العرض والملف وسلسلة HTML
لاختبار ما إذا كانت التعليمة البرمجية تعمل بعد تنفيذك لها للتو، يمكنك تشغيل الرمز على جهازك والنقر على أحد عناصر القائمة المضافة حديثًا. لاحظ كيف يستخدم _onLoadFlutterAssetExample
اللون style.css
الذي أضفناه لتغيير عنوان ملف HTML إلى اللون الأزرق.
13. أكملت كل الإجراءات
تهانينا! لقد أكملت الدرس التطبيقي حول الترميز. يمكنك العثور على الرمز المكتمل لهذا الدرس التطبيقي في مستودع الدرس التطبيقي حول الترميز.
لمزيد من المعلومات، يمكنك تجربة الدروس التطبيقية حول ترميز Flutter.