Flutter モーダルボトムシート（iOS風）でWebView表示

# はじめに
**Flutter**で**iOS風 モーダルボトムシート**（モーダルが下から出て、後ろの画面が少し小さくなるあれ）でWebView表示を実装しました。

# 環境
- Dart: 2.15.1
- Flutter: 2.8.1

# 完成形

![modl_bottom_sheet.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519965/ad735574-504d-e6fc-6494-c85c8edbf69f.gif)

# 実装
WebViewの説明に関しては、以下の記事で説明しているのでご覧ください。
- [Web画面表示（進捗バー付き）](https://qiita.com/k2rt/items/77cbd77b486c1b825d3f)

## Package

[modal_bottom_sheet](https://pub.dev/packages/modal_bottom_sheet)を使用します。（名前そのまま）

```pubspec.yaml
dependencies:
  modal_bottom_sheet: ^2.0.0
  webview_flutter: ^3.0.0
```

## コード
今回の実装にあたって、主に３か所の追加修正が必要です。

### ルート
homeのままだと画面が小さくならないため、ルーティングの修正をします。
```home: ~~~,```となっていたところを、
```initialRoute: ~~~```, ```onGenerateRoute: (RouteSettings settings) {~~~}```
に変更します。

```main.dart
import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(const MyApp(),);
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      initialRoute: HomeScreen.routeName,
      onGenerateRoute: (RouteSettings settings) {
        switch (settings.name) {
          case HomeScreen.routeName:
            return MaterialWithModalsPageRoute(
                builder: (context) => const HomeScreen(),
                settings: settings,);
        }
      },
    );
  }
}

〜〜〜
続く
〜〜〜
```
### モーダル
パッケージが用意してくれた、**showCupertinoModalBottomSheet**でiOS風 モーダルを呼び出します。

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

  static const routeName = '/home';

  @override
  Widget build(BuildContext context) {

    void _showModalBottomSheet() {
      showCupertinoModalBottomSheet(
        expand: true,
        context: context,
        backgroundColor: Colors.transparent,
        builder: (context) => const WebViewScreen(),);
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: ElevatedButton(
          child: const Text('Press'),
          onPressed: () {
            _showModalBottomSheet();
          },
        ),
      ),
    );
  }
}

〜〜〜
続く
〜〜〜
```

### WebView
そのまま表示しても、モーダルシート上だとスクロールが効かなかったため
**EagerGestureRecognizer()**をWebViewの**gestureRecognizers**に渡します。
ただWebView上のリンクで遷移した後、左 → 右のスワイプで戻れないです。
方法があれば教えて欲しいです！

```
class WebViewScreen extends StatefulWidget {
  const WebViewScreen({
    Key? key,
  }) : super(key: key);

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

class _WebViewScreenState extends State<WebViewScreen> {
  final Set<Factory<OneSequenceGestureRecognizer>> _gestureRecognizers
      = {Factory(() => EagerGestureRecognizer())}; // タッチ操作用

  final _key = UniqueKey();
  final Completer<WebViewController> _controller = Completer<WebViewController>();
  bool _isLoading = true; // ローディングの有無
  String _title = ''; // Webタイトル
  double _downloadPercentage = 0.0; // 進捗バー用

  @override
  void initState() {
    super.initState();
    if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
  }

  // ローディングの切り替え
  void toggleLoading() {
    setState(() {
      _isLoading = !_isLoading;
    });
  }

  // 進捗計算
  void calculateProgress(int progress) {
    setState(() {
      _downloadPercentage = (progress / 100);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_title),// Web画面のタイトルを表示
        automaticallyImplyLeading: false, // バックボタンの非表示
        actions: [
          IconButton(
            icon: const Icon(Icons.close),
            onPressed: () => Navigator.pop(context),
          ),
        ],
      ),
      body: Column(
        children: [
          // 進捗バー
          _isLoading ? LinearProgressIndicator(value: _downloadPercentage, minHeight: 7.0,) : const SizedBox.shrink(),
          Expanded(
            child: Scrollbar(
              showTrackOnHover: true,
              child: WebView(
                  gestureRecognizers: _gestureRecognizers, // タッチ操作の有効化
                  key: _key,
                  initialUrl: 'https://qiita.com/',
                  javascriptMode: JavascriptMode.unrestricted,
                  onWebViewCreated: _controller.complete,
                  onPageFinished: (String url) async {
                    if (_isLoading) toggleLoading();
                    final controller = await _controller.future;
                    final title = await controller.getTitle();
                    if (title != null) _title = title;
                  },
                  onProgress: (progress) {
                    calculateProgress(progress);
                  }
              ),
            ),
          ),
        ],
      ),
    );
  }
}
```

以上で動くはずです。