source-code/
student-hub
Public
codeCodeinfoIssues 0call_splitPull Requestsplay_circleActions
student-hub/lib/screens/elok_portal_screen.dart
text246 lines8.6 KB
import 'dart:io'; // Mengimpor pustaka dart:io untuk mendukung pengecekan platform sistem operasi
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:url_launcher/url_launcher.dart';
import 'pdf_viewer_screen.dart';

class ElokPortalScreen extends StatefulWidget {
  const ElokPortalScreen({super.key});

  @override
  State<ElokPortalScreen> createState() => _ElokPortalScreenState();
}

class _ElokPortalScreenState extends State<ElokPortalScreen> {
  double _progress = 0;
  InAppWebViewController? _webViewController;
  bool _canGoBack = false;
  bool _isUpdatingCanGoBack = false;

  // Mendefinisikan daftar 'whitelist' domain yang aman dan berizin untuk diakses di dalam WebView
  final List<String> _allowedDomains = [
    'elok.ugm.ac.id',
    'simaster.ugm.ac.id', // Domain otentikasi/sistem terintegrasi
    'sso.ugm.ac.id', // Single Sign-On otentikasi UGM
    // Domain sekunder terverifikasi dari UGM dapat ditambahkan ke sini
  ];

  /// Memverifikasi pakah alamat URI (Uniform Resource Identifier) termasuk dalam daftar domain sah.
  bool _isAllowedUrl(Uri? uri) {
    if (uri == null) return false;

    // Memastikan kecocokan domain utama maupun sub-domain terkait
    return _allowedDomains.any(
      (domain) => uri.host == domain || uri.host.endsWith('.$domain'),
    );
  }

  Future<void> _updateCanGoBack() async {
    if (!mounted) return;
    if (_isUpdatingCanGoBack) return;
    final controller = _webViewController;
    if (controller == null) return;
    _isUpdatingCanGoBack = true;
    try {
      final canGoBack = await controller.canGoBack();
      if (!mounted) return;
      if (canGoBack == _canGoBack) return;
      setState(() {
        _canGoBack = canGoBack;
      });
    } finally {
      _isUpdatingCanGoBack = false;
    }
  }

  Future<void> _handleBackPressed() async {
    final controller = _webViewController;
    if (controller != null && _canGoBack) {
      await controller.goBack();
      await _updateCanGoBack();
      return;
    }
    if (mounted) {
      Navigator.of(context).pop();
    }
  }

