diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a9398b5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,46 @@ +# Build outputs +build/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages + +# IDE +.idea/ +.vscode/ +*.iml +*.ipr +*.iws + +# OS +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Docker +Dockerfile +docker-compose.yml +.dockerignore + +# Docs +*.md +README.md + +# Tests +test/ +.test_coverage/ + +# Android/iOS (not needed for web) +android/ +ios/ +macos/ +windows/ +linux/ + +# Misc +*.log +*.swp +.env diff --git a/.gitea/workflows/android-build.yml b/.gitea/workflows/android-build.yml index 373d1f3..7cb8145 100644 --- a/.gitea/workflows/android-build.yml +++ b/.gitea/workflows/android-build.yml @@ -18,6 +18,7 @@ jobs: env: ANDROID_HOME: /root/android-sdk ANDROID_SDK_ROOT: /root/android-sdk + GRADLE_OPTS: -Xmx2g -XX:MaxMetaspaceSize=512m steps: - name: Checkout code diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2ec2b8f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +# Stage 1: Build the Flutter web app +FROM ghcr.io/cirruslabs/flutter:stable AS build + +WORKDIR /app + +# Copy pubspec files and get dependencies +COPY pubspec.yaml pubspec.lock ./ +RUN flutter pub get + +# Copy the rest of the app +COPY . . + +# Build web app +RUN flutter build web --release --web-renderer html + +# Stage 2: Serve with nginx +FROM nginx:alpine + +# Copy the built web app from build stage +COPY --from=build /app/build/web /usr/share/nginx/html + +# Copy custom nginx config +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Expose port +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e058d67..c392ae9 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,10 @@ android:label="quotegen_client" android:name="${applicationName}" android:icon="@mipmap/ic_launcher"> + + = 1.1) + - google_mobile_ads (5.3.1): + - Flutter + - Google-Mobile-Ads-SDK (~> 11.13.0) + - webview_flutter_wkwebview + - GoogleUserMessagingPlatform (3.1.0) - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -46,18 +53,25 @@ PODS: - Flutter - FlutterMacOS - SwiftyGif (5.4.5) + - webview_flutter_wkwebview (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) + - google_mobile_ads (from `.symlinks/plugins/google_mobile_ads/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: trunk: - DKImagePickerController - DKPhotoGallery + - Google-Mobile-Ads-SDK + - GoogleUserMessagingPlatform - SDWebImage - SwiftyGif @@ -66,23 +80,31 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: Flutter + google_mobile_ads: + :path: ".symlinks/plugins/google_mobile_ads/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + Google-Mobile-Ads-SDK: 14f57f2dc33532a24db288897e26494640810407 + google_mobile_ads: fe0e2c1764ad95323dd0e3081d0bb2d58411f957 + GoogleUserMessagingPlatform: befe603da6501006420c206222acd449bba45a9c path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba SDWebImage: f29024626962457f3470184232766516dee8dfea share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 + webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4 PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 9a6eaed..4dc8cf9 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -199,6 +199,7 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 202FD1FBBA50227619E09BFC /* [CP] Embed Pods Frameworks */, + 9EB32ED1B9C2EE1090869288 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -340,6 +341,23 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; + 9EB32ED1B9C2EE1090869288 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; F9A7E4B35A518B03A03269AF /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 426b523..54fd508 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,5 +45,14 @@ UIApplicationSupportsIndirectInputEvents + GADApplicationIdentifier + ca-app-pub-5177609929140951~5220611390 + SKAdNetworkItems + + + SKAdNetworkIdentifier + cstr6suwn9.skadnetwork + + diff --git a/lib/main.dart b/lib/main.dart index 27419d0..1a140a5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,8 +2,16 @@ import 'package:flutter/foundation.dart'; import 'package:go_router/go_router.dart'; import 'package:quotegen_client/pages/home/page.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Initialize mobile ads (AdMob) only on mobile platforms + if (!kIsWeb) { + await MobileAds.instance.initialize(); + } -void main() { runApp(const MyApp()); } diff --git a/lib/pages/home/page.dart b/lib/pages/home/page.dart index b5ba6f6..7d33e1e 100644 --- a/lib/pages/home/page.dart +++ b/lib/pages/home/page.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:image/image.dart' as img; import 'package:quotegen_client/services/quote_api_v2.dart'; +import 'package:quotegen_client/widgets/ad_banner.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:share_plus/share_plus.dart'; import 'package:path_provider/path_provider.dart'; @@ -148,16 +149,24 @@ class _HomePageState extends State { height: 600, child: _buildPreview(context) ), - + + Gap(14), - + ConstrainedBox( constraints: BoxConstraints( maxWidth: 400, ), - child: _buildForm(context) + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded(child: _buildForm(context)), + Gap(14), + AdBanner(), + ], + ), ), - + Gap(max(bottomPadding, 14)), ], ), @@ -170,7 +179,7 @@ class _HomePageState extends State { child: SingleChildScrollView( child: Column( children: [ - + Gap(max(topPadding, 14)), _buildPreview(context), @@ -179,6 +188,11 @@ class _HomePageState extends State { _buildForm(context), + Gap(14), + + // Banner ad + AdBanner(), + Gap(max(bottomPadding, 14)), ], ), diff --git a/lib/services/ad_service.dart b/lib/services/ad_service.dart new file mode 100644 index 0000000..9a541a4 --- /dev/null +++ b/lib/services/ad_service.dart @@ -0,0 +1,19 @@ +import "package:flutter/widgets.dart"; + +abstract class AdService { + + /// Initialize ad SDK + Future initialize(); + + /// Load banner ad + Future loadBannerAd(); + + /// Get banner widget to display + Widget getBannerWidget(); + + /// Dispose resources + void dispose(); + + /// Check if ads are loaded + bool get isLoaded; +} diff --git a/lib/services/admob_service.dart b/lib/services/admob_service.dart new file mode 100644 index 0000000..1b98f57 --- /dev/null +++ b/lib/services/admob_service.dart @@ -0,0 +1,73 @@ +import "package:flutter/foundation.dart"; +import "package:flutter/widgets.dart"; +import "package:google_mobile_ads/google_mobile_ads.dart"; +import "ad_service.dart"; + +class AdMobService implements AdService { + BannerAd? _bannerAd; + bool _isLoaded = false; + + // Ad unit IDs + static const String _androidBannerAdUnitId = "ca-app-pub-5177609929140951/8415596856"; + static const String _iosBannerAdUnitId = "ca-app-pub-5177609929140951/7102515188"; + + String get _bannerAdUnitId { + if (defaultTargetPlatform == TargetPlatform.android) { + return _androidBannerAdUnitId; + } else if (defaultTargetPlatform == TargetPlatform.iOS) { + return _iosBannerAdUnitId; + } else { + throw UnsupportedError("Platform not supported"); + } + } + + @override + Future initialize() async { + await MobileAds.instance.initialize(); + print("AdMob initialized"); + } + + @override + Future loadBannerAd() async { + _bannerAd = BannerAd( + adUnitId: _bannerAdUnitId, + size: AdSize.banner, + request: const AdRequest(), + listener: BannerAdListener( + onAdLoaded: (ad) { + print("Banner ad loaded"); + _isLoaded = true; + }, + onAdFailedToLoad: (ad, error) { + print("Banner ad failed to load: $error"); + ad.dispose(); + _isLoaded = false; + }, + ), + ); + + await _bannerAd!.load(); + } + + @override + Widget getBannerWidget() { + if (_bannerAd == null || !_isLoaded) { + return SizedBox.shrink(); + } + + return SizedBox( + height: 50, + child: AdWidget(ad: _bannerAd!), + ); + } + + @override + void dispose() { + _bannerAd?.dispose(); + _bannerAd = null; + _isLoaded = false; + } + + @override + bool get isLoaded => _isLoaded; +} diff --git a/lib/services/adsense_service.dart b/lib/services/adsense_service.dart new file mode 100644 index 0000000..378d030 --- /dev/null +++ b/lib/services/adsense_service.dart @@ -0,0 +1,3 @@ +// Conditional export based on platform +export "adsense_service_stub.dart" + if (dart.library.js_interop) "adsense_service_web.dart"; diff --git a/lib/services/adsense_service_stub.dart b/lib/services/adsense_service_stub.dart new file mode 100644 index 0000000..6b64f44 --- /dev/null +++ b/lib/services/adsense_service_stub.dart @@ -0,0 +1,32 @@ +import "package:flutter/widgets.dart"; +import "ad_service.dart"; + +/// Stub implementation for non-web platforms +class AdSenseService implements AdService { + bool _isLoaded = false; + + @override + Future initialize() async { + // Not supported on non-web platforms + _isLoaded = false; + } + + @override + Future loadBannerAd() async { + // Not supported on non-web platforms + _isLoaded = false; + } + + @override + Widget getBannerWidget() { + return SizedBox.shrink(); + } + + @override + void dispose() { + _isLoaded = false; + } + + @override + bool get isLoaded => _isLoaded; +} diff --git a/lib/services/adsense_service_web.dart b/lib/services/adsense_service_web.dart new file mode 100644 index 0000000..806c378 --- /dev/null +++ b/lib/services/adsense_service_web.dart @@ -0,0 +1,80 @@ +import "dart:ui_web" as ui_web; +import "dart:js" as js; +import "package:flutter/widgets.dart"; +import "package:universal_html/html.dart" as html; +import "ad_service.dart"; + +/// Web implementation of AdSense service +class AdSenseService implements AdService { + bool _isLoaded = false; + static const String _adSlotId = "XXXXXXXXXX"; // Replace with your ad slot ID + + @override + Future initialize() async { + // AdSense is loaded via script tag in index.html + print("AdSense initialized (web)"); + _isLoaded = true; + } + + @override + Future loadBannerAd() async { + // AdSense ads are loaded automatically when widget renders + _isLoaded = true; + } + + @override + Widget getBannerWidget() { + final viewType = "adsense-banner-${DateTime.now().millisecondsSinceEpoch}"; + + // Register the view factory + // ignore: undefined_prefixed_name + ui_web.platformViewRegistry.registerViewFactory( + viewType, + (int viewId) => _createAdElement(), + ); + + return SizedBox( + height: 90, + child: HtmlElementView(viewType: viewType), + ); + } + + html.Element _createAdElement() { + // Create AdSense div + final adContainer = html.DivElement() + ..style.width = "100%" + ..style.height = "90px" + ..style.textAlign = "center"; + + final adElement = html.Element.html(""" + + """); + + adContainer.append(adElement); + + // Push ad using dart:js instead of eval + try { + final adsbygoogle = js.context["adsbygoogle"]; + if (adsbygoogle != null) { + adsbygoogle.callMethod("push", [js.JsObject.jsify({})]); + } + } catch (e) { + print("Error pushing AdSense ad: $e"); + } + + return adContainer; + } + + @override + void dispose() { + _isLoaded = false; + } + + @override + bool get isLoaded => _isLoaded; +} diff --git a/lib/services/quote_api.dart b/lib/services/quote_api.dart index a678096..ffbc171 100644 --- a/lib/services/quote_api.dart +++ b/lib/services/quote_api.dart @@ -111,7 +111,10 @@ class QuoteGeneratorApi { final response = await http.post( url, - headers: {"Content-Type": "application/json"}, + headers: { + "Content-Type": "application/json", + "User-Agent": "QuoteGen-Flutter/1.0", + }, body: jsonEncode(requestBody), ); @@ -127,7 +130,10 @@ class QuoteGeneratorApi { final queryString = request.toQueryString(); final url = Uri.parse("$_baseUrl/generate?$queryString"); - final response = await http.get(url); + final response = await http.get( + url, + headers: {"User-Agent": "QuoteGen-Flutter/1.0"}, + ); if (response.statusCode == 200) { return response.bodyBytes; @@ -146,7 +152,10 @@ class QuoteGeneratorApi { static Future checkHealth() async { try { final url = Uri.parse("$_baseUrl/health"); - final response = await http.get(url); + final response = await http.get( + url, + headers: {"User-Agent": "QuoteGen-Flutter/1.0"}, + ); if (response.statusCode == 200) { final data = jsonDecode(response.body); diff --git a/lib/services/quote_api_v2.dart b/lib/services/quote_api_v2.dart index cd02263..f38ae8b 100644 --- a/lib/services/quote_api_v2.dart +++ b/lib/services/quote_api_v2.dart @@ -170,7 +170,10 @@ class QuoteGeneratorApiV2 { final response = await http.post( url, - headers: {"Content-Type": "application/json"}, + headers: { + "Content-Type": "application/json", + "User-Agent": "QuoteGen-Flutter/1.0", + }, body: jsonEncode(request.toJson()), ); @@ -178,7 +181,7 @@ class QuoteGeneratorApiV2 { final data = jsonDecode(response.body); return QuoteSession.fromJson(data); } else { - throw Exception("Failed to create sesion: ${response.statusCode}"); + throw Exception("Failed to create session: ${response.statusCode}"); } } @@ -187,7 +190,10 @@ class QuoteGeneratorApiV2 { static Future getSession(String sessionId) async { final url = Uri.parse("$_baseUrl/v2/quote/$sessionId"); - final response = await http.get(url); + final response = await http.get( + url, + headers: {"User-Agent": "QuoteGen-Flutter/1.0"}, + ); if (response.statusCode == 200) { final data = jsonDecode(response.body); @@ -206,7 +212,10 @@ class QuoteGeneratorApiV2 { final response = await http.patch( url, - headers: {"Content-Type": "application/json"}, + headers: { + "Content-Type": "application/json", + "User-Agent": "QuoteGen-Flutter/1.0", + }, body: jsonEncode(updates.toJson()), ); @@ -214,7 +223,7 @@ class QuoteGeneratorApiV2 { final data = jsonDecode(response.body); return QuoteSession.fromJson(data); } else { - throw Exception("Failed to updte session: ${response.statusCode}"); + throw Exception("Failed to update session: ${response.statusCode}"); } } @@ -222,12 +231,15 @@ class QuoteGeneratorApiV2 { static Future generateImage(String sessionId) async { final url = Uri.parse("$_baseUrl/v2/quote/$sessionId/image"); - final response = await http.get(url); + final response = await http.get( + url, + headers: {"User-Agent": "QuoteGen-Flutter/1.0"}, + ); if (response.statusCode == 200) { return response.bodyBytes; } else { - throw Exception("Failed to genrate image: ${response.statusCode}"); + throw Exception("Failed to generate image: ${response.statusCode}"); } } @@ -236,15 +248,18 @@ class QuoteGeneratorApiV2 { static Future deleteSession(String sessionId) async { final url = Uri.parse("$_baseUrl/v2/quote/$sessionId"); - final response = await http.delete(url); + final response = await http.delete( + url, + headers: {"User-Agent": "QuoteGen-Flutter/1.0"}, + ); if (response.statusCode != 204) { - throw Exception("Failed to delte session: ${response.statusCode}"); + throw Exception("Failed to delete session: ${response.statusCode}"); } } - // helper to get curren timestamp in seconds + // helper to get current timestamp in seconds static int getCurrentTimestamp() { return DateTime.now().millisecondsSinceEpoch ~/ 1000; } diff --git a/lib/widgets/ad_banner.dart b/lib/widgets/ad_banner.dart new file mode 100644 index 0000000..80b499e --- /dev/null +++ b/lib/widgets/ad_banner.dart @@ -0,0 +1,75 @@ +import "package:flutter/foundation.dart" show kIsWeb; +import "package:flutter/widgets.dart"; +import "package:shadcn_flutter/shadcn_flutter.dart"; +import "../services/ad_service.dart"; +import "../services/admob_service.dart"; +import "../services/adsense_service.dart"; + +class AdBanner extends StatefulWidget { + const AdBanner({Key? key}) : super(key: key); + + @override + State createState() => _AdBannerState(); +} + +class _AdBannerState extends State { + late AdService _adService; + bool _isLoading = true; + + @override + void initState() { + super.initState(); + _initializeAds(); + } + + Future _initializeAds() async { + // Platform detection + if (kIsWeb) { + _adService = AdSenseService(); + } else { + _adService = AdMobService(); + } + + try { + await _adService.initialize(); + await _adService.loadBannerAd(); + } catch (e) { + print("Error initializng ads: $e"); + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } + + @override + void dispose() { + _adService.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_isLoading) { + return SizedBox( + height: 50, + child: Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ); + } + + if (!_adService.isLoaded) { + // Ad failed to load, show nothing + return SizedBox.shrink(); + } + + return _adService.getBannerWidget(); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f12d843..15f40e0 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,10 +9,12 @@ import file_picker import path_provider_foundation import share_plus import shared_preferences_foundation +import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) } diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..b3927d0 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,30 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript; + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Main Flutter app + location / { + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-cache, no-store, must-revalidate"; + } + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; +} diff --git a/pubspec.lock b/pubspec.lock index afc38dc..384062c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" clock: dependency: transitive description: @@ -89,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.7" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -208,6 +224,22 @@ packages: url: "https://pub.dev" source: hosted version: "17.0.1" + google_mobile_ads: + dependency: "direct main" + description: + name: google_mobile_ads + sha256: "0d4a3744b5e8ed1b8be6a1b452d309f811688855a497c6113fc4400f922db603" + url: "https://pub.dev" + source: hosted + version: "5.3.1" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: "direct main" description: @@ -589,6 +621,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_html: + dependency: "direct main" + description: + name: universal_html + sha256: c0bcae5c733c60f26c7dfc88b10b0fd27cbcc45cb7492311cdaa6067e21c9cd4 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 + url: "https://pub.dev" + source: hosted + version: "2.3.1" url_launcher_linux: dependency: transitive description: @@ -653,6 +701,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba + url: "https://pub.dev" + source: hosted + version: "4.13.0" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: eeeb3fcd5f0ff9f8446c9f4bbc18a99b809e40297528a3395597d03aafb9f510 + url: "https://pub.dev" + source: hosted + version: "4.10.11" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" + url: "https://pub.dev" + source: hosted + version: "2.14.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: e49f378ed066efb13fc36186bbe0bd2425630d4ea0dbc71a18fdd0e4d8ed8ebc + url: "https://pub.dev" + source: hosted + version: "3.23.5" win32: dependency: transitive description: diff --git a/web/index.html b/web/index.html index 1d9b79f..bb718b9 100644 --- a/web/index.html +++ b/web/index.html @@ -31,6 +31,10 @@ quotegen_client + + +