1. مقدمة
تاريخ آخر تعديل: 19-10-2021
باستخدام مكوّن WebView الإضافي في Flutter، يمكنك إضافة أداة WebView إلى تطبيق Android أو iOS Flutter. في نظام التشغيل 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
المتطلبات
- Android Studio 4.1 أو إصدار أحدث (لتطوير تطبيقات Android)
- Xcode 12 أو إصدار أحدث (لتطوير تطبيقات iOS)
- حزمة تطوير البرامج (SDK) في Flutter
- محرِّر رموز، مثل استوديو Android أو Visual Studio Code
2. إعداد بيئة تطوير Flutter
يجب توفّر برنامجَين لإكمال هذا الدرس التطبيقي، وهما حزمة تطوير البرامج (SDK) الخاصة بإطار عمل Flutter ومحرِّر.
يمكنك تشغيل الدرس العملي باستخدام أيّ من الأجهزة التالية:
- جهاز Android أو iOS فعلي متصل بالكمبيوتر وتم ضبطه على وضع "المطوّر"
- محاكي iOS (يتطلّب تثبيت أدوات Xcode)
- محاكي Android (يتطلّب الإعداد في "استوديو Android")
3- الخطوات الأولى
بدء استخدام Flutter
تتوفّر طرق متنوعة لإنشاء مشروع Flutter جديد، إذ يوفّر كلّ من "استوديو Android" و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... collection 1.18.0 (1.19.0 available) leak_tracker 10.0.5 (10.0.7 available) leak_tracker_flutter_testing 3.0.5 (3.0.7 available) material_color_utilities 0.11.1 (0.12.0 available) + plugin_platform_interface 2.1.8 string_scanner 1.2.0 (1.3.0 available) test_api 0.7.2 (0.7.3 available) + webview_flutter 4.9.0 + webview_flutter_android 3.16.7 + webview_flutter_platform_interface 2.10.0 + webview_flutter_wkwebview 3.15.0 Changed 5 dependencies! 6 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
إلى تطبيقك. إنّ WebViews هي طرق عرض مضمّنة مستضافة، ويمكنك كمطوّر تطبيقات اختيار طريقة استضافة طرق العرض المضمّنة هذه في تطبيقك. على نظام التشغيل 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
.
إضافة عناصر التحكّم في التنقّل إلى شريط التطبيقات
بعد تعديل 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. تتبُّع التنقّل باستخدام NavigationDelegate
توفّر WebView
لتطبيقك NavigationDelegate,
يتيح له تتبُّع عملية التنقّل بين الصفحات في أداة WebView
والتحكّم فيها. عندما يتم بدء عملية تنقّل من خلال WebView,
، مثلاً عندما ينقر المستخدم على رابط، يتم استدعاء NavigationDelegate
. يمكن استخدام دالة معاودة الاتصال NavigationDelegate
للتحكّم في ما إذا كان WebView
سيواصل التنقّل.
تسجيل NavigationDelegate مخصّص
في هذه الخطوة، ستسجّل 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
تُستخدَم لتقييم 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، يتم تنفيذ طريقة loadRequest
في WebViewController
. سيتم حظر عملية التنقّل هذه من خلال ردّ الاتصال 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 موضّحة بالتفصيل على شبكة مطوّري Mozilla.
أضِف ما يلي إلى فئة _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.
],
);
}
استخدام CookieManager
لاستخدام جميع الوظائف التي أضفتها إلى التطبيق، جرِّب الخطوات التالية:
- اختَر عرض ملفات تعريف الارتباط. من المفترض أن تعرض ملفات تعريف الارتباط في "إحصاءات Google" التي تم ضبطها من خلال flutter.dev.
- انقر على محو ملفات تعريف الارتباط. من المفترض أن يوضّح أنّه تم محو ملفات تعريف الارتباط بالفعل.
- انقر على محو ملفات تعريف الارتباط مرة أخرى. يجب أن يُبلغ بأنّه لم تتوفّر أي ملفات تعريف ارتباط لمحو بياناتها.
- اختَر عرض ملفات تعريف الارتباط. يجب أن يُظهر أنّه لا تتوفّر أي ملفات تعريف ارتباط.
- انقر على إضافة ملف تعريف ارتباط. يجب أن يُبلغ عن إضافة ملف تعريف الارتباط.
- انقر على ضبط ملف تعريف الارتباط. يجب أن يُبلغ عن ضبط ملف تعريف الارتباط.
- انقر على عرض ملفات تعريف الارتباط (List cookies)، ثم انقر على إزالة ملف تعريف الارتباط (Remove cookie).
12. تحميل مواد عرض Flutter وملفاتها وسلاسل HTML في 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
:
assets/www/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:
assets/www/styles/style.css
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
أسفل عمليات الاستيراد مباشرةً.
lib/src/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()
. أضِف الطرق التالية إلى الرمز البرمجي:
lib/src/menu.dart
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 المقدَّمة. أضِف الطريقة التالية إلى الرمز البرمجي:
lib/src/menu.dart
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 البرمجية الأخرى.