Compare commits

18 Commits
v1.0.0 ... main

Author SHA1 Message Date
ImBenji
b66b73a3c6 update base URL for QuoteGeneratorApiV2 to use localhost in debug mode
Some checks failed
Build Android App / build (push) Failing after 43s
2026-01-02 21:01:35 +00:00
ImBenji
416efe6aba update base URL for QuoteGeneratorApiV2 to use localhost in debug mode
Some checks failed
Build Android App / build (push) Failing after 47s
2026-01-02 20:56:13 +00:00
ImBenji
efa7eabfaa update base URL for QuoteGeneratorApiV2 to use localhost in debug mode
Some checks failed
Build Android App / build (push) Failing after 57s
2026-01-02 19:02:39 +00:00
ImBenji
7d8e19d1f5 add macOS support for ad loading and improve timestamp handling
Some checks failed
Build Android App / build (push) Failing after 49s
2026-01-02 18:46:12 +00:00
Ubuntu
016f7911f3 Fixed netowork
Some checks failed
Build Android App / build (push) Failing after 45s
2026-01-02 16:30:13 +00:00
ImBenji
49b18b5701 configure Gradle memory settings for improved build performance
Some checks failed
Build Android App / build (push) Failing after 47s
2026-01-02 16:25:11 +00:00
ImBenji
b4be160b93 configure Gradle memory settings for improved build performance
Some checks failed
Build Android App / build (push) Failing after 46s
2026-01-02 16:14:54 +00:00
ImBenji
0043428abf configure Gradle memory settings for improved build performance
Some checks failed
Build Android App / build (push) Failing after 28m27s
2026-01-02 15:30:45 +00:00
ImBenji
f1ce1c77a4 add ad services and Docker configuration for web app
Some checks failed
Build Android App / build (push) Has been cancelled
2026-01-02 15:21:27 +00:00
ImBenji
7a88585b6e initial
Some checks failed
Build Android App / build (push) Failing after 32m54s
2026-01-02 12:05:49 +00:00
ImBenji
4808116608 initial
Some checks failed
Build Android App / build (push) Failing after 11m41s
2026-01-01 09:22:14 +00:00
ImBenji
8932455fe0 initial
Some checks failed
Build Android App / build (push) Failing after 5m12s
2026-01-01 08:41:48 +00:00
ImBenji
ebd34f2e93 initial
Some checks failed
Build Android App / build (push) Has been cancelled
2026-01-01 08:12:43 +00:00
ImBenji
6c470d7a58 initial
Some checks failed
Build Android App / build (push) Failing after 2m46s
2026-01-01 08:01:24 +00:00
ImBenji
ef7136b58e initial
Some checks failed
Build Android App / build (push) Failing after 1m16s
2026-01-01 07:55:17 +00:00
ImBenji
e7b240655d initial
Some checks failed
Build Android App / build (push) Failing after 39s
2026-01-01 07:51:52 +00:00
ImBenji
5cbdc428e8 initial
Some checks failed
Build Android App / build (push) Failing after 41s
2026-01-01 07:50:03 +00:00
ImBenji
02d9d82575 initial
Some checks failed
Build Android App / build (push) Failing after 2m15s
2026-01-01 07:45:17 +00:00
34 changed files with 2692 additions and 250 deletions

46
.dockerignore Normal file
View File

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

View File

