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
+
+
+