add ad services and Docker configuration for web app
Some checks failed
Build Android App / build (push) Has been cancelled
Some checks failed
Build Android App / build (push) Has been cancelled
This commit is contained in:
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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<HomePage> {
|
||||
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<HomePage> {
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
|
||||
|
||||
Gap(max(topPadding, 14)),
|
||||
|
||||
_buildPreview(context),
|
||||
@@ -179,6 +188,11 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
_buildForm(context),
|
||||
|
||||
Gap(14),
|
||||
|
||||
// Banner ad
|
||||
AdBanner(),
|
||||
|
||||
Gap(max(bottomPadding, 14)),
|
||||
],
|
||||
),
|
||||
|
||||
19
lib/services/ad_service.dart
Normal file
19
lib/services/ad_service.dart
Normal 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;
|
||||
}
|
||||
73
lib/services/admob_service.dart
Normal file
73
lib/services/admob_service.dart
Normal 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;
|
||||
}
|
||||
3
lib/services/adsense_service.dart
Normal file
3
lib/services/adsense_service.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
// Conditional export based on platform
|
||||
export "adsense_service_stub.dart"
|
||||
if (dart.library.js_interop) "adsense_service_web.dart";
|
||||
32
lib/services/adsense_service_stub.dart
Normal file
32
lib/services/adsense_service_stub.dart
Normal 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;
|
||||
}
|
||||
80
lib/services/adsense_service_web.dart
Normal file
80
lib/services/adsense_service_web.dart
Normal 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 = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> 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("""
|
||||
<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;
|
||||
}
|
||||
@@ -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<bool> 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);
|
||||
|
||||
@@ -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<QuoteSession> 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<Uint8List> 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<void> 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;
|
||||
}
|
||||
|
||||
75
lib/widgets/ad_banner.dart
Normal file
75
lib/widgets/ad_banner.dart
Normal file
@@ -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<AdBanner> createState() => _AdBannerState();
|
||||
}
|
||||
|
||||
class _AdBannerState extends State<AdBanner> {
|
||||
late AdService _adService;
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_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() {
|
||||
_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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user