mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 16:56:19 +01:00
Add AdMob
This commit is contained in:
parent
cfc1bf34ec
commit
e1bfca432e
22 changed files with 538 additions and 6 deletions
|
@ -59,5 +59,8 @@
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.geo.API_KEY"
|
android:name="com.google.android.geo.API_KEY"
|
||||||
android:value="" />
|
android:value="" />
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.ads.APPLICATION_ID"
|
||||||
|
android:value="" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
19
app/lib/ad_helper.dart
Normal file
19
app/lib/ad_helper.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:np_platform_util/np_platform_util.dart';
|
||||||
|
|
||||||
|
class AdHelper {
|
||||||
|
static String get bannerAdUnitId {
|
||||||
|
if (getRawPlatform() == NpPlatform.android) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
throw UnsupportedError("Unsupported platform");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String get rewardedAdUnitId {
|
||||||
|
if (getRawPlatform() == NpPlatform.android) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
throw UnsupportedError("Unsupported platform");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
@ -84,6 +85,8 @@ Future<void> init(InitIsolateType isolateType) async {
|
||||||
// init session storage
|
// init session storage
|
||||||
SessionStorage();
|
SessionStorage();
|
||||||
|
|
||||||
|
await _initAds();
|
||||||
|
|
||||||
_hasInitedInThisIsolate = true;
|
_hasInitedInThisIsolate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,5 +232,9 @@ Future<Pref> _createSecurePref() async {
|
||||||
return Pref.scoped(provider);
|
return Pref.scoped(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<InitializationStatus> _initAds() {
|
||||||
|
return MobileAds.instance.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
final _log = Logger("app_init");
|
final _log = Logger("app_init");
|
||||||
var _hasInitedInThisIsolate = false;
|
var _hasInitedInThisIsolate = false;
|
||||||
|
|
|
@ -118,6 +118,7 @@ enum PrefKey implements PrefKeyInterface {
|
||||||
viewerAppBarButtons,
|
viewerAppBarButtons,
|
||||||
viewerBottomAppBarButtons,
|
viewerBottomAppBarButtons,
|
||||||
homeCollectionsNavBarButtons,
|
homeCollectionsNavBarButtons,
|
||||||
|
lastAdRewardTime,
|
||||||
;
|
;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -218,6 +219,8 @@ enum PrefKey implements PrefKeyInterface {
|
||||||
return "viewerBottomAppBarButtons";
|
return "viewerBottomAppBarButtons";
|
||||||
case PrefKey.homeCollectionsNavBarButtons:
|
case PrefKey.homeCollectionsNavBarButtons:
|
||||||
return "homeCollectionsNavBarButtons";
|
return "homeCollectionsNavBarButtons";
|
||||||
|
case PrefKey.lastAdRewardTime:
|
||||||
|
return "lastAdRewardTime";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -165,6 +165,13 @@ extension PrefExtension on Pref {
|
||||||
(key, value) => provider.setBool(key, value));
|
(key, value) => provider.setBool(key, value));
|
||||||
|
|
||||||
bool? isNewHttpEngine() => provider.getBool(PrefKey.isNewHttpEngine);
|
bool? isNewHttpEngine() => provider.getBool(PrefKey.isNewHttpEngine);
|
||||||
|
|
||||||
|
int? getLastAdRewardTime() => provider.getInt(PrefKey.lastAdRewardTime);
|
||||||
|
int getLastAdRewardTimeOr(int def) => getLastAdRewardTime() ?? def;
|
||||||
|
Future<bool> setLastAdRewardTime(int value) => _set<int>(
|
||||||
|
PrefKey.lastAdRewardTime,
|
||||||
|
value,
|
||||||
|
(key, value) => provider.setInt(key, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AccountPrefExtension on AccountPref {
|
extension AccountPrefExtension on AccountPref {
|
||||||
|
|
|
@ -4,3 +4,5 @@ final isSupportMapView =
|
||||||
[NpPlatform.android, NpPlatform.web].contains(getRawPlatform());
|
[NpPlatform.android, NpPlatform.web].contains(getRawPlatform());
|
||||||
final isSupportSelfSignedCert = getRawPlatform() == NpPlatform.android;
|
final isSupportSelfSignedCert = getRawPlatform() == NpPlatform.android;
|
||||||
final isSupportEnhancement = getRawPlatform() == NpPlatform.android;
|
final isSupportEnhancement = getRawPlatform() == NpPlatform.android;
|
||||||
|
|
||||||
|
final isSupportAds = getRawPlatform() != NpPlatform.web;
|
||||||
|
|
217
app/lib/widget/ad.dart
Normal file
217
app/lib/widget/ad.dart
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/ad_helper.dart';
|
||||||
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
|
|
||||||
|
class AdBanner extends StatefulWidget {
|
||||||
|
const AdBanner({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
createState() => AdBannerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdBannerState extends State<AdBanner> {
|
||||||
|
@override
|
||||||
|
initState() {
|
||||||
|
super.initState();
|
||||||
|
_initPlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
build(BuildContext context) {
|
||||||
|
if (!_isAdLoading) {
|
||||||
|
_isAdLoading = true;
|
||||||
|
_createBanner(context);
|
||||||
|
}
|
||||||
|
return _buildAdContent(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dispose() {
|
||||||
|
super.dispose();
|
||||||
|
_ad?.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the height of the placeholder widget
|
||||||
|
void _initPlaceholder() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_getAdSize().then((size) {
|
||||||
|
_log.info(
|
||||||
|
"[_initPlaceholder] Placeholder size: ${size?.width}x${size?.height}");
|
||||||
|
if (size != null) {
|
||||||
|
setState(() {
|
||||||
|
_placeholderHeight = size.height;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAdContent(BuildContext context) {
|
||||||
|
if (_isLoadFailed) {
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
height: _placeholderHeight?.toDouble() ?? 77,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
const Text("Tired of ads?"),
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: AlignmentDirectional.centerEnd,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
launch(
|
||||||
|
"https://play.google.com/store/apps/details?id=com.nkming.nc_photos.paid&referrer=utm_source%3Dfreeapp");
|
||||||
|
},
|
||||||
|
child: const Text("SUPPORT THE APP"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (_ad != null) {
|
||||||
|
return Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
width: _ad!.size.width.toDouble(),
|
||||||
|
height: _ad!.size.height.toDouble(),
|
||||||
|
child: AdWidget(ad: _ad!),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (_placeholderHeight == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
} else {
|
||||||
|
return SizedBox(height: _placeholderHeight!.toDouble());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _createBanner(BuildContext context) async {
|
||||||
|
AdSize? size = await _getAdSize();
|
||||||
|
if (size == null) {
|
||||||
|
_log.severe("[_createBanner] Unable to get size of adaptive banner");
|
||||||
|
size = AdSize.banner;
|
||||||
|
}
|
||||||
|
|
||||||
|
final BannerAd banner = BannerAd(
|
||||||
|
size: size,
|
||||||
|
request: _request,
|
||||||
|
adUnitId: AdHelper.bannerAdUnitId,
|
||||||
|
listener: BannerAdListener(
|
||||||
|
onAdLoaded: (ad) {
|
||||||
|
setState(() {
|
||||||
|
_log.fine("[_createBanner] Ad loaded");
|
||||||
|
_ad = ad as BannerAd;
|
||||||
|
_log.fine(
|
||||||
|
"[_createBanner] Size: ${_ad!.size.width} * ${_ad!.size.height}");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onAdFailedToLoad: (ad, e) {
|
||||||
|
_log.shout("[_createBanner] Failed while loading ads", e);
|
||||||
|
ad.dispose();
|
||||||
|
setState(() {
|
||||||
|
_isLoadFailed = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return banner.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AdSize?> _getAdSize() => AdSize.getAnchoredAdaptiveBannerAdSize(
|
||||||
|
Orientation.portrait,
|
||||||
|
MediaQuery.of(context).size.shortestSide.truncate(),
|
||||||
|
);
|
||||||
|
|
||||||
|
BannerAd? _ad;
|
||||||
|
var _isAdLoading = false;
|
||||||
|
var _isLoadFailed = false;
|
||||||
|
int? _placeholderHeight;
|
||||||
|
|
||||||
|
static final _log = Logger("widget.ad.AdBannerState");
|
||||||
|
}
|
||||||
|
|
||||||
|
class RewardedAdHandler {
|
||||||
|
static bool isCooldownPeriod(Duration cooldown) {
|
||||||
|
final lastEpoch = Pref().getLastAdRewardTime();
|
||||||
|
if (lastEpoch == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final last = DateTime.fromMillisecondsSinceEpoch(lastEpoch).toUtc();
|
||||||
|
final now = DateTime.now().toUtc();
|
||||||
|
return last.isBefore(now) && now.difference(last) < cooldown;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init() {
|
||||||
|
_log.info("[init] Start loading ad");
|
||||||
|
_createRewarded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void show({
|
||||||
|
VoidCallback? onAdDismissedFullScreenContent,
|
||||||
|
void Function(AdError error)? onAdFailedToShowFullScreenContent,
|
||||||
|
void Function(RewardItem reward)? onUserEarnedReward,
|
||||||
|
}) {
|
||||||
|
if (!isAdReady) {
|
||||||
|
_log.warning("[show] Ad is not ready");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ad!.fullScreenContentCallback = FullScreenContentCallback(
|
||||||
|
onAdDismissedFullScreenContent: (ad) {
|
||||||
|
_log.info("[show] onAdDismissedFullScreenContent");
|
||||||
|
ad.dispose();
|
||||||
|
onAdDismissedFullScreenContent?.call();
|
||||||
|
},
|
||||||
|
onAdFailedToShowFullScreenContent: (ad, e) {
|
||||||
|
_log.severe("[show] onAdFailedToShowFullScreenContent", e);
|
||||||
|
ad.dispose();
|
||||||
|
onAdFailedToShowFullScreenContent?.call(e);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_ad!.setImmersiveMode(true);
|
||||||
|
_ad!.show(
|
||||||
|
onUserEarnedReward: (ad, reward) {
|
||||||
|
_log.info("[show] onUserEarnedReward");
|
||||||
|
onUserEarnedReward?.call(reward);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_ad = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isAdReady => _ad != null;
|
||||||
|
bool get isGood => _loadAttempts < 3;
|
||||||
|
|
||||||
|
void _createRewarded() {
|
||||||
|
RewardedAd.load(
|
||||||
|
adUnitId: AdHelper.rewardedAdUnitId,
|
||||||
|
request: _request,
|
||||||
|
rewardedAdLoadCallback: RewardedAdLoadCallback(
|
||||||
|
onAdLoaded: (ad) {
|
||||||
|
_ad = ad;
|
||||||
|
_loadAttempts = 0;
|
||||||
|
},
|
||||||
|
onAdFailedToLoad: (e) {
|
||||||
|
_log.shout("[_createRewarded] Failed while loading ads", e);
|
||||||
|
++_loadAttempts;
|
||||||
|
if (isGood) {
|
||||||
|
_createRewarded();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RewardedAd? _ad;
|
||||||
|
var _loadAttempts = 0;
|
||||||
|
|
||||||
|
static final _log = Logger("widget.ad.RewardedAdHandler");
|
||||||
|
}
|
||||||
|
|
||||||
|
const _request = AdRequest();
|
|
@ -41,9 +41,11 @@ import 'package:nc_photos/gps_map_util.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/np_api_util.dart';
|
import 'package:nc_photos/np_api_util.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
|
import 'package:nc_photos/platform/features.dart' as features;
|
||||||
import 'package:nc_photos/session_storage.dart';
|
import 'package:nc_photos/session_storage.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/stream_util.dart';
|
import 'package:nc_photos/stream_util.dart';
|
||||||
|
import 'package:nc_photos/widget/ad.dart';
|
||||||
import 'package:nc_photos/widget/album_share_outlier_browser.dart';
|
import 'package:nc_photos/widget/album_share_outlier_browser.dart';
|
||||||
import 'package:nc_photos/widget/app_bar_circular_progress_indicator.dart';
|
import 'package:nc_photos/widget/app_bar_circular_progress_indicator.dart';
|
||||||
import 'package:nc_photos/widget/app_intermediate_circular_progress_indicator.dart';
|
import 'package:nc_photos/widget/app_intermediate_circular_progress_indicator.dart';
|
||||||
|
@ -336,6 +338,13 @@ class _WrappedCollectionBrowserState extends State<_WrappedCollectionBrowser>
|
||||||
: const SizedBox(height: 4),
|
: const SizedBox(height: 4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (features.isSupportAds)
|
||||||
|
const SliverPadding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
|
sliver: SliverToBoxAdapter(
|
||||||
|
child: AdBanner(),
|
||||||
|
),
|
||||||
|
),
|
||||||
_BlocBuilder(
|
_BlocBuilder(
|
||||||
buildWhen: (previous, current) =>
|
buildWhen: (previous, current) =>
|
||||||
previous.isEditMode != current.isEditMode ||
|
previous.isEditMode != current.isEditMode ||
|
||||||
|
|
114
app/lib/widget/handler/ad_gate_handler.dart
Normal file
114
app/lib/widget/handler/ad_gate_handler.dart
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/platform/features.dart' as features;
|
||||||
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
|
import 'package:nc_photos/widget/ad.dart';
|
||||||
|
import 'package:np_math/np_math.dart';
|
||||||
|
|
||||||
|
class AdGateHandler {
|
||||||
|
AdGateHandler() {
|
||||||
|
if (features.isSupportAds && !_isCooldown()) {
|
||||||
|
_hasLoadedAd = true;
|
||||||
|
_adHandler.init();
|
||||||
|
} else {
|
||||||
|
_hasLoadedAd = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle ad-gated contents
|
||||||
|
///
|
||||||
|
/// Return true if this user can proceed to the contents
|
||||||
|
Future<bool> call({
|
||||||
|
required BuildContext context,
|
||||||
|
required String contentText,
|
||||||
|
required String rewardedText,
|
||||||
|
}) async {
|
||||||
|
// check cooldown again since the ads may get shown in other instances
|
||||||
|
if (!_hasLoadedAd || _isCooldown()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
content: Text(contentText),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
|
child: const Text("WATCH AD"),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
launch(
|
||||||
|
"https://play.google.com/store/apps/details?id=com.nkming.nc_photos.paid&referrer=utm_source%3Dfreeapp");
|
||||||
|
},
|
||||||
|
child: const Text("GET PAID VERSION"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (result != true) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return await _showAd(rewardedText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isCooldown() =>
|
||||||
|
RewardedAdHandler.isCooldownPeriod(const Duration(days: 1));
|
||||||
|
|
||||||
|
Future<bool> _showAd(String rewardedText) async {
|
||||||
|
// wait for the ad to finish loading, max 5s
|
||||||
|
for (final _ in 0.until(50)) {
|
||||||
|
if (_adHandler.isAdReady) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
}
|
||||||
|
if (!_adHandler.isAdReady) {
|
||||||
|
_log.shout("[_showAd] Ad failed to load in time");
|
||||||
|
SnackBarManager().showSnackBar(const SnackBar(
|
||||||
|
content: Text("Failed to load ad"),
|
||||||
|
duration: k.snackBarDurationNormal,
|
||||||
|
));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final dismissCompleter = Completer();
|
||||||
|
bool isEarned = false;
|
||||||
|
_adHandler.show(
|
||||||
|
onAdDismissedFullScreenContent: () {
|
||||||
|
dismissCompleter.complete();
|
||||||
|
},
|
||||||
|
onAdFailedToShowFullScreenContent: (_) {
|
||||||
|
dismissCompleter.complete();
|
||||||
|
},
|
||||||
|
onUserEarnedReward: (_) {
|
||||||
|
isEarned = true;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await dismissCompleter.future;
|
||||||
|
if (isEarned) {
|
||||||
|
await Pref().setLastAdRewardTime(DateTime.now().millisecondsSinceEpoch);
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content: Text(rewardedText),
|
||||||
|
duration: k.snackBarDurationNormal,
|
||||||
|
));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final _adHandler = RewardedAdHandler();
|
||||||
|
late final bool _hasLoadedAd;
|
||||||
|
|
||||||
|
static final _log = Logger("widget.handler.ad_gate_handler.AdGateHandler");
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/stream_util.dart';
|
import 'package:nc_photos/stream_util.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/theme/dimension.dart';
|
import 'package:nc_photos/theme/dimension.dart';
|
||||||
|
import 'package:nc_photos/widget/ad.dart';
|
||||||
import 'package:nc_photos/widget/album_importer.dart';
|
import 'package:nc_photos/widget/album_importer.dart';
|
||||||
import 'package:nc_photos/widget/archive_browser.dart';
|
import 'package:nc_photos/widget/archive_browser.dart';
|
||||||
import 'package:nc_photos/widget/collection_browser.dart';
|
import 'package:nc_photos/widget/collection_browser.dart';
|
||||||
|
@ -168,6 +169,13 @@ class _BodyView extends StatelessWidget {
|
||||||
? const _AppBar()
|
? const _AppBar()
|
||||||
: const _SelectionAppBar(),
|
: const _SelectionAppBar(),
|
||||||
),
|
),
|
||||||
|
if (features.isSupportAds)
|
||||||
|
const SliverPadding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
|
sliver: SliverToBoxAdapter(
|
||||||
|
child: AdBanner(),
|
||||||
|
),
|
||||||
|
),
|
||||||
const SliverToBoxAdapter(
|
const SliverToBoxAdapter(
|
||||||
child: SizedBox(height: 8),
|
child: SizedBox(height: 8),
|
||||||
),
|
),
|
||||||
|
|
20
app/lib/widget/home_photos/ads.dart
Normal file
20
app/lib/widget/home_photos/ads.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
part of '../home_photos2.dart';
|
||||||
|
|
||||||
|
class _BannerAd extends StatelessWidget {
|
||||||
|
const _BannerAd();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SliverMeasureExtent(
|
||||||
|
onChange: (extent) {
|
||||||
|
context.addEvent(_UpdateBannerAdExtent(extent));
|
||||||
|
},
|
||||||
|
child: const SliverPadding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
|
sliver: SliverToBoxAdapter(
|
||||||
|
child: AdBanner(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,8 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
|
|
||||||
on<_TripMissingVideoPreview>(_onTripMissingVideoPreview);
|
on<_TripMissingVideoPreview>(_onTripMissingVideoPreview);
|
||||||
|
|
||||||
|
on<_UpdateBannerAdExtent>(_onUpdateBannerAdExtent);
|
||||||
|
|
||||||
on<_SetError>(_onSetError);
|
on<_SetError>(_onSetError);
|
||||||
|
|
||||||
_subscriptions
|
_subscriptions
|
||||||
|
@ -372,7 +374,8 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
itemPerRow: state.itemPerRow!,
|
itemPerRow: state.itemPerRow!,
|
||||||
viewHeight: state.viewHeight!,
|
viewHeight: state.viewHeight!,
|
||||||
);
|
);
|
||||||
final totalHeight = minimapItems.map((e) => e.logicalHeight).sum;
|
final totalHeight = (state.bannerAdExtent ?? 0) +
|
||||||
|
minimapItems.map((e) => e.logicalHeight).sum;
|
||||||
final ratio = state.viewHeight! / totalHeight;
|
final ratio = state.viewHeight! / totalHeight;
|
||||||
_log.info(
|
_log.info(
|
||||||
"[_onTransformMinimap] view height: ${state.viewHeight!}, logical height: $totalHeight");
|
"[_onTransformMinimap] view height: ${state.viewHeight!}, logical height: $totalHeight");
|
||||||
|
@ -492,6 +495,11 @@ class _Bloc extends Bloc<_Event, _State>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onUpdateBannerAdExtent(_UpdateBannerAdExtent ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(bannerAdExtent: ev.value));
|
||||||
|
}
|
||||||
|
|
||||||
void _onSetError(_SetError ev, Emitter<_State> emit) {
|
void _onSetError(_SetError ev, Emitter<_State> emit) {
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace)));
|
emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace)));
|
||||||
|
|
|
@ -36,6 +36,9 @@ class _MinimapView extends StatelessWidget {
|
||||||
// prevent overlap
|
// prevent overlap
|
||||||
top = prevItemY! + _minDateY;
|
top = prevItemY! + _minDateY;
|
||||||
}
|
}
|
||||||
|
// add extra padding for the banner ads
|
||||||
|
top +=
|
||||||
|
(state.bannerAdExtent ?? 0) * state.minimapYRatio;
|
||||||
prevItemY = top;
|
prevItemY = top;
|
||||||
final text =
|
final text =
|
||||||
"${DateFormat.y().format(prevDate!.toLocalDateTime())} —";
|
"${DateFormat.y().format(prevDate!.toLocalDateTime())} —";
|
||||||
|
|
|
@ -26,6 +26,7 @@ class _State {
|
||||||
required this.minimapYRatio,
|
required this.minimapYRatio,
|
||||||
this.scrollDate,
|
this.scrollDate,
|
||||||
required this.hasMissingVideoPreview,
|
required this.hasMissingVideoPreview,
|
||||||
|
this.bannerAdExtent,
|
||||||
this.error,
|
this.error,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -80,6 +81,8 @@ class _State {
|
||||||
|
|
||||||
final bool hasMissingVideoPreview;
|
final bool hasMissingVideoPreview;
|
||||||
|
|
||||||
|
final double? bannerAdExtent;
|
||||||
|
|
||||||
final ExceptionEvent? error;
|
final ExceptionEvent? error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,6 +320,15 @@ class _TripMissingVideoPreview implements _Event {
|
||||||
String toString() => _$toString();
|
String toString() => _$toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _UpdateBannerAdExtent implements _Event {
|
||||||
|
const _UpdateBannerAdExtent(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final double? value;
|
||||||
|
}
|
||||||
|
|
||||||
@toString
|
@toString
|
||||||
class _SetError implements _Event {
|
class _SetError implements _Event {
|
||||||
const _SetError(this.error, [this.stackTrace]);
|
const _SetError(this.error, [this.stackTrace]);
|
||||||
|
|
|
@ -36,6 +36,7 @@ import 'package:nc_photos/exception_event.dart';
|
||||||
import 'package:nc_photos/flutter_util.dart' as flutter_util;
|
import 'package:nc_photos/flutter_util.dart' as flutter_util;
|
||||||
import 'package:nc_photos/help_utils.dart' as help_util;
|
import 'package:nc_photos/help_utils.dart' as help_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/platform/features.dart' as features;
|
||||||
import 'package:nc_photos/progress_util.dart';
|
import 'package:nc_photos/progress_util.dart';
|
||||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:nc_photos/session_storage.dart';
|
import 'package:nc_photos/session_storage.dart';
|
||||||
|
@ -44,12 +45,14 @@ import 'package:nc_photos/stream_extension.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/theme/dimension.dart';
|
import 'package:nc_photos/theme/dimension.dart';
|
||||||
import 'package:nc_photos/url_launcher_util.dart';
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
|
import 'package:nc_photos/widget/ad.dart';
|
||||||
import 'package:nc_photos/widget/collection_browser.dart';
|
import 'package:nc_photos/widget/collection_browser.dart';
|
||||||
import 'package:nc_photos/widget/collection_picker.dart';
|
import 'package:nc_photos/widget/collection_picker.dart';
|
||||||
import 'package:nc_photos/widget/double_tap_exit_container/double_tap_exit_container.dart';
|
import 'package:nc_photos/widget/double_tap_exit_container/double_tap_exit_container.dart';
|
||||||
import 'package:nc_photos/widget/file_sharer_dialog.dart';
|
import 'package:nc_photos/widget/file_sharer_dialog.dart';
|
||||||
import 'package:nc_photos/widget/finger_listener.dart';
|
import 'package:nc_photos/widget/finger_listener.dart';
|
||||||
import 'package:nc_photos/widget/home_app_bar.dart';
|
import 'package:nc_photos/widget/home_app_bar.dart';
|
||||||
|
import 'package:nc_photos/widget/measure.dart';
|
||||||
import 'package:nc_photos/widget/navigation_bar_blur_filter.dart';
|
import 'package:nc_photos/widget/navigation_bar_blur_filter.dart';
|
||||||
import 'package:nc_photos/widget/network_thumbnail.dart';
|
import 'package:nc_photos/widget/network_thumbnail.dart';
|
||||||
import 'package:nc_photos/widget/photo_list_item.dart';
|
import 'package:nc_photos/widget/photo_list_item.dart';
|
||||||
|
@ -69,6 +72,7 @@ import 'package:np_ui/np_ui.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
import 'package:visibility_detector/visibility_detector.dart';
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
|
part 'home_photos/ads.dart';
|
||||||
part 'home_photos/app_bar.dart';
|
part 'home_photos/app_bar.dart';
|
||||||
part 'home_photos/bloc.dart';
|
part 'home_photos/bloc.dart';
|
||||||
part 'home_photos/minimap_view.dart';
|
part 'home_photos/minimap_view.dart';
|
||||||
|
@ -329,7 +333,8 @@ class _BodyState extends State<_Body> {
|
||||||
(previous.isEnableMemoryCollection &&
|
(previous.isEnableMemoryCollection &&
|
||||||
previous.memoryCollections.isNotEmpty) !=
|
previous.memoryCollections.isNotEmpty) !=
|
||||||
(current.isEnableMemoryCollection &&
|
(current.isEnableMemoryCollection &&
|
||||||
current.memoryCollections.isNotEmpty),
|
current.memoryCollections.isNotEmpty) ||
|
||||||
|
previous.bannerAdExtent != current.bannerAdExtent,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final scrollExtent = _getScrollViewExtent(
|
final scrollExtent = _getScrollViewExtent(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -392,6 +397,7 @@ class _BodyState extends State<_Body> {
|
||||||
? const _AppBar()
|
? const _AppBar()
|
||||||
: const _SelectionAppBar(),
|
: const _SelectionAppBar(),
|
||||||
),
|
),
|
||||||
|
if (features.isSupportAds) const _BannerAd(),
|
||||||
_BlocBuilder(
|
_BlocBuilder(
|
||||||
buildWhen: (previous, current) =>
|
buildWhen: (previous, current) =>
|
||||||
(previous.isEnableMemoryCollection &&
|
(previous.isEnableMemoryCollection &&
|
||||||
|
@ -479,7 +485,9 @@ class _BodyState extends State<_Body> {
|
||||||
required bool hasMemoryCollection,
|
required bool hasMemoryCollection,
|
||||||
required double? contentListMaxExtent,
|
required double? contentListMaxExtent,
|
||||||
}) {
|
}) {
|
||||||
if (contentListMaxExtent != null && constraints.hasBoundedHeight) {
|
if (contentListMaxExtent != null &&
|
||||||
|
constraints.hasBoundedHeight &&
|
||||||
|
(!features.isSupportAds || context.state.bannerAdExtent != null)) {
|
||||||
final appBarExtent = _getAppBarExtent(context);
|
final appBarExtent = _getAppBarExtent(context);
|
||||||
final bottomAppBarExtent =
|
final bottomAppBarExtent =
|
||||||
AppDimension.of(context).homeBottomAppBarHeight;
|
AppDimension.of(context).homeBottomAppBarHeight;
|
||||||
|
@ -494,13 +502,15 @@ class _BodyState extends State<_Body> {
|
||||||
appBarExtent +
|
appBarExtent +
|
||||||
bottomAppBarExtent +
|
bottomAppBarExtent +
|
||||||
// metadataTaskHeaderExtent +
|
// metadataTaskHeaderExtent +
|
||||||
smartAlbumListHeight;
|
smartAlbumListHeight +
|
||||||
|
(context.state.bannerAdExtent ?? 0);
|
||||||
_log.info("[_getScrollViewExtent] $contentListMaxExtent "
|
_log.info("[_getScrollViewExtent] $contentListMaxExtent "
|
||||||
"- ${constraints.maxHeight} "
|
"- ${constraints.maxHeight} "
|
||||||
"+ $appBarExtent "
|
"+ $appBarExtent "
|
||||||
"+ $bottomAppBarExtent "
|
"+ $bottomAppBarExtent "
|
||||||
// "+ $metadataTaskHeaderExtent "
|
// "+ $metadataTaskHeaderExtent "
|
||||||
"+ $smartAlbumListHeight "
|
"+ $smartAlbumListHeight "
|
||||||
|
"+ ${context.state.bannerAdExtent} "
|
||||||
"= $scrollExtent");
|
"= $scrollExtent");
|
||||||
return scrollExtent;
|
return scrollExtent;
|
||||||
} else {
|
} else {
|
||||||
|
@ -527,7 +537,7 @@ typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
|
|
||||||
extension on BuildContext {
|
extension on BuildContext {
|
||||||
_Bloc get bloc => read<_Bloc>();
|
_Bloc get bloc => read<_Bloc>();
|
||||||
// _State get state => bloc.state;
|
_State get state => bloc.state;
|
||||||
void addEvent(_Event event) => bloc.add(event);
|
void addEvent(_Event event) => bloc.add(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@ abstract class $_StateCopyWithWorker {
|
||||||
double? minimapYRatio,
|
double? minimapYRatio,
|
||||||
Date? scrollDate,
|
Date? scrollDate,
|
||||||
bool? hasMissingVideoPreview,
|
bool? hasMissingVideoPreview,
|
||||||
|
double? bannerAdExtent,
|
||||||
ExceptionEvent? error});
|
ExceptionEvent? error});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +67,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
dynamic minimapYRatio,
|
dynamic minimapYRatio,
|
||||||
dynamic scrollDate = copyWithNull,
|
dynamic scrollDate = copyWithNull,
|
||||||
dynamic hasMissingVideoPreview,
|
dynamic hasMissingVideoPreview,
|
||||||
|
dynamic bannerAdExtent = copyWithNull,
|
||||||
dynamic error = copyWithNull}) {
|
dynamic error = copyWithNull}) {
|
||||||
return _State(
|
return _State(
|
||||||
files: files as List<FileDescriptor>? ?? that.files,
|
files: files as List<FileDescriptor>? ?? that.files,
|
||||||
|
@ -106,6 +108,9 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
scrollDate == copyWithNull ? that.scrollDate : scrollDate as Date?,
|
scrollDate == copyWithNull ? that.scrollDate : scrollDate as Date?,
|
||||||
hasMissingVideoPreview:
|
hasMissingVideoPreview:
|
||||||
hasMissingVideoPreview as bool? ?? that.hasMissingVideoPreview,
|
hasMissingVideoPreview as bool? ?? that.hasMissingVideoPreview,
|
||||||
|
bannerAdExtent: bannerAdExtent == copyWithNull
|
||||||
|
? that.bannerAdExtent
|
||||||
|
: bannerAdExtent as double?,
|
||||||
error: error == copyWithNull ? that.error : error as ExceptionEvent?);
|
error: error == copyWithNull ? that.error : error as ExceptionEvent?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +189,7 @@ extension _$_ContentListBodyNpLog on _ContentListBody {
|
||||||
extension _$_StateToString on _State {
|
extension _$_StateToString on _State {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// ignore: unnecessary_string_interpolations
|
||||||
return "_State {files: [length: ${files.length}], isLoading: $isLoading, transformedItems: [length: ${transformedItems.length}], selectedItems: {length: ${selectedItems.length}}, filesSummary: $filesSummary, visibleDates: {length: ${visibleDates.length}}, queriedDates: {length: ${queriedDates.length}}, isEnableMemoryCollection: $isEnableMemoryCollection, memoryCollections: [length: ${memoryCollections.length}], contentListMaxExtent: ${contentListMaxExtent == null ? null : "${contentListMaxExtent!.toStringAsFixed(3)}"}, syncProgress: $syncProgress, zoom: $zoom, scale: ${scale == null ? null : "${scale!.toStringAsFixed(3)}"}, viewWidth: ${viewWidth == null ? null : "${viewWidth!.toStringAsFixed(3)}"}, viewHeight: ${viewHeight == null ? null : "${viewHeight!.toStringAsFixed(3)}"}, itemPerRow: $itemPerRow, itemSize: ${itemSize == null ? null : "${itemSize!.toStringAsFixed(3)}"}, isScrolling: $isScrolling, minimapItems: ${minimapItems == null ? null : "[length: ${minimapItems!.length}]"}, minimapYRatio: ${minimapYRatio.toStringAsFixed(3)}, scrollDate: $scrollDate, hasMissingVideoPreview: $hasMissingVideoPreview, error: $error}";
|
return "_State {files: [length: ${files.length}], isLoading: $isLoading, transformedItems: [length: ${transformedItems.length}], selectedItems: {length: ${selectedItems.length}}, filesSummary: $filesSummary, visibleDates: {length: ${visibleDates.length}}, queriedDates: {length: ${queriedDates.length}}, isEnableMemoryCollection: $isEnableMemoryCollection, memoryCollections: [length: ${memoryCollections.length}], contentListMaxExtent: ${contentListMaxExtent == null ? null : "${contentListMaxExtent!.toStringAsFixed(3)}"}, syncProgress: $syncProgress, zoom: $zoom, scale: ${scale == null ? null : "${scale!.toStringAsFixed(3)}"}, viewWidth: ${viewWidth == null ? null : "${viewWidth!.toStringAsFixed(3)}"}, viewHeight: ${viewHeight == null ? null : "${viewHeight!.toStringAsFixed(3)}"}, itemPerRow: $itemPerRow, itemSize: ${itemSize == null ? null : "${itemSize!.toStringAsFixed(3)}"}, isScrolling: $isScrolling, minimapItems: ${minimapItems == null ? null : "[length: ${minimapItems!.length}]"}, minimapYRatio: ${minimapYRatio.toStringAsFixed(3)}, scrollDate: $scrollDate, hasMissingVideoPreview: $hasMissingVideoPreview, bannerAdExtent: ${bannerAdExtent == null ? null : "${bannerAdExtent!.toStringAsFixed(3)}"}, error: $error}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,6 +369,13 @@ extension _$_TripMissingVideoPreviewToString on _TripMissingVideoPreview {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension _$_UpdateBannerAdExtentToString on _UpdateBannerAdExtent {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_UpdateBannerAdExtent {value: ${value == null ? null : "${value!.toStringAsFixed(3)}"}}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension _$_SetErrorToString on _SetError {
|
extension _$_SetErrorToString on _SetError {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// ignore: unnecessary_string_interpolations
|
||||||
|
|
|
@ -17,6 +17,7 @@ import 'package:nc_photos/np_api_util.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/url_launcher_util.dart';
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
|
import 'package:nc_photos/widget/handler/ad_gate_handler.dart';
|
||||||
import 'package:nc_photos/widget/handler/permission_handler.dart';
|
import 'package:nc_photos/widget/handler/permission_handler.dart';
|
||||||
import 'package:nc_photos/widget/image_editor/color_toolbar.dart';
|
import 'package:nc_photos/widget/image_editor/color_toolbar.dart';
|
||||||
import 'package:nc_photos/widget/image_editor/crop_controller.dart';
|
import 'package:nc_photos/widget/image_editor/crop_controller.dart';
|
||||||
|
@ -286,6 +287,15 @@ class _ImageEditorState extends State<ImageEditor> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSavePressed(BuildContext context) async {
|
Future<void> _onSavePressed(BuildContext context) async {
|
||||||
|
if (!await _adGateHandler(
|
||||||
|
context: context,
|
||||||
|
contentText:
|
||||||
|
"Photo editing is a premium feature but you can unlock it by watching an ad. Once unlocked it will last for 1 day.",
|
||||||
|
rewardedText: "Your photo will now be processed in the background",
|
||||||
|
)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
await ImageProcessor.filter(
|
await ImageProcessor.filter(
|
||||||
"${widget.account.url}/${widget.file.fdPath}",
|
"${widget.account.url}/${widget.file.fdPath}",
|
||||||
|
@ -344,6 +354,8 @@ class _ImageEditorState extends State<ImageEditor> {
|
||||||
var _colorFilters = <ColorArguments>[];
|
var _colorFilters = <ColorArguments>[];
|
||||||
var _transformFilters = <TransformArguments>[];
|
var _transformFilters = <TransformArguments>[];
|
||||||
TransformArguments? _cropFilter;
|
TransformArguments? _cropFilter;
|
||||||
|
|
||||||
|
final _adGateHandler = AdGateHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum _ToolType {
|
enum _ToolType {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/url_launcher_util.dart';
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
|
import 'package:nc_photos/widget/handler/ad_gate_handler.dart';
|
||||||
import 'package:nc_photos/widget/handler/permission_handler.dart';
|
import 'package:nc_photos/widget/handler/permission_handler.dart';
|
||||||
import 'package:nc_photos/widget/image_editor_persist_option_dialog.dart';
|
import 'package:nc_photos/widget/image_editor_persist_option_dialog.dart';
|
||||||
import 'package:nc_photos/widget/selectable.dart';
|
import 'package:nc_photos/widget/selectable.dart';
|
||||||
|
@ -193,6 +194,14 @@ class _ImageEnhancerState extends State<ImageEnhancer> {
|
||||||
// user canceled
|
// user canceled
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!await _adGateHandler(
|
||||||
|
context: context,
|
||||||
|
contentText:
|
||||||
|
"Photo enhancement is a premium feature but you can unlock it by watching an ad. Once unlocked it will last for 1 day.",
|
||||||
|
rewardedText: "Your photo will now be processed in the background",
|
||||||
|
)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
switch (_selectedOption.algorithm) {
|
switch (_selectedOption.algorithm) {
|
||||||
case _Algorithm.zeroDce:
|
case _Algorithm.zeroDce:
|
||||||
await ImageProcessor.zeroDce(
|
await ImageProcessor.zeroDce(
|
||||||
|
@ -585,6 +594,8 @@ class _ImageEnhancerState extends State<ImageEnhancer> {
|
||||||
late final DiContainer _c;
|
late final DiContainer _c;
|
||||||
late var _selectedOption = _options[0];
|
late var _selectedOption = _options[0];
|
||||||
late final _pageController = PageController(keepPage: false);
|
late final _pageController = PageController(keepPage: false);
|
||||||
|
|
||||||
|
final _adGateHandler = AdGateHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum _Algorithm {
|
enum _Algorithm {
|
||||||
|
|
|
@ -18,11 +18,13 @@ import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/exception_event.dart';
|
import 'package:nc_photos/exception_event.dart';
|
||||||
import 'package:nc_photos/help_utils.dart' as help_util;
|
import 'package:nc_photos/help_utils.dart' as help_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/platform/features.dart' as features;
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/stream_util.dart';
|
import 'package:nc_photos/stream_util.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/url_launcher_util.dart';
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
import 'package:nc_photos/use_case/list_location_group.dart';
|
import 'package:nc_photos/use_case/list_location_group.dart';
|
||||||
|
import 'package:nc_photos/widget/ad.dart';
|
||||||
import 'package:nc_photos/widget/app_intermediate_circular_progress_indicator.dart';
|
import 'package:nc_photos/widget/app_intermediate_circular_progress_indicator.dart';
|
||||||
import 'package:nc_photos/widget/collection_browser.dart';
|
import 'package:nc_photos/widget/collection_browser.dart';
|
||||||
import 'package:nc_photos/widget/network_thumbnail.dart';
|
import 'package:nc_photos/widget/network_thumbnail.dart';
|
||||||
|
@ -141,6 +143,11 @@ class _WrappedSearchLandingState extends State<_WrappedSearchLanding> {
|
||||||
],
|
],
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
if (features.isSupportAds)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: AdBanner(),
|
||||||
|
),
|
||||||
ValueStreamBuilder<PersonProvider>(
|
ValueStreamBuilder<PersonProvider>(
|
||||||
stream: context
|
stream: context
|
||||||
.read<AccountController>()
|
.read<AccountController>()
|
||||||
|
|
|
@ -29,6 +29,7 @@ import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/list_file_tag.dart';
|
import 'package:nc_photos/use_case/list_file_tag.dart';
|
||||||
import 'package:nc_photos/use_case/update_property.dart';
|
import 'package:nc_photos/use_case/update_property.dart';
|
||||||
import 'package:nc_photos/widget/about_geocoding_dialog.dart';
|
import 'package:nc_photos/widget/about_geocoding_dialog.dart';
|
||||||
|
import 'package:nc_photos/widget/ad.dart';
|
||||||
import 'package:nc_photos/widget/handler/add_selection_to_collection_handler.dart';
|
import 'package:nc_photos/widget/handler/add_selection_to_collection_handler.dart';
|
||||||
import 'package:nc_photos/widget/list_tile_center_leading.dart';
|
import 'package:nc_photos/widget/list_tile_center_leading.dart';
|
||||||
import 'package:nc_photos/widget/photo_date_time_edit_dialog.dart';
|
import 'package:nc_photos/widget/photo_date_time_edit_dialog.dart';
|
||||||
|
@ -258,6 +259,11 @@ class _ViewerDetailPaneState extends State<ViewerDetailPane> {
|
||||||
child: Divider(),
|
child: Divider(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
if (features.isSupportAds)
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8),
|
||||||
|
child: AdBanner(),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const ListTileCenterLeading(
|
leading: const ListTileCenterLeading(
|
||||||
child: Icon(Icons.image_outlined),
|
child: Icon(Icons.image_outlined),
|
||||||
|
|
|
@ -748,6 +748,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.4+3"
|
version: "0.5.4+3"
|
||||||
|
google_mobile_ads:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: google_mobile_ads
|
||||||
|
sha256: e2d18992d30b2be77cb6976b931112fc3c4612feffb5eb7a8b036bd7a64934da
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.0"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1998,6 +2006,38 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.1"
|
||||||
|
webview_flutter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter
|
||||||
|
sha256: "6869c8786d179f929144b4a1f86e09ac0eddfe475984951ea6c634774c16b522"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.8.0"
|
||||||
|
webview_flutter_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_android
|
||||||
|
sha256: "0d21cfc3bfdd2e30ab2ebeced66512b91134b39e72e97b43db2d47dda1c4e53a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.16.3"
|
||||||
|
webview_flutter_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_platform_interface
|
||||||
|
sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.10.0"
|
||||||
|
webview_flutter_wkwebview:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webview_flutter_wkwebview
|
||||||
|
sha256: "7affdf9d680c015b11587181171d3cad8093e449db1f7d9f0f08f4f33d24f9a0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.13.1"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -161,6 +161,8 @@ dependencies:
|
||||||
visibility_detector: 0.3.3
|
visibility_detector: 0.3.3
|
||||||
wakelock_plus: ^1.1.1
|
wakelock_plus: ^1.1.1
|
||||||
woozy_search: ^2.0.3
|
woozy_search: ^2.0.3
|
||||||
|
# android/ios only
|
||||||
|
google_mobile_ads: 5.1.0
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
video_player:
|
video_player:
|
||||||
|
|
Loading…
Reference in a new issue