  @override
  Widget build(BuildContext context) {
    if (Platform.isLinux || Platform.isWindows) {
      return Scaffold(
        appBar: AppBar(
          title: const Text("Portal eLOK"),
          backgroundColor: const Color(0xFF4A00E0),
          foregroundColor: Colors.white,
        ),
        body: const Center(
          child: Text(
            "WebView eLOK nggak disupport di Linux/Windows.\nHarus di-run di Emulator Android atau HP asli ya!",
            textAlign: TextAlign.center,
            style: TextStyle(fontSize: 16),
          ),
        ),
      );
    }
    return Scaffold(
      appBar: AppBar(
        title: const Text("Portal eLOK UGM"),
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          tooltip: 'Kembali',
          onPressed: _handleBackPressed,
        ),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () => _webViewController?.reload(),
          ),
        ],
      ),
      body: Stack(
        children: [
          InAppWebView(
            initialUrlRequest: URLRequest(
              url: WebUri("https://elok.ugm.ac.id"),
            ),
            initialSettings: InAppWebViewSettings(
              javaScriptEnabled: true,
              domStorageEnabled: true,
              javaScriptCanOpenWindowsAutomatically: true,
              useShouldOverrideUrlLoading: true,
              mediaPlaybackRequiresUserGesture: false,
              allowsInlineMediaPlayback: true,
              useOnDownloadStart: true,
              supportZoom: true,
              builtInZoomControls: true,
              displayZoomControls: false,

              // Mengamankan WebView dengan memblokir izin akses sistem berkas (file system) dari URL
              allowFileAccessFromFileURLs: false,
              allowUniversalAccessFromFileURLs: false,

              // Menyesuaikan kebijakan konten campuran (HTTP/HTTPS) agar berjalan pada mode kompatibilitas penuh
              mixedContentMode:
                  MixedContentMode.MIXED_CONTENT_COMPATIBILITY_MODE,

              // Memastikan cache WebView berfungsi dengan optimal tanpa interupsi
              clearCache: false,
              cacheEnabled: true,
            ),
            onWebViewCreated: (controller) {
              _webViewController = controller;
            },
            onLoadStop: (controller, url) {
              _updateCanGoBack();
            },

            shouldOverrideUrlLoading: (controller, navigationAction) async {
              var uri = navigationAction.request.url;

              // Mengamankan arus lalu lintas navigasi: Mencegat dan mengevaluasi akses URL ke luar domain UGM
              if (uri != null && !_isAllowedUrl(uri)) {
                // Menampilkan dialog keamanan apabila tujuan navigasi berada di luar whitelist
                if (context.mounted) {
                  final shouldOpen = await showDialog<bool>(
                    context: context,
                    builder: (context) => AlertDialog(
                      title: const Text('⚠️ Link Eksternal'),
                      content: Text(
                        'Link ini mengarah ke ${uri.host} yang bukan bagian dari portal UGM.\n\n'
                        'Tetap buka?',
                      ),
                      actions: [
                        TextButton(
                          onPressed: () => Navigator.pop(context, false),
                          child: const Text('Batal'),
                        ),
                        TextButton(
                          onPressed: () => Navigator.pop(context, true),
                          child: const Text('Buka di Browser'),
                        ),
                      ],
                    ),
                  );

                  if (shouldOpen == true) {
                    // Meneruskan peluncuran URL berisiko eksternal ke dalam mesin peramban bawaan sistem
                    if (await canLaunchUrl(uri)) {
                      await launchUrl(
                        uri,
                        mode: LaunchMode.externalApplication,
                      );
                    }
                  }
                }
                return NavigationActionPolicy.CANCEL;
              }

              // Menangani penangkapan URL berakhiran PDF untuk diarahkan ke layar internal (PdfViewerScreen)
              if (uri != null && uri.path.toLowerCase().endsWith(".pdf")) {
                CookieManager cookieManager = CookieManager.instance();
                List<Cookie> cookies = await cookieManager.getCookies(
                  url: WebUri("https://elok.ugm.ac.id"),
                );

                String cookieString = cookies
                    .map((c) => "${c.name}=${c.value}")
                    .join("; ");

                if (context.mounted) {
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => PdfViewerScreen(
                        pdfUrl: uri.toString(),
                        cookie: cookieString,
                      ),
                    ),
                  );
                }
                return NavigationActionPolicy.CANCEL;
              }

              return NavigationActionPolicy.ALLOW;
            },

            // Tangkapan eksekusi dan penanganan galat (error handling) bilamana terjadi masalah pemuatan
            onReceivedError: (controller, request, error) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('Error loading page: ${error.description}'),
                  backgroundColor: Colors.red,
                ),
              );
            },

            onDownloadStartRequest: (controller, downloadStartRequest) async {
              var url = downloadStartRequest.url;

              if (await canLaunchUrl(url)) {
                await launchUrl(url, mode: LaunchMode.externalApplication);
              } else {
                if (context.mounted) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(
                      content: Text("Gak bisa buka link download-nya!"),
                    ),
                  );
                }
              }
            },

            onProgressChanged: (controller, progress) {
              setState(() {
                _progress = progress / 100;
              });
            },
          ),
          if (_progress < 1.0)
            LinearProgressIndicator(value: _progress, color: Colors.orange),
        ],
      ),
    );
  }
}

About

Student Hub UGM - Task & Schedule Tracker

FlutterDartSQLiteAIGeminiMobile App

Contributors

2