@@ -15,6 +15,10 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
ANDROID_HOME: /root/android-sdk
ANDROID_SDK_ROOT: /root/android-sdk
GRADLE_OPTS: -Xmx2g -XX:MaxMetaspaceSize=512m
steps: steps:
- name: Checkout code - name: Checkout code
@@ -26,16 +30,43 @@ jobs:
distribution: "zulu" distribution: "zulu"
java-version: "17" java-version: "17"
- name: Install dependencies
run: apt-get update && apt-get install -y jq wget unzip
- name: Setup Android SDK
run: |
mkdir -p /root/android-sdk/cmdline-tools
cd /root/android-sdk/cmdline-tools
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O tools.zip
unzip -q tools.zip
mv cmdline-tools latest
rm tools.zip
yes | /root/android-sdk/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null 2>&1 || true
/root/android-sdk/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
- name: Setup Flutter - name: Setup Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: "3.27.1" flutter-version: "3.36.0-0.5.pre"
channel: "stable" channel: "beta"
cache: true
- name: Fix flutter git ownership
run: git config --global --add safe.directory '*'
- name: Configure Flutter
run: |
flutter config --android-sdk /root/android-sdk
flutter doctor -v
- name: Get dependancies - name: Get dependancies
run: flutter pub get run: flutter pub get
- name: Configure Gradle memory
run: |
echo "org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m" >> android/gradle.properties
echo "org.gradle.daemon=true" >> android/gradle.properties
- name: Build APK - name: Build APK
run: flutter build apk --release run: flutter build apk --release

28
Dockerfile Normal file
View File

@@ -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
# 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;"]

View File

@@ -1,8 +1,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="quotegen_client" android:label="quotegen_client"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<!-- AdMob App ID -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-5177609929140951~7846774739"/>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@@ -19,8 +19,8 @@ pluginManagement {
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false id("com.android.application") version "8.3.2" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false id("org.jetbrains.kotlin.android") version "1.9.22" apply false
} }
include(":app") include(":app")

3
devtools_options.yaml Normal file
View File

@@ -0,0 +1,3 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

23
docker-compose.yml Normal file
View File

@@ -0,0 +1,23 @@
services:
quotegen-web:
build:
context: .
dockerfile: Dockerfile
container_name: quotegen_web
ports:
- "8087:80"
restart: unless-stopped
environment:
- TZ=UTC
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
- nginx_proxy_manager_default
networks:
nginx_proxy_manager_default:
external: true

View File

@@ -34,19 +34,44 @@ PODS:
- DKImagePickerController/PhotoGallery - DKImagePickerController/PhotoGallery
- Flutter - Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- Google-Mobile-Ads-SDK (11.13.0):
- GoogleUserMessagingPlatform (>= 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
- SDWebImage (5.21.1): - SDWebImage (5.21.1):
- SDWebImage/Core (= 5.21.1) - SDWebImage/Core (= 5.21.1)
- SDWebImage/Core (5.21.1) - SDWebImage/Core (5.21.1)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- SwiftyGif (5.4.5) - SwiftyGif (5.4.5)
- webview_flutter_wkwebview (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES: DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - 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: SPEC REPOS:
trunk: trunk:
- DKImagePickerController - DKImagePickerController
- DKPhotoGallery - DKPhotoGallery
- Google-Mobile-Ads-SDK
- GoogleUserMessagingPlatform
- SDWebImage - SDWebImage
- SwiftyGif - SwiftyGif
@@ -55,14 +80,31 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/file_picker/ios" :path: ".symlinks/plugins/file_picker/ios"
Flutter: Flutter:
:path: 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: SPEC CHECKSUMS:
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49 file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
Google-Mobile-Ads-SDK: 14f57f2dc33532a24db288897e26494640810407
google_mobile_ads: fe0e2c1764ad95323dd0e3081d0bb2d58411f957
GoogleUserMessagingPlatform: befe603da6501006420c206222acd449bba45a9c
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
SDWebImage: f29024626962457f3470184232766516dee8dfea SDWebImage: f29024626962457f3470184232766516dee8dfea
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e

View File

@@ -199,6 +199,7 @@
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
202FD1FBBA50227619E09BFC /* [CP] Embed Pods Frameworks */, 202FD1FBBA50227619E09BFC /* [CP] Embed Pods Frameworks */,
9EB32ED1B9C2EE1090869288 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
@@ -340,6 +341,23 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 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 */ = { F9A7E4B35A518B03A03269AF /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;

View File

@@ -45,5 +45,14 @@
<true/> <true/>
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-5177609929140951~5220611390</string>
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
</array>
</dict> </dict>
</plist> </plist>

View File

@@ -1,9 +1,18 @@
import 'package:flutter/material.dart'; import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:quotegen_client/pages/home/page.dart'; import 'package:quotegen_client/pages/home/page.dart';
import 'package:shadcn_flutter/shadcn_flutter.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 (not web or macOS)
if (!kIsWeb && !Platform.isMacOS) {
await MobileAds.instance.initialize();
}
void main() {
runApp(const MyApp()); runApp(const MyApp());
} }
@@ -14,8 +23,16 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ShadcnApp.router( return ShadcnApp.router(
title: 'Flutter Demo', title: 'TweetForge',
routerConfig: _router, routerConfig: _router,
scaling: defaultTargetPlatform == TargetPlatform.android ? AdaptiveScaling.only(
sizeScaling: 1,
textScaling: 1.1,
radiusScaling: 1.1
) : null,
theme: ThemeData(
colorScheme: ColorSchemes.darkBlue,
),
); );
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
import "package:flutter/widgets.dart";
abstract class AdService {
/// Initialize ad SDK
Future<void> initialize();
/// Load banner ad
Future<void> loadBannerAd();
/// Get banner widget to display
Widget getBannerWidget();
/// Dispose resources
void dispose();
/// Check if ads are loaded
bool get isLoaded;
}

View File

@@ -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<void> initialize() async {
await MobileAds.instance.initialize();
print("AdMob initialized");
}
@override
Future<void> 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;
}

View File

@@ -0,0 +1,3 @@
// Conditional export based on platform
export "adsense_service_stub.dart"
if (dart.library.js_interop) "adsense_service_web.dart";

View File

@@ -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<void> initialize() async {
// Not supported on non-web platforms
_isLoaded = false;
}
@override
Future<void> 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;
}

