Skip to content

Commit

Permalink
Add prototype of Google AdMob ads (#1760)
Browse files Browse the repository at this point in the history
## Description

This pull request adds Google AdMob for displaying ads in Sharezone.
It's an experiment that we will run in the next weeks. Because we set
the
[`tagForUnderAgeOfConsent`](https://developers.google.com/admob/flutter/privacy/gdpr#tag_for_under_age_of_consent)
to `true`, we don't need to show a consent dialog because only ["limited
ads"](https://developers.google.com/admob/flutter/privacy/ad-serving-modes#limited_ads)
will be displayed.

Use the activation code "ads" to enable ads.

## Demo

| Dashboard | Timetable | Homework | Info Screen |
|--------|--------|-----|---------|
|
![Screenshot_20241012-221743](https://github.com/user-attachments/assets/280dadcf-4272-4507-b99c-fe31a7093521)
|
![Screenshot_20241012-222240](https://github.com/user-attachments/assets/744fba56-aa97-49e0-9c13-deaf5873dadf)
|
![Screenshot_20241012-222232](https://github.com/user-attachments/assets/e812351b-22f9-4f0e-b682-9625d13e3d82)
|
![Screenshot_20241013-184158](https://github.com/user-attachments/assets/6920ff44-0f2f-4b38-91f2-538625bf57b4)
|

Additionally, we show after every fifth homework check a full-screen ad:


https://github.com/user-attachments/assets/62ca4b3f-c30a-4835-b10d-678fddf48f1a
  • Loading branch information
nilsreichardt authored Oct 14, 2024
1 parent 33b4813 commit 2a07153
Show file tree
Hide file tree
Showing 37 changed files with 2,000 additions and 547 deletions.
3 changes: 3 additions & 0 deletions app/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
</queries>

<application android:requestLegacyExternalStorage="true" android:label="@string/app_name" android:icon="@mipmap/ic_launcher">
<!-- From: https://developers.google.com/admob/flutter/quick-start#platform_specific_setup -->
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-7730914075870960~2331360962"/>

<meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/statusbar_notification" />
<meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/colorAccent" />
<meta-data android:name="flutterEmbedding" android:value="2" />
Expand Down
15 changes: 15 additions & 0 deletions app/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ PODS:
- SDWebImageWebPCoder
- flutter_timezone (0.0.1):
- Flutter
- Google-Mobile-Ads-SDK (11.10.0):
- GoogleUserMessagingPlatform (>= 1.1)
- google_mobile_ads (5.2.0):
- Flutter
- Google-Mobile-Ads-SDK (~> 11.10.0)
- webview_flutter_wkwebview
- google_sign_in_ios (0.0.1):
- AppAuth (>= 1.7.4)
- Flutter
Expand Down Expand Up @@ -304,6 +310,7 @@ PODS:
- GoogleToolboxForMac/Defines (= 4.2.1)
- "GoogleToolboxForMac/NSData+zlib (4.2.1)":
- GoogleToolboxForMac/Defines (= 4.2.1)
- GoogleUserMessagingPlatform (2.6.0)
- GoogleUtilities/AppDelegateSwizzler (7.13.3):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
Expand Down Expand Up @@ -462,6 +469,7 @@ DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_image_compress_common (from `.symlinks/plugins/flutter_image_compress_common/ios`)
- flutter_timezone (from `.symlinks/plugins/flutter_timezone/ios`)
- google_mobile_ads (from `.symlinks/plugins/google_mobile_ads/ios`)
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
Expand Down Expand Up @@ -515,11 +523,13 @@ SPEC REPOS:
- FirebaseSessions
- FirebaseSharedSwift
- FirebaseStorage
- Google-Mobile-Ads-SDK
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleMLKit
- GoogleSignIn
- GoogleToolboxForMac
- GoogleUserMessagingPlatform
- GoogleUtilities
- GoogleUtilitiesComponents
- GTMAppAuth
Expand Down Expand Up @@ -581,6 +591,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_image_compress_common/ios"
flutter_timezone:
:path: ".symlinks/plugins/flutter_timezone/ios"
google_mobile_ads:
:path: ".symlinks/plugins/google_mobile_ads/ios"
google_sign_in_ios:
:path: ".symlinks/plugins/google_sign_in_ios/darwin"
image_picker_ios:
Expand Down Expand Up @@ -676,12 +688,15 @@ SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e
flutter_timezone: ffb07bdad3c6276af8dada0f11978d8a1f8a20bb
Google-Mobile-Ads-SDK: 13e6e98edfd78ad8d8a791edb927658cc260a56f
google_mobile_ads: 2a538d8e42b1813809782792e48f8cf4374c2180
google_sign_in_ios: 07375bfbf2620bc93a602c0e27160d6afc6ead38
GoogleAppMeasurement: 9abf64b682732fed36da827aa2a68f0221fd2356
GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a
GoogleMLKit: 97ac7af399057e99182ee8edfa8249e3226a4065
GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db
GoogleToolboxForMac: d1a2cbf009c453f4d6ded37c105e2f67a32206d8
GoogleUserMessagingPlatform: 0c3a08353e53ce8c2feab7addd0b652cde522450
GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15
GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
Expand Down
6 changes: 6 additions & 0 deletions app/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,10 @@
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseFirestoreGRPCCPPBinary/FirebaseFirestoreGRPCCPPBinary_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseFirestoreGRPCCoreBinary/FirebaseFirestoreGRPCCoreBinary_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/FirebaseFirestoreInternalBinary/FirebaseFirestoreInternalBinary_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/Google-Mobile-Ads-SDK/GoogleMobileAdsResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUserMessagingPlatform/UserMessagingPlatformResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/firebase_messaging/firebase_messaging_Privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/google_mobile_ads/google_mobile_ads.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/google_sign_in_ios/google_sign_in_ios_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/mobile_scanner/mobile_scanner_privacy.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/permission_handler_apple/permission_handler_apple_privacy.bundle",
Expand All @@ -488,7 +491,10 @@
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseFirestoreGRPCCPPBinary_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseFirestoreGRPCCoreBinary_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FirebaseFirestoreInternalBinary_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMobileAdsResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/UserMessagingPlatformResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/firebase_messaging_Privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/google_mobile_ads.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/google_sign_in_ios_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/mobile_scanner_privacy.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/permission_handler_apple_privacy.bundle",
Expand Down
2 changes: 2 additions & 0 deletions app/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,7 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-7730914075870960~4953718516</string>
</dict>
</plist>
21 changes: 21 additions & 0 deletions app/lib/activation_code/src/bloc/enter_activation_code_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,33 @@ class EnterActivationCodeBloc extends BlocBase {
return;
}

// Required for testing as long we run the A/B test.
//
// In case you are in A/B test group, you can't deactivate the ads by
// entering 'ads' in the activation code field.
if (_lastEnteredValue?.trim().toLowerCase() == 'ads') {
_toggleAds();
return;
}

_changeEnterActivationCodeResult(LoadingEnterActivationCodeResult());

final enterActivationCodeResult = await _runAppFunction(enteredValue);
_changeEnterActivationCodeResult(enterActivationCodeResult);
}

void _toggleAds() {
final currentValue = keyValueStore.getBool('show-ads') ?? false;
keyValueStore.setBool('show-ads', !currentValue);

_changeEnterActivationCodeResult(
SuccessfulEnterActivationCodeResult(
'ads',
'Ads wurden ${!currentValue ? 'aktiviert' : 'deaktiviert'}. Starte die App neu, um die Änderungen zu sehen.',
),
);
}

Future<void> _clearCache(BuildContext context) async {
await Future.wait([
keyValueStore.clear(),
Expand Down
85 changes: 85 additions & 0 deletions app/lib/ads/ad_banner.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt)
// Licensed under the EUPL-1.2-or-later.
//
// You may obtain a copy of the Licence at:
// https://joinup.ec.europa.eu/software/page/eupl
//
// SPDX-License-Identifier: EUPL-1.2

import 'package:flutter/widgets.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:provider/provider.dart';
import 'package:sharezone/ads/ads_controller.dart';

class AdBanner extends StatefulWidget {
const AdBanner({
super.key,
required this.adUnitId,
});

final String adUnitId;

@override
State<AdBanner> createState() => _AdBannerState();
}

class _AdBannerState extends State<AdBanner> {
BannerAd? ad;
bool _isLoaded = false;

@override
void initState() {
super.initState();
if (context.read<AdsController>().isQualifiedForAds()) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final size =
await AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(
MediaQuery.sizeOf(context).width.truncate());

if (size == null) {
// Unable to get width of anchored banner.
return;
}

if (!mounted) {
return;
}

ad = BannerAd(
adUnitId: widget.adUnitId,
request: context.read<AdsController>().createAdRequest(),
size: size,
listener: BannerAdListener(
onAdLoaded: (ad) {
debugPrint('$ad loaded.');
setState(() {
_isLoaded = true;
});
},
onAdFailedToLoad: (ad, err) {
debugPrint('BannerAd failed to load: $err');
ad.dispose();
},
),
)..load();
});
}
}

@override
void dispose() {
ad?.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
final areAdsVisible = context.watch<AdsController>().areAdsVisible;
if (!areAdsVisible || _isLoaded == false) return Container();
return SizedBox(
height: ad!.size.height.toDouble(),
width: ad!.size.width.toDouble(),
child: AdWidget(ad: ad!),
);
}
}
56 changes: 56 additions & 0 deletions app/lib/ads/ad_info_dialog.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2024 Sharezone UG (haftungsbeschränkt)
// Licensed under the EUPL-1.2-or-later.
//
// You may obtain a copy of the Licence at:
// https://joinup.ec.europa.eu/software/page/eupl
//
// SPDX-License-Identifier: EUPL-1.2

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:sharezone/sharezone_plus/page/sharezone_plus_page.dart';
import 'package:sharezone_widgets/sharezone_widgets.dart';

void showAdInfoDialog(BuildContext context) async {
final navigateToPlusPage = await showDialog<bool>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Werbung in Sharezone'),
content: Text.rich(
TextSpan(
children: [
const TextSpan(
text:
'Innerhalb der nächsten Wochen führen wir ein Experiment mit Werbung in Sharezone durch. Wenn du keine Werbung sehen möchten, kannst du ',
),
TextSpan(
text: 'Sharezone Plus',
style: const TextStyle(
color: primaryColor,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () => Navigator.of(context).pop(true),
),
const TextSpan(
text: ' erwerben.',
),
],
),
),
actions: <Widget>[
ElevatedButton(
style: ElevatedButton.styleFrom(foregroundColor: Colors.white),
child: const Text('OK'),
onPressed: () => Navigator.of(context).pop(),
),
],
);
},
);

if (navigateToPlusPage == true && context.mounted) {
navigateToSharezonePlusPage(context);
}
}
Loading

0 comments on commit 2a07153

Please sign in to comment.