View File

@@ -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<void> initialize() async {
// AdSense is loaded via script tag in index.html
print("AdSense initialized (web)");
_isLoaded = false;
}
@override
Future<void> loadBannerAd() async {
// AdSense ads are loaded automatically when widget renders
_isLoaded = false;
}
@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("""
<ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-XXXXXXXXXXXXXXXX"
data-ad-slot="$_adSlotId"
data-ad-format="auto"
data-full-width-responsive="true"></ins>
""");
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;
}

View File

@@ -3,6 +3,29 @@ import "dart:typed_data";
import "package:http/http.dart" as http; import "package:http/http.dart" as http;
class QuoteEngagement {
final int? likes;
final int? retweets;
final int? replies;
final int? views;
QuoteEngagement({
this.likes,
this.retweets,
this.replies,
this.views,
});
Map<String, dynamic> toJson() {
return {
if (likes != null) "likes": likes,
if (retweets != null) "retweets": retweets,
if (replies != null) "replies": replies,
if (views != null) "views": views,
};
}
}
class QuoteRequest { class QuoteRequest {
final String? displayName; final String? displayName;
final String? username; final String? username;
@@ -10,6 +33,8 @@ class QuoteRequest {
final String? text; final String? text;
final dynamic imageUrl; // can be String or Uint8List final dynamic imageUrl; // can be String or Uint8List
final int? timestamp; final int? timestamp;
final bool? verified;
final QuoteEngagement? engagement;
QuoteRequest({ QuoteRequest({
this.displayName, this.displayName,
@@ -18,6 +43,8 @@ class QuoteRequest {
this.text, this.text,
this.imageUrl, this.imageUrl,
this.timestamp, this.timestamp,
this.verified,
this.engagement,
}); });
@@ -40,6 +67,8 @@ class QuoteRequest {
"text": text, "text": text,
"imageUrl": _encodeImage(imageUrl), "imageUrl": _encodeImage(imageUrl),
"timestamp": timestamp, "timestamp": timestamp,
if (verified != null) "verified": verified,
if (engagement != null) "engagement": engagement!.toJson(),
}; };
} }
@@ -58,13 +87,20 @@ class QuoteRequest {
if (timestamp != null) params["timestamp"] = timestamp.toString(); if (timestamp != null) params["timestamp"] = timestamp.toString();
if (verified != null) params["verified"] = verified.toString();
// engagment is complex obj, better suited for POST reqests
if (engagement != null) {
params["engagement"] = jsonEncode(engagement!.toJson());
}
return Uri(queryParameters: params).query; return Uri(queryParameters: params).query;
} }
} }
class QuoteGeneratorApi { class QuoteGeneratorApi {
static const String _baseUrl = "https://quotes.imbenji.net"; // static const String _baseUrl = "https://quotes.imbenji.net";
// static const String _baseUrl = "http://localhost:3000"; static const String _baseUrl = "http://localhost:3000";
// genrate a quote image using POST // genrate a quote image using POST
@@ -75,7 +111,10 @@ class QuoteGeneratorApi {
final response = await http.post( final response = await http.post(
url, url,
headers: {"Content-Type": "application/json"}, headers: {
"Content-Type": "application/json",
"User-Agent": "QuoteGen-Flutter/1.0",
},
body: jsonEncode(requestBody), body: jsonEncode(requestBody),
); );
@@ -91,7 +130,10 @@ class QuoteGeneratorApi {
final queryString = request.toQueryString(); final queryString = request.toQueryString();
final url = Uri.parse("$_baseUrl/generate?$queryString"); 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) { if (response.statusCode == 200) {
return response.bodyBytes; return response.bodyBytes;
@@ -110,7 +152,10 @@ class QuoteGeneratorApi {
static Future<bool> checkHealth() async { static Future<bool> checkHealth() async {
try { try {
final url = Uri.parse("$_baseUrl/health"); 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) { if (response.statusCode == 200) {
final data = jsonDecode(response.body); final data = jsonDecode(response.body);

View File

@@ -0,0 +1,328 @@
import "dart:convert";
import "dart:typed_data";
import "package:flutter/foundation.dart";
import "package:http/http.dart" as http;
class QuoteEngagement {
final int? likes;
final int? retweets;
final int? replies;
final int? views;
QuoteEngagement({
this.likes,
this.retweets,
this.replies,
this.views,
});
Map<String, dynamic> toJson() {
return {
if (likes != null) "likes": likes,
if (retweets != null) "retweets": retweets,
if (replies != null) "replies": replies,
if (views != null) "views": views,
};
}
factory QuoteEngagement.fromJson(Map<String, dynamic> json) {
return QuoteEngagement(
likes: json["likes"],
retweets: json["retweets"],
replies: json["replies"],
views: json["views"],
);
}
}
class QuoteSessionRequest {
final String? displayName;
final String? username;
final dynamic avatarUrl;
final String? text;
final dynamic imageUrl;
final int? timestamp;
final bool? verified;
final QuoteEngagement? engagement;
final bool clearAvatarUrl;
final bool clearImageUrl;
final bool clearEngagement;
QuoteSessionRequest({
this.displayName,
this.username,
this.avatarUrl,
this.text,
this.imageUrl,
this.timestamp,
this.verified,
this.engagement,
this.clearAvatarUrl = false,
this.clearImageUrl = false,
this.clearEngagement = false,
});
// convert img bytes to base64 data uri
String? _encodeImage(dynamic image) {
if (image == null) return null;
if (image is String) return image;
if (image is Uint8List) {
final base64String = base64Encode(image);
return "data:image/png;base64,$base64String";
}
return null;
}
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
if (displayName != null) map["displayName"] = displayName;
if (username != null) map["username"] = username;
if (clearAvatarUrl) {
map["avatarUrl"] = null;
} else {
final encodedAvatar = _encodeImage(avatarUrl);
if (encodedAvatar != null) map["avatarUrl"] = encodedAvatar;
}
if (text != null) map["text"] = text;
if (clearImageUrl) {
map["imageUrl"] = null;
} else {
final encodedImage = _encodeImage(imageUrl);
if (encodedImage != null) map["imageUrl"] = encodedImage;
}
if (timestamp != null) map["timestamp"] = timestamp;
if (verified != null) map["verified"] = verified;
if (clearEngagement) {
map["engagement"] = null;
} else if (engagement != null) {
map["engagement"] = engagement!.toJson();
}
return map;
}
}
class QuoteSession {
final String id;
final String? displayName;
final String? username;
final String? avatarUrl;
final String? text;
final String? imageUrl;
final int? timestamp;
final bool? verified;
final QuoteEngagement? engagement;
final int createdAt;
final int updatedAt;
QuoteSession({
required this.id,
this.displayName,
this.username,
this.avatarUrl,
this.text,
this.imageUrl,
this.timestamp,
this.verified,
this.engagement,
required this.createdAt,
required this.updatedAt,
});
factory QuoteSession.fromJson(Map<String, dynamic> json) {
return QuoteSession(
id: json["id"],
displayName: json["displayName"],
username: json["username"],
avatarUrl: json["avatarUrl"],
text: json["text"],
imageUrl: json["imageUrl"],
timestamp: json["timestamp"],
verified: json["verified"],
engagement: json["engagement"] != null
? QuoteEngagement.fromJson(json["engagement"])
: null,
createdAt: json["createdAt"],
updatedAt: json["updatedAt"],
);
}
}
class QuoteSnapshot {
final String token;
final String url;
final String sessionId;
final int createdAt;
final int expiresAt;
QuoteSnapshot({
required this.token,
required this.url,
required this.sessionId,
required this.createdAt,
required this.expiresAt,
});
factory QuoteSnapshot.fromJson(Map<String, dynamic> json) {
return QuoteSnapshot(
token: json["token"],
url: json["url"],
sessionId: json["sessionId"],
createdAt: json["createdAt"],
expiresAt: json["expiresAt"],
);
}
}
class QuoteGeneratorApiV2 {
// static const String _baseUrl = "https://quotes.imbenji.net";
static const String _baseUrl = kDebugMode ? "http://localhost:3000" : "https://quotes.imbenji.net";
// create new session
static Future<QuoteSession> createSession(QuoteSessionRequest request) async {
final url = Uri.parse("$_baseUrl/v2/quote");
final response = await http.post(
url,
headers: {
"Content-Type": "application/json",
"User-Agent": "QuoteGen-Flutter/1.0",
},
body: jsonEncode(request.toJson()),
);
if (response.statusCode == 201) {
final data = jsonDecode(response.body);
return QuoteSession.fromJson(data);
} else {
throw Exception("Failed to create session: ${response.statusCode}");
}
}
// get session state
static Future<QuoteSession> getSession(String sessionId) async {
final url = Uri.parse("$_baseUrl/v2/quote/$sessionId");
final response = await http.get(
url,
headers: {"User-Agent": "QuoteGen-Flutter/1.0"},
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return QuoteSession.fromJson(data);
} else {
throw Exception("Failed to get session: ${response.statusCode}");
}
}
// update session fields (only send whats changed)
static Future<QuoteSession> updateSession(
String sessionId,
QuoteSessionRequest updates,
) async {
final url = Uri.parse("$_baseUrl/v2/quote/$sessionId");
final response = await http.patch(
url,
headers: {
"Content-Type": "application/json",
"User-Agent": "QuoteGen-Flutter/1.0",
},
body: jsonEncode(updates.toJson()),
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return QuoteSession.fromJson(data);
} else {
throw Exception("Failed to update session: ${response.statusCode}");
}
}
// render the session as image
static Future<Uint8List> generateImage(String sessionId) async {
final url = Uri.parse("$_baseUrl/v2/quote/$sessionId/image");
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 generate image: ${response.statusCode}");
}
}
// delete session
static Future<void> deleteSession(String sessionId) async {
final url = Uri.parse("$_baseUrl/v2/quote/$sessionId");
final response = await http.delete(
url,
headers: {"User-Agent": "QuoteGen-Flutter/1.0"},
);
if (response.statusCode != 204) {
throw Exception("Failed to delete session: ${response.statusCode}");
}
}
// create persistant snapshot link
static Future<QuoteSnapshot> createSnapshotLink(String sessionId) async {
final url = Uri.parse("$_baseUrl/v2/quote/$sessionId/snapshot-link");
final response = await http.post(
url,
headers: {"User-Agent": "QuoteGen-Flutter/1.0"},
);
if (response.statusCode == 201) {
final data = jsonDecode(response.body);
return QuoteSnapshot.fromJson(data);
} else {
throw Exception("Failed to create snapshot link: ${response.statusCode}");
}
}
// get snapshot image by token
static Future<Uint8List> getSnapshot(String token) async {
final url = Uri.parse("$_baseUrl/v2/snapshot/$token");
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 get snapshot: ${response.statusCode}");
}
}
// helper to get current timestamp in seconds
static int getCurrentTimestamp() {
return DateTime.now().millisecondsSinceEpoch ~/ 1000;
}
}

109
lib/widgets/ad_banner.dart Normal file
View File

@@ -0,0 +1,109 @@
import "dart:io" show Platform;
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<AdBanner> createState() => _AdBannerState();
}
class _AdBannerState extends State<AdBanner> {
late AdService _adService;
bool _isLoading = true;
bool _isMacOS = false;
@override
void initState() {
super.initState();
// check if running on macOS
if (!kIsWeb && Platform.isMacOS) {
_isMacOS = true;
_isLoading = false;
} else {
_initializeAds();
}
}
Future<void> _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() {
if (!_isMacOS) {
_adService.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
// Check if we're on mobile (iOS/Android)
bool isMobile = !kIsWeb && !_isMacOS && (Platform.isIOS || Platform.isAndroid);
// Show placeholder only on macOS and web when ads fail to load
// On mobile, we want real ads or nothing
if (_isMacOS || (!isMobile && !_isLoading && !_adService.isLoaded)) {
return ConstrainedBox(
constraints: const BoxConstraints(
minHeight: 80,
),
child: OutlinedContainer(
child: Center(
child: Text(
"ADVERTISEMENT SPACE",
style: TextStyle(
color: Theme.of(context).colorScheme.mutedForeground,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
),
),
).withPadding(
horizontal: 8
);
}
if (_isLoading) {
return SizedBox(
height: 50,
child: Center(
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
),
),
);
}
return _adService.getBannerWidget();
}
}

View File

@@ -6,6 +6,14 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_saver_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin");
file_saver_plugin_register_with_registrar(file_saver_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
} }

View File

@@ -3,6 +3,8 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_saver
url_launcher_linux
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@@ -6,7 +6,17 @@ import FlutterMacOS
import Foundation import Foundation
import file_picker import file_picker
import file_saver
import path_provider_foundation
import share_plus
import shared_preferences_foundation
import webview_flutter_wkwebview
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin"))
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"))
} }

49
macos/Podfile.lock Normal file
View File

@@ -0,0 +1,49 @@
PODS:
- file_picker (0.0.1):
- FlutterMacOS
- FlutterMacOS (1.0.0)
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- share_plus (0.0.1):
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- webview_flutter_wkwebview (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
- FlutterMacOS (from `Flutter/ephemeral`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`)
EXTERNAL SOURCES:
file_picker:
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
FlutterMacOS:
:path: Flutter/ephemeral
path_provider_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
share_plus:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
webview_flutter_wkwebview:
:path: Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin
SPEC CHECKSUMS:
file_picker: e716a70a9fe5fd9e09ebc922d7541464289443af
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
share_plus: 1fa619de8392a4398bfaf176d441853922614e89
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
webview_flutter_wkwebview: 29eb20d43355b48fe7d07113835b9128f84e3af4
PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009
COCOAPODS: 1.16.2

View File

@@ -27,6 +27,8 @@
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
4B88E3BA5783FAAA20C55FF8 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B1D06A2639F3E3C2D980953 /* Pods_RunnerTests.framework */; };
66220A6CD753CFE95C5E895B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2275505DFB8F93088ED6B919 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -60,11 +62,15 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
027FCB389F2CAAB3130196E9 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
02934033A4D23A11AC357D9C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
1B1D06A2639F3E3C2D980953 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2275505DFB8F93088ED6B919 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* quotegen_client.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "quotegen_client.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10ED2044A3C60003C045 /* quotegen_client.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = quotegen_client.app; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@@ -76,8 +82,12 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
5348D45CFF6462B1764A19C6 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
BCF862636D90C513DDDEC9AC /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
BF503F87C67D76C2AAECB687 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
F88395ACF672EC99D7216FE7 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -85,6 +95,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
4B88E3BA5783FAAA20C55FF8 /* Pods_RunnerTests.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -92,12 +103,27 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
66220A6CD753CFE95C5E895B /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
1B8C4A4125941B1E08A389DC /* Pods */ = {
isa = PBXGroup;
children = (
02934033A4D23A11AC357D9C /* Pods-Runner.debug.xcconfig */,
5348D45CFF6462B1764A19C6 /* Pods-Runner.release.xcconfig */,
BF503F87C67D76C2AAECB687 /* Pods-Runner.profile.xcconfig */,
027FCB389F2CAAB3130196E9 /* Pods-RunnerTests.debug.xcconfig */,
BCF862636D90C513DDDEC9AC /* Pods-RunnerTests.release.xcconfig */,
F88395ACF672EC99D7216FE7 /* Pods-RunnerTests.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
331C80D6294CF71000263BE5 /* RunnerTests */ = { 331C80D6294CF71000263BE5 /* RunnerTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -125,6 +151,7 @@
331C80D6294CF71000263BE5 /* RunnerTests */, 331C80D6294CF71000263BE5 /* RunnerTests */,
33CC10EE2044A3C60003C045 /* Products */, 33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */, D73912EC22F37F3D000D13A0 /* Frameworks */,
1B8C4A4125941B1E08A389DC /* Pods */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@@ -175,6 +202,8 @@
D73912EC22F37F3D000D13A0 /* Frameworks */ = { D73912EC22F37F3D000D13A0 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
2275505DFB8F93088ED6B919 /* Pods_Runner.framework */,
1B1D06A2639F3E3C2D980953 /* Pods_RunnerTests.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -186,6 +215,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = ( buildPhases = (
2910AB2B6910A71DCB69BE84 /* [CP] Check Pods Manifest.lock */,
331C80D1294CF70F00263BE5 /* Sources */, 331C80D1294CF70F00263BE5 /* Sources */,
331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D2294CF70F00263BE5 /* Frameworks */,
331C80D3294CF70F00263BE5 /* Resources */, 331C80D3294CF70F00263BE5 /* Resources */,
@@ -204,11 +234,13 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
0E297FF21CD9AA21EB56B11C /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */, 33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */, 33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */, 33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */, 3399D490228B24CF009A79C7 /* ShellScript */,
4051E39AED5CF4F08B394FE0 /* [CP] Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@@ -291,6 +323,50 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
0E297FF21CD9AA21EB56B11C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
2910AB2B6910A71DCB69BE84 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3399D490228B24CF009A79C7 /* ShellScript */ = { 3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1; alwaysOutOfDate = 1;
@@ -329,6 +405,23 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
}; };
4051E39AED5CF4F08B394FE0 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
@@ -380,6 +473,7 @@
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
331C80DB294CF71000263BE5 /* Debug */ = { 331C80DB294CF71000263BE5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 027FCB389F2CAAB3130196E9 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@@ -394,6 +488,7 @@
}; };
331C80DC294CF71000263BE5 /* Release */ = { 331C80DC294CF71000263BE5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = BCF862636D90C513DDDEC9AC /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
@@ -408,6 +503,7 @@
}; };
331C80DD294CF71000263BE5 /* Profile */ = { 331C80DD294CF71000263BE5 /* Profile */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = F88395ACF672EC99D7216FE7 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;

View File

@@ -4,4 +4,7 @@
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "group:Runner.xcodeproj">
</FileRef> </FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace> </Workspace>

View File

@@ -8,5 +8,9 @@
<true/> <true/>
<key>com.apple.security.network.server</key> <key>com.apple.security.network.server</key>
<true/> <true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@@ -4,5 +4,9 @@
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict> </dict>
</plist> </plist>

30
nginx.conf Normal file
View File

@@ -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;
}

View File

@@ -41,6 +41,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -89,6 +97,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.7" version: "3.0.7"
csslib:
dependency: transitive
description:
name: csslib
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -105,6 +121,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.2" version: "0.0.2"
dio:
dependency: transitive
description:
name: dio
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
url: "https://pub.dev"
source: hosted
version: "5.9.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
email_validator: email_validator:
dependency: transitive dependency: transitive
description: description:
@@ -137,6 +169,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
file_picker: file_picker:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -145,6 +185,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.3.7" version: "8.3.7"
file_saver:
dependency: "direct main"
description:
name: file_saver
sha256: "017a127de686af2d2fbbd64afea97052d95f2a0f87d19d25b87e097407bf9c1e"
url: "https://pub.dev"
source: hosted
version: "0.2.14"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -192,6 +248,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "17.0.1" 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: http:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -216,6 +288,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.2" version: "4.7.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
url: "https://pub.dev"
source: hosted
version: "0.19.0"
jovial_misc: jovial_misc:
dependency: transitive dependency: transitive
description: description:
@@ -296,6 +376,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@@ -304,6 +392,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.9.1"
path_provider:
dependency: "direct main"
description:
name: path_provider
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
url: "https://pub.dev"
source: hosted
version: "2.1.5"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e
url: "https://pub.dev"
source: hosted
version: "2.2.22"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4"
url: "https://pub.dev"
source: hosted
version: "2.5.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@@ -320,6 +456,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.4" version: "0.0.4"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -360,6 +504,78 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.47" version: "0.0.47"
share_plus:
dependency: "direct main"
description:
name: share_plus
sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da
url: "https://pub.dev"
source: hosted
version: "10.1.4"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b
url: "https://pub.dev"
source: hosted
version: "5.0.2"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc"
url: "https://pub.dev"
source: hosted
version: "2.4.18"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
skeletonizer: skeletonizer:
dependency: transitive dependency: transitive
description: description:
@@ -429,6 +645,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" 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:
name: url_launcher_linux
sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
uuid:
dependency: transitive
description:
name: uuid
sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
url: "https://pub.dev"
source: hosted
version: "4.5.2"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@@ -453,6 +725,38 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" 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: win32:
dependency: transitive dependency: transitive
description: description:
@@ -461,6 +765,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.15.0" version: "5.15.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml: xml:
dependency: transitive dependency: transitive
description: description:

View File

@@ -35,6 +35,15 @@ dependencies:
http: ^1.2.2 http: ^1.2.2
file_picker: ^8.1.6 file_picker: ^8.1.6
image: ^4.3.0 image: ^4.3.0
share_plus: ^10.1.4
path_provider: ^2.1.5
shared_preferences: ^2.3.4
intl: ^0.19.0
file_saver: ^0.2.14
# Ad integration
google_mobile_ads: ^5.2.0
universal_html: ^2.2.4
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.

View File

@@ -31,6 +31,10 @@
<title>quotegen_client</title> <title>quotegen_client</title>
<link rel="manifest" href="manifest.json"> <link rel="manifest" href="manifest.json">
<!-- Google AdSense -->
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXXXXXXXX"
crossorigin="anonymous"></script>
</head> </head>
<body> <body>
<script src="flutter_bootstrap.js" async></script> <script src="flutter_bootstrap.js" async></script>

View File

@@ -6,6 +6,15 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <file_saver/file_saver_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FileSaverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSaverPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View File

@@ -3,6 +3,9 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
file_saver
share_plus
url_launcher_windows
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST