diff --git a/android/app/build.gradle b/android/app/build.gradle
index a1a328e..d0f0f9c 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -1,5 +1,9 @@
plugins {
id "com.android.application"
+ // START: FlutterFire Configuration
+ id 'com.google.gms.google-services'
+ id 'com.google.firebase.crashlytics'
+ // END: FlutterFire Configuration
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
diff --git a/android/app/google-services.json b/android/app/google-services.json
new file mode 100644
index 0000000..7763b09
--- /dev/null
+++ b/android/app/google-services.json
@@ -0,0 +1,29 @@
+{
+ "project_info": {
+ "project_number": "280378307504",
+ "project_id": "tonesnap-and",
+ "storage_bucket": "tonesnap-and.appspot.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:280378307504:android:bcee5d63088dcbb7ca5c75",
+ "android_client_info": {
+ "package_name": "com.tone.music.offline"
+ }
+ },
+ "oauth_client": [],
+ "api_key": [
+ {
+ "current_key": "AIzaSyCLEOFjenMfiZKdoKdGh9mylckpIpTn854"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": []
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 34cc3db..9160878 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -48,7 +48,7 @@
+ android:value="ca-app-pub-5684307632319406~5753747604"/>
@@ -14,13 +16,14 @@
-
+
-
+
+
diff --git a/lib/ads/app_open_ad_manager.dart b/lib/ads/app_open_ad_manager.dart
index 7b480a8..12ee7d5 100644
--- a/lib/ads/app_open_ad_manager.dart
+++ b/lib/ads/app_open_ad_manager.dart
@@ -20,17 +20,17 @@ class AppOpenAdManager {
factory AppOpenAdManager() => _instance;
+ AppOpenAd? _appOpenAd;
+ bool _isShowingAd = false;
+
/// 加载和显示广告之间允许的最长持续时间
- final Duration maxCacheDuration = const Duration(hours: 4);
+ final Duration _maxCacheDuration = const Duration(minutes: 50);
/// 跟踪加载时间,这样我们就不会显示过期的广告
DateTime? _appOpenLoadTime;
- AppOpenAd? _appOpenAd;
- bool _isShowingAd = false;
-
/// 记录关闭时的时间,用于下一次展示时计算时间差
- DateTime? closeDateTime;
+ DateTime? _closeDateTime;
/// 开屏广告单元id
final adUnitId = Platform.isAndroid
@@ -41,6 +41,7 @@ class AppOpenAdManager {
Future loadAd() async {
final List connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult.contains(ConnectivityResult.none)) {
+ LogUtil.d('当前无网络,不加载广告');
return;
}
if (isAdAvailable) {
@@ -87,7 +88,7 @@ class AppOpenAdManager {
if(onTap != null) onTap();
return;
}
- if (DateTime.now().subtract(maxCacheDuration).isAfter(_appOpenLoadTime!)) {
+ if (DateTime.now().subtract(_maxCacheDuration).isAfter(_appOpenLoadTime!)) {
LogUtil.d('超过了最大缓存持续时间。正在加载另一个广告');
_appOpenAd!.dispose();
_appOpenAd = null;
@@ -95,9 +96,9 @@ class AppOpenAdManager {
if(onTap != null) onTap();
return;
}
- if (closeDateTime != null) {
+ if (_closeDateTime != null) {
// 计算时间差
- Duration timeDifference = DateTime.now().difference(closeDateTime!);
+ Duration timeDifference = DateTime.now().difference(_closeDateTime!);
// 获取配置的 openAppEventDuration
int openAppEventDuration = MusicBox().getOpenAppEventDuration();
// 检查时间差是否小于10秒
@@ -130,7 +131,7 @@ class AppOpenAdManager {
},
onAdDismissedFullScreenContent: (ad) {
LogUtil.d('$ad onAdDismissedFullScreenContent');
- closeDateTime = DateTime.now();
+ _closeDateTime = DateTime.now();
// 显示状态栏(用户关闭广告后)
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values);
diff --git a/lib/ads/interstitial_ad_manager.dart b/lib/ads/interstitial_ad_manager.dart
index 7a44ec1..2e44a39 100644
--- a/lib/ads/interstitial_ad_manager.dart
+++ b/lib/ads/interstitial_ad_manager.dart
@@ -2,12 +2,16 @@
// Date: 2024/6/25
// Description: 插页广告
-import 'dart:io' show Platform;
-
import 'package:connectivity_plus/connectivity_plus.dart';
-import 'package:flutter/foundation.dart';
+import 'package:get/get.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
+import 'package:tone_snap/data/models/ad_config_model.dart';
+import 'package:tone_snap/data/storage/music_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
+import 'package:tone_snap/modules/launch/launch_controller.dart';
import 'package:tone_snap/utils/log_util.dart';
+import 'package:tone_snap/utils/obj_util.dart';
class InterstitialAdManager {
InterstitialAdManager._();
@@ -16,97 +20,208 @@ class InterstitialAdManager {
factory InterstitialAdManager() => _instance;
+ /// 广告对象Map
+ final Map _interstitialAdMap = {};
+
/// 加载和显示广告之间允许的最长持续时间
- final Duration maxCacheDuration = const Duration(hours: 4);
+ final Duration _maxCacheDuration = const Duration(minutes: 50);
/// 跟踪加载时间,这样我们就不会显示过期的广告
- DateTime? _appOpenLoadTime;
+ final Map _appOpenLoadTimeMap = {};
- InterstitialAd? _interstitialAd;
+ /// 记录关闭时的时间,用于下一次展示时计算时间差
+ DateTime? _closeDateTime;
+
+ /// 是否显示
bool isShowingAd = false;
- /// 插页广告单元id
- final adUnitId = Platform.isAndroid
- ? (kReleaseMode ? '' : 'ca-app-pub-3940256099942544/1033173712')
- : (kReleaseMode ? 'ca-app-pub-5684307632319406/2760767691' : 'ca-app-pub-3940256099942544/4411468910');
+ /// 加载所有插页广告位
+ void loadAdAll() {
+ AdConfigModel adConfigModel = MusicBox().getAdConfig();
+ _loadSingleAd(adConfigModel.coldLoading, AdScenes.coldLoading.name);
+ _loadSingleAd(adConfigModel.hotLoading, AdScenes.hotLoading.name);
+ _loadSingleAd(adConfigModel.play, AdScenes.play.name);
+ _loadSingleAd(adConfigModel.download, AdScenes.download.name);
+ _loadSingleAd(adConfigModel.list, AdScenes.list.name);
+ _loadSingleAd(adConfigModel.playCut, AdScenes.playCut.name);
+ _loadSingleAd(adConfigModel.search, AdScenes.search.name);
+ }
+
+ /// 加载单个广告位
+ void _loadSingleAd(List? adList, String adScenes) {
+ if (adList != null && adList.isNotEmpty) {
+ var adModel = adList.reduce((value, element) => element.level! < value.level! ? element : value);
+ loadAd(adModel.identifier, adScenes);
+ }
+ }
/// 加载广告
- void loadAd() async {
+ void loadAd(String? adUnitId, String adScenes) async {
final List connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult.contains(ConnectivityResult.none)) {
+ LogUtil.d('当前无网络,不加载广告');
return;
}
- if (isAdAvailable) {
+ if (ObjUtil.isEmpty(adUnitId)) {
return;
}
InterstitialAd.load(
- adUnitId: adUnitId,
+ adUnitId: adUnitId!,
request: const AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: (ad) {
- LogUtil.d('插页广告加载完成');
- _interstitialAd = ad;
- _appOpenLoadTime = DateTime.now();
+ LogUtil.d('$adScenes:${ad.adUnitId}插页广告加载完成');
+ _interstitialAdMap[adScenes] = ad;
+ _appOpenLoadTimeMap[adScenes] = DateTime.now();
+ if (adScenes == AdScenes.coldLoading.name) {
+ if (Get.isRegistered()) {
+ LaunchController.to.editChangeValue();
+ }
+ }
},
onAdFailedToLoad: (LoadAdError error) {
- LogUtil.d('插页广告加载失败: $error');
+ LogUtil.d('$adScenes:$adUnitId插页广告加载失败: $error');
+ _loadFailHandle(adUnitId, adScenes);
+ FirebaseAnalyticsManager.logPlayAdsLoadFailure(error.message);
},
),
);
}
+ /// 加载失败处理
+ void _loadFailHandle(String adUnitId, String adScenes) {
+ AdConfigModel adConfigModel = MusicBox().getAdConfig();
+ var adList = _getAdList(adScenes);
+ if (adList != null) {
+ var currentItem = adList.firstWhereOrNull((e) => e.identifier == adUnitId);
+ if (currentItem != null) {
+ var nextItem = adList.firstWhereOrNull((e) => e.level == currentItem.level! + 1);
+ if (nextItem != null) {
+ // 当前id加载失败,加载下一个id
+ loadAd(nextItem.identifier, adScenes);
+ } else {
+ // 对应场景id都加载失败,加载全局id
+ _loadSingleAd(adConfigModel.backup, adScenes);
+ }
+ } else {
+ var currentItem = adConfigModel.backup?.firstWhereOrNull((e) => e.identifier == adUnitId);
+ if (currentItem != null) {
+ var nextItem = adConfigModel.backup?.firstWhereOrNull((e) => e.level == currentItem.level! + 1);
+ if (nextItem != null) {
+ loadAd(nextItem.identifier, adScenes);
+ } else {
+ if (adList.isNotEmpty) {
+ _loadSingleAd(adList, adScenes);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ List? _getAdList(String adScenes) {
+ AdConfigModel adConfigModel = MusicBox().getAdConfig();
+ if (adScenes == AdScenes.coldLoading.name) {
+ return adConfigModel.coldLoading;
+ } else if (adScenes == AdScenes.hotLoading.name) {
+ return adConfigModel.hotLoading;
+ } else if (adScenes == AdScenes.play.name) {
+ return adConfigModel.play;
+ } else if (adScenes == AdScenes.download.name) {
+ return adConfigModel.download;
+ } else if (adScenes == AdScenes.list.name) {
+ return adConfigModel.list;
+ } else if (adScenes == AdScenes.playCut.name) {
+ return adConfigModel.playCut;
+ } else if (adScenes == AdScenes.search.name) {
+ return adConfigModel.search;
+ } else if (adScenes == AdScenes.backup.name) {
+ return adConfigModel.backup;
+ }
+ return null;
+ }
+
/// 是否可以播放广告
- bool get isAdAvailable {
- return _interstitialAd != null;
+ bool isAdAvailable(String adScenes) {
+ return _interstitialAdMap[adScenes] != null;
}
/// 显示广告(如果存在且尚未显示)
/// 如果先前缓存的广告已过期,则只加载并缓存新广告
- void showAdIfAvailable({Function()? onTap}) {
- if (!isAdAvailable) {
+ void showAdIfAvailable(String adScenes, {Function()? onTap}) {
+ if (!isAdAvailable(adScenes)) {
LogUtil.d('尝试在可用之前显示广告');
- loadAd();
+
if(onTap != null) onTap();
return;
}
if (isShowingAd) {
LogUtil.d('尝试在已显示广告的情况下显示广告');
+
if(onTap != null) onTap();
return;
}
- if (DateTime.now().subtract(maxCacheDuration).isAfter(_appOpenLoadTime!)) {
+ if (DateTime.now().subtract(_maxCacheDuration).isAfter(_appOpenLoadTimeMap[adScenes]!)) {
LogUtil.d('超过了最大缓存持续时间。正在加载另一个广告');
- _interstitialAd!.dispose();
- _interstitialAd = null;
- loadAd();
+ _interstitialAdMap[adScenes]!.dispose();
+ loadAd(_interstitialAdMap[adScenes]?.adUnitId, adScenes);
+ _interstitialAdMap[adScenes] = null;
+ _interstitialAdMap.remove(adScenes);
+
if(onTap != null) onTap();
return;
}
+ if (_closeDateTime != null) {
+ // 计算时间差
+ Duration timeDifference = DateTime.now().difference(_closeDateTime!);
+
+ int seconds;
+ if (adScenes == AdScenes.hotLoading.name) {
+ seconds = MusicBox().getOpenAppEventDuration();
+ } else {
+ seconds = MusicBox().getInterstitialEventDuration();
+ }
+ if (timeDifference < Duration(seconds: seconds)) {
+ LogUtil.d('距离上次广告展示未达到插页广告事件间隔时长');
+
+ if(onTap != null) onTap();
+ return;
+ }
+ }
+
+ FirebaseAnalyticsManager.logPlayAdsChanceAction();
+
// 设置 fullScreenContentCallback 并显示广告
- _interstitialAd!.fullScreenContentCallback = FullScreenContentCallback(
+ _interstitialAdMap[adScenes]!.fullScreenContentCallback = FullScreenContentCallback(
// 暂停应用程序中的活动或记录广告展示的时间
onAdShowedFullScreenContent: (ad) {
- isShowingAd = true;
LogUtil.d('$ad onAdShowedFullScreenContent');
+ isShowingAd = true;
},
// 更适合用于跟踪广告展示的次数和效果,以及执行与广告展示相关的操作
onAdImpression: (ad) {
LogUtil.d('$ad onAdImpression');
+ FirebaseAnalyticsManager.logPlayAdsShowSuccess(ad);
},
onAdFailedToShowFullScreenContent: (ad, error) {
LogUtil.d('$ad onAdFailedToShowFullScreenContent: $error');
isShowingAd = false;
ad.dispose();
- _interstitialAd = null;
+ loadAd(_interstitialAdMap[adScenes]?.adUnitId, adScenes);
+ _interstitialAdMap[adScenes] = null;
+ _interstitialAdMap.remove(adScenes);
+ FirebaseAnalyticsManager.logPlayAdsLoadFailure(error.message);
if(onTap != null) onTap();
},
onAdDismissedFullScreenContent: (ad) {
LogUtil.d('$ad onAdDismissedFullScreenContent');
isShowingAd = false;
+ _closeDateTime = DateTime.now();
ad.dispose();
- _interstitialAd = null;
- loadAd();
+ loadAd(_interstitialAdMap[adScenes]?.adUnitId, adScenes);
+ _interstitialAdMap[adScenes] = null;
+ _interstitialAdMap.remove(adScenes);
if(onTap != null) onTap();
},
@@ -114,6 +229,6 @@ class InterstitialAdManager {
LogUtil.d('$ad onAdClicked');
},
);
- _interstitialAd!.show();
+ _interstitialAdMap[adScenes]!.show();
}
}
\ No newline at end of file
diff --git a/lib/ads/library_native_ad_manager.dart b/lib/ads/library_native_ad_manager.dart
new file mode 100644
index 0000000..a7fc693
--- /dev/null
+++ b/lib/ads/library_native_ad_manager.dart
@@ -0,0 +1,94 @@
+// Author: fengshengxiong
+// Date: 2024/6/25
+// Description: 原生广告
+
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:google_mobile_ads/google_mobile_ads.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
+import 'package:tone_snap/data/models/ad_config_model.dart';
+import 'package:tone_snap/data/storage/music_box.dart';
+import 'package:tone_snap/res/themes/app_colors.dart';
+import 'package:tone_snap/utils/log_util.dart';
+import 'package:tone_snap/utils/obj_util.dart';
+
+class LibraryNativeAdManager {
+ LibraryNativeAdManager._();
+
+ static final LibraryNativeAdManager _instance = LibraryNativeAdManager._();
+
+ factory LibraryNativeAdManager() => _instance;
+
+ /// 广告对象
+ NativeAd? nativeAd;
+
+ /// 广告是否已加载
+ var nativeAdIsLoaded = false.obs;
+
+ /// 加载广告
+ void loadNativeAd() {
+ AdConfigModel adConfigModel = MusicBox().getAdConfig();
+ if (adConfigModel.library != null && adConfigModel.library!.isNotEmpty) {
+ _loadAd(adConfigModel.library![0].identifier, AdScenes.library.name);
+ }
+ }
+
+ /// 加载广告
+ void _loadAd(String? adUnitId, String adScenes) async {
+ if (ObjUtil.isEmpty(adUnitId)) {
+ return;
+ }
+ nativeAd = NativeAd(
+ adUnitId: adUnitId!,
+ request: const AdRequest(),
+ listener: NativeAdListener(
+ onAdLoaded: (ad) {
+ LogUtil.d('$adScenes:${ad.adUnitId}原生广告加载完成');
+ nativeAdIsLoaded.value = true;
+ },
+ onAdFailedToLoad: (ad, error) {
+ LogUtil.e('$adScenes:${ad.adUnitId}原生广告加载失败: $error');
+ nativeAdIsLoaded.value = false;
+ loadNativeAd();
+ ad.dispose();
+ },
+ onAdClicked: (ad) {},
+ onAdImpression: (ad) {
+
+ },
+ onAdClosed: (ad) {},
+ onAdOpened: (ad) {},
+ onAdWillDismissScreen: (ad) {},
+ onPaidEvent: (ad, valueMicros, precision, currencyCode) {
+
+ },
+ ),
+ nativeTemplateStyle: NativeTemplateStyle(
+ templateType: TemplateType.medium,
+ mainBackgroundColor: scaffoldBgColor,
+ cornerRadius: 8.0,
+ callToActionTextStyle: NativeTemplateTextStyle(
+ backgroundColor: seedColor,
+ textColor: Colors.white,
+ style: NativeTemplateFontStyle.bold,
+ size: 16.0,
+ ),
+ primaryTextStyle: NativeTemplateTextStyle(
+ textColor: seedColor,
+ style: NativeTemplateFontStyle.bold,
+ size: 14.0,
+ ),
+ secondaryTextStyle: NativeTemplateTextStyle(
+ textColor: Colors.white,
+ style: NativeTemplateFontStyle.italic,
+ size: 12.0,
+ ),
+ tertiaryTextStyle: NativeTemplateTextStyle(
+ textColor: seedColor,
+ style: NativeTemplateFontStyle.normal,
+ size: 14.0,
+ ),
+ ),
+ )..load();
+ }
+}
\ No newline at end of file
diff --git a/lib/data/api/music_api.dart b/lib/data/api/music_api.dart
index 34c9be4..2d57c51 100644
--- a/lib/data/api/music_api.dart
+++ b/lib/data/api/music_api.dart
@@ -20,6 +20,7 @@ class MusicApi {
String? visitorData,
Map? queryParameters,
T Function(Map)? formJson,
+ Function(BaseError baseError)? fail,
bool showLoading = false,
bool showToast = false,
}) async {
@@ -47,6 +48,7 @@ class MusicApi {
queryParameters: queryParameters,
formJson: formJson,
success: (data) => model = data,
+ fail: fail
);
return model;
}
@@ -173,6 +175,7 @@ class MusicApi {
static Future search({
Map? queryParameters,
T Function(Map)? formJson,
+ Function(BaseError baseError)? fail,
bool showLoading = false,
bool showToast = true,
}) async {
@@ -199,6 +202,7 @@ class MusicApi {
queryParameters: queryParameters,
formJson: formJson,
success: (data) => model = data,
+ fail: fail,
);
return model;
}
diff --git a/lib/data/enum/ad_format.dart b/lib/data/enum/ad_format.dart
new file mode 100644
index 0000000..bc44a6f
--- /dev/null
+++ b/lib/data/enum/ad_format.dart
@@ -0,0 +1,15 @@
+// Author: fengshengxiong
+// Date: 2024/6/13
+// Description: 广告格式
+
+enum AdFormat {
+ /// 插页
+ insert('Insert'),
+
+ /// 原生
+ native('Native');
+
+ const AdFormat(this.type);
+
+ final String type;
+}
diff --git a/lib/data/enum/ad_scenes.dart b/lib/data/enum/ad_scenes.dart
new file mode 100644
index 0000000..365df22
--- /dev/null
+++ b/lib/data/enum/ad_scenes.dart
@@ -0,0 +1,39 @@
+// Author: fengshengxiong
+// Date: 2024/6/13
+// Description: 广告场景
+
+enum AdScenes {
+ /// 冷启动cold_loading(插页式广告)
+ coldLoading('coldLoading'),
+
+ /// 热启动hot_loading(插页式广告)
+ hotLoading('hotLoading'),
+
+ /// 播放play(插页式广告)
+ play('play'),
+
+ /// 下载download(插页式广告)
+ download('download'),
+
+ /// 列表list(插页式广告)
+ list('list'),
+
+ /// 曲库library(原生高级广告)
+ library('library'),
+
+ /// 切歌play_cut(插页式广告)
+ playCut('playCut'),
+
+ /// 点击搜索search(插页式广告)
+ search('search'),
+
+ /// 搜索结果展示search(原生高级广告)
+ searchResult('searchResult'),
+
+ /// 全局backup(插页式广告)
+ backup('backup');
+
+ const AdScenes(this.name);
+
+ final String name;
+}
diff --git a/lib/data/models/ad_config_model.dart b/lib/data/models/ad_config_model.dart
new file mode 100644
index 0000000..cedb959
--- /dev/null
+++ b/lib/data/models/ad_config_model.dart
@@ -0,0 +1,93 @@
+// Author: fengshengxiong
+// Date: 2024/8/6
+// Description: 广告配置模型
+
+import 'dart:convert';
+
+class AdConfigModel {
+ List? coldLoading;
+ List? hotLoading;
+ List? play;
+ List? download;
+ List? list;
+ List? library;
+ List? playCut;
+ List? search;
+ List? searchResult;
+ List? backup;
+
+ AdConfigModel({
+ this.coldLoading,
+ this.hotLoading,
+ this.play,
+ this.download,
+ this.list,
+ this.library,
+ this.playCut,
+ this.search,
+ this.searchResult,
+ this.backup,
+ });
+
+ factory AdConfigModel.fromJson(String str) => AdConfigModel.fromMap(json.decode(str));
+
+ String toJson() => json.encode(toMap());
+
+ factory AdConfigModel.fromMap(Map json) => AdConfigModel(
+ coldLoading: json["coldLoading"] == null ? [] : List.from(json["coldLoading"]!.map((x) => AdModel.fromMap(x))),
+ hotLoading: json["hotLoading"] == null ? [] : List.from(json["hotLoading"]!.map((x) => AdModel.fromMap(x))),
+ play: json["play"] == null ? [] : List.from(json["play"]!.map((x) => AdModel.fromMap(x))),
+ download: json["download"] == null ? [] : List.from(json["download"]!.map((x) => AdModel.fromMap(x))),
+ list: json["list"] == null ? [] : List.from(json["list"]!.map((x) => AdModel.fromMap(x))),
+ library: json["library"] == null ? [] : List.from(json["library"]!.map((x) => AdModel.fromMap(x))),
+ playCut: json["playCut"] == null ? [] : List.from(json["playCut"]!.map((x) => AdModel.fromMap(x))),
+ search: json["search"] == null ? [] : List.from(json["search"]!.map((x) => AdModel.fromMap(x))),
+ searchResult: json["searchResult"] == null ? [] : List.from(json["searchResult"]!.map((x) => AdModel.fromMap(x))),
+ backup: json["backup"] == null ? [] : List.from(json["backup"]!.map((x) => AdModel.fromMap(x))),
+ );
+
+ Map toMap() => {
+ "coldLoading": coldLoading == null ? [] : List.from(coldLoading!.map((x) => x.toMap())),
+ "hotLoading": hotLoading == null ? [] : List.from(hotLoading!.map((x) => x.toMap())),
+ "play": play == null ? [] : List.from(play!.map((x) => x.toMap())),
+ "download": download == null ? [] : List.from(download!.map((x) => x.toMap())),
+ "list": list == null ? [] : List.from(list!.map((x) => x.toMap())),
+ "library": library == null ? [] : List.from(library!.map((x) => x.toMap())),
+ "playCut": playCut == null ? [] : List.from(playCut!.map((x) => x.toMap())),
+ "search": search == null ? [] : List.from(search!.map((x) => x.toMap())),
+ "searchResult": searchResult == null ? [] : List.from(searchResult!.map((x) => x.toMap())),
+ "backup": backup == null ? [] : List.from(backup!.map((x) => x.toMap())),
+ };
+}
+
+class AdModel {
+ int? level;
+ String? identifier;
+ String? ad;
+ String? type;
+
+ AdModel({
+ this.level,
+ this.identifier,
+ this.ad,
+ this.type,
+ });
+
+ factory AdModel.fromJson(String str) => AdModel.fromMap(json.decode(str));
+
+ String toJson() => json.encode(toMap());
+
+ factory AdModel.fromMap(Map json) => AdModel(
+ level: json["level"],
+ identifier: json["identifier"],
+ ad: json["ad"],
+ type: json["type"],
+ );
+
+ Map toMap() => {
+ "level": level,
+ "identifier": identifier,
+ "ad": ad,
+ "type": type,
+ };
+}
diff --git a/lib/data/storage/music_box.dart b/lib/data/storage/music_box.dart
index 9d89d21..eb5999d 100644
--- a/lib/data/storage/music_box.dart
+++ b/lib/data/storage/music_box.dart
@@ -3,10 +3,12 @@
// Description: 音乐非结构化数据盒子
import 'dart:convert';
+import 'dart:io';
import 'package:hive/hive.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:tone_snap/data/enum/play_mode.dart';
+import 'package:tone_snap/data/models/ad_config_model.dart';
import 'package:tone_snap/data/storage/hive_storage.dart';
import 'package:tone_snap/global/app_config.dart';
import 'package:tone_snap/utils/log_util.dart';
@@ -21,21 +23,24 @@ class MusicBox {
return _instance;
}
- /// RemoteConfig openStatus
- final _openStatusKey = 'openStatusKey';
-
- /// RemoteConfig dataVersion
- final _dataVersionKey = 'dataVersionKey';
-
/// 是否进入过B面
final _isOpenedSideBKey = 'isOpenedSideBKey';
+ /// openStatus
+ final _openStatusKey = 'openStatusKey';
+
+ /// dataVersion
+ final _dataVersionKey = 'dataVersionKey';
+
/// 开屏启动插页事件间隔时长
final _openAppEventDurationKey = 'openAppEventDurationKey';
/// 插页广告事件间隔时长
final _interstitialEventDurationKey = 'interstitialEventDurationKey';
+ /// 广告配置
+ final _adMobLevelIDsKey = 'adMobLevelIDsKey';
+
/// 播放模式
final _playModeKey = 'playModeKey';
@@ -46,6 +51,16 @@ class MusicBox {
/// 注意, main函数中这个盒子已经打开, 可以进行存储操作
final _box = Hive.box(musicBox);
+ /// 设置是否打开过B面
+ Future putIsOpenedSideB(bool isOpen) async {
+ return await _box.put(_isOpenedSideBKey, isOpen);
+ }
+
+ /// 获取是否打开过B面
+ bool getIsOpenedSideB() {
+ return _box.get(_isOpenedSideBKey, defaultValue: false);
+ }
+
/// 设置 openStatus
Future putOpenStatus(String openStatus) async {
return await _box.put(_openStatusKey, openStatus);
@@ -114,16 +129,6 @@ class MusicBox {
return AppConfig.playerVersion;
}
- /// 设置是否打开过B面
- Future putIsOpenedSideB(bool isOpen) async {
- return await _box.put(_isOpenedSideBKey, isOpen);
- }
-
- /// 获取是否打开过B面
- bool getIsOpenedSideB() {
- return _box.get(_isOpenedSideBKey, defaultValue: false);
- }
-
/// 设置开屏启动插页事件间隔时长
Future putOpenAppEventDuration(int time) async {
return await _box.put(_openAppEventDurationKey, time);
@@ -144,6 +149,17 @@ class MusicBox {
return _box.get(_interstitialEventDurationKey, defaultValue: AppConfig.interstitialEventDuration);
}
+ /// 设置广告配置
+ Future putAdConfig(String adConfigStr) async {
+ return await _box.put(_adMobLevelIDsKey, adConfigStr);
+ }
+
+ /// 获取广告配置
+ AdConfigModel getAdConfig() {
+ var configStr = _box.get(_adMobLevelIDsKey, defaultValue: Platform.isIOS ? AppConfig.adIOSDefaultConfig : AppConfig.adAndroidDefaultConfig);
+ return AdConfigModel.fromJson(configStr);
+ }
+
/// 设置播放模式
Future putPlayMode(PlayMode playMode) async {
return await _box.put(_playModeKey, playMode);
diff --git a/lib/data/storage/playlists_box.dart b/lib/data/storage/playlists_box.dart
index 3a682b5..b8e75a6 100644
--- a/lib/data/storage/playlists_box.dart
+++ b/lib/data/storage/playlists_box.dart
@@ -9,6 +9,7 @@ import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/playlist_model.dart';
import 'package:tone_snap/data/storage/hive_storage.dart';
import 'package:tone_snap/utils/date_util.dart';
+import 'package:tone_snap/utils/log_util.dart';
class PlaylistsBox {
PlaylistsBox._();
@@ -25,12 +26,13 @@ class PlaylistsBox {
/// 获取列表
List getList() {
- return _box.values.toList();
- }
-
- /// 获取倒序列表
- List getReversedList() {
- return _box.values.toList().reversed.toList();
+ var list = _box.values.toList();
+ try {
+ list.sort((a, b) => b.milliseconds!.compareTo(a.milliseconds!));
+ } catch (e) {
+ LogUtil.e(e.toString());
+ }
+ return list;
}
/// 添加数据
diff --git a/lib/firebase/firebase_analytics_manager.dart b/lib/firebase/firebase_analytics_manager.dart
index 36acf0a..ab66268 100644
--- a/lib/firebase/firebase_analytics_manager.dart
+++ b/lib/firebase/firebase_analytics_manager.dart
@@ -3,17 +3,291 @@
// Description: firebase_analytics管理
import 'package:firebase_analytics/firebase_analytics.dart';
+import 'package:google_mobile_ads/google_mobile_ads.dart';
+import 'package:tone_snap/utils/log_util.dart';
+import 'package:tone_snap/utils/obj_util.dart';
class FirebaseAnalyticsManager {
- static const homeApv = 'home_a_pv';
-
/// 埋点
/// name:事件名
/// parameters:业务参数
static void logEvent(String eventName, {Map? parameters}) {
- FirebaseAnalytics.instance.logEvent(
- name: eventName,
- parameters: parameters,
+ try {
+ FirebaseAnalytics.instance.logEvent(
+ name: eventName,
+ parameters: parameters,
+ );
+ } catch (e) {
+ LogUtil.e(e.toString());
+ }
+ }
+
+ /// 启动页曝光
+ static void logLaunchPV() {
+ logEvent('launch_pv');
+ }
+
+ /// 跳转
+ static void logJumpEvent(String? side, String? reason) {
+ logEvent('jump_event',
+ parameters: {
+ 'side': ObjUtil.getStr(side),
+ 'reason': ObjUtil.getStr(reason)
+ },
+ );
+ }
+
+ /// A面首页曝光
+ static void logHomeAPV() {
+ logEvent('home_a_pv');
+ }
+
+ /// B面首页曝光
+ static void logHomeBPV() {
+ logEvent('home_b_pv');
+ }
+
+ /// 首页资源曝光成功
+ static void logHomeBModuleShowsuccesAction() {
+ logEvent('home_b_module_showsucces_action');
+ }
+
+ /// 首页资源曝光失败
+ static void logHomeBModuleFailAction(String? failreason) {
+ logEvent('home_b_module_fail_action',
+ parameters: {
+ 'failreason': ObjUtil.getStr(failreason)
+ },
+ );
+ }
+
+ /// 点击首页模块
+ static void logHomeBModuleClick(String? modulename) {
+ logEvent('home_b_module_click',
+ parameters: {
+ 'modulename': ObjUtil.getStr(modulename)
+ },
+ );
+ }
+
+ /// B面我的曝光
+ static void logMeBPV() {
+ logEvent('me_b_pv');
+ }
+
+ /// 点击歌曲
+ static void logSongClick(String? songfrom) {
+ logEvent('song_click',
+ parameters: {
+ 'songfrom': ObjUtil.getStr(songfrom)
+ },
+ );
+ }
+
+ /// B面歌曲曝光
+ static void logPlayerBpv(String? videoid, String? videoname, String? artistname) {
+ logEvent('player_b_pv',
+ parameters: {
+ 'videoid': ObjUtil.getStr(videoid),
+ 'videoname': ObjUtil.getStr(videoname),
+ 'artistname': ObjUtil.getStr(artistname),
+ },
+ );
+ }
+
+ /// 统计加载时间
+ static void logPlayerBDelayAction(String? videoid, String? videoname, String? artistname, String? delay) {
+ logEvent('player_b_delay_action',
+ parameters: {
+ 'videoid': ObjUtil.getStr(videoid),
+ 'videoname': ObjUtil.getStr(videoname),
+ 'artistname': ObjUtil.getStr(artistname),
+ 'delay': ObjUtil.getStr(delay),
+ },
+ );
+ }
+
+ /// 播放成功
+ static void logPlayerBSuccessAction(String? videoid, String? videoname, String? artistname) {
+ logEvent('player_b_success_action',
+ parameters: {
+ 'videoid': ObjUtil.getStr(videoid),
+ 'videoname': ObjUtil.getStr(videoname),
+ 'artistname': ObjUtil.getStr(artistname),
+ },
+ );
+ }
+
+ /// 播放失败
+ static void logPlayerBFailAction(String? failreason) {
+ logEvent('player_b_fail_action',
+ parameters: {
+ 'failreason': ObjUtil.getStr(failreason)
+ },
+ );
+ }
+
+ /// 收藏
+ static void logPlayerBLoveClick(String? videoid, String? videoname, String? artistname) {
+ logEvent('player_b_love_click',
+ parameters: {
+ 'videoid': ObjUtil.getStr(videoid),
+ 'videoname': ObjUtil.getStr(videoname),
+ 'artistname': ObjUtil.getStr(artistname),
+ },
+ );
+ }
+
+ /// 取消收藏
+ static void logPlayerBUnloveClick(String? videoid, String? videoname, String? artistname) {
+ logEvent('player_b_unlove_click',
+ parameters: {
+ 'videoid': ObjUtil.getStr(videoid),
+ 'videoname': ObjUtil.getStr(videoname),
+ 'artistname': ObjUtil.getStr(artistname),
+ },
+ );
+ }
+
+ /// 点击下载
+ static void logPlayerBDownloadClick(String? videoid, String? videoname, String? artistname) {
+ logEvent('player_b_download_click',
+ parameters: {
+ 'videoid': ObjUtil.getStr(videoid),
+ 'videoname': ObjUtil.getStr(videoname),
+ 'artistname': ObjUtil.getStr(artistname),
+ },
+ );
+ }
+
+ /// 下载成功
+ static void logPlayerBDownloadsuccessAction(String? videoid, String? videoname, String? artistname) {
+ logEvent('player_b_downloadsuccess_action',
+ parameters: {
+ 'videoid': ObjUtil.getStr(videoid),
+ 'videoname': ObjUtil.getStr(videoname),
+ 'artistname': ObjUtil.getStr(artistname),
+ },
+ );
+ }
+
+ /// 下载失败
+ static void logPlayerBDownloadfailAction(String? failreason) {
+ logEvent('player_b_downloadfail_action',
+ parameters: {
+ 'failreason': ObjUtil.getStr(failreason)
+ },
+ );
+ }
+
+ /// B面搜索曝光
+ static void logSearchPV() {
+ logEvent('search_pv');
+ }
+
+ /// 搜索SUG曝光
+ static void logSearchSugShow() {
+ logEvent('search_sug_show');
+ }
+
+ /// 输入搜索内容,点击搜索(包含联想点击)
+ static void logSearchSugClick(String? sugname) {
+ logEvent('search_sug_click',
+ parameters: {
+ 'sugname': ObjUtil.getStr(sugname)
+ },
+ );
+ }
+
+ /// 搜索结果曝光
+ static void logSearchResultPV() {
+ logEvent('search_result_pv');
+ }
+
+ /// 搜索有结果
+ static void logSearchResultsuccessAction() {
+ logEvent('search_resultsuccess_action');
+ }
+
+ /// 搜索无结果
+ static void logSearchResultfailAction(String? failreason) {
+ logEvent('search_resultfail_action',
+ parameters: {
+ 'failreason': ObjUtil.getStr(failreason)
+ },
+ );
+ }
+
+ /// 搜索点击
+ static void logSearchFromAction(String? searchfrom) {
+ logEvent('search_from_action',
+ parameters: {
+ 'searchfrom': ObjUtil.getStr(searchfrom)
+ },
+ );
+ }
+
+ /// 播放器弹出
+ static void logPlayerBImp() {
+ logEvent('player_b_imp');
+ }
+
+ /// 列表加载
+ static void logPlayerBList() {
+ logEvent('player_b_list');
+ }
+
+ /// 创建歌单
+ static void logCreateList() {
+ logEvent('create_list');
+ }
+
+ /// 曲库点击
+ static void logLibraryClick(String? clickfrom, String? folderclick) {
+ logEvent('library_click',
+ parameters: {
+ 'clickfrom': ObjUtil.getStr(clickfrom),
+ 'folderclick': ObjUtil.getStr(folderclick)
+ },
+ );
+ }
+
+ /// 广告展示机会
+ static void logPlayAdsChanceAction() {
+ logEvent('play_ads_chance');
+ }
+
+ /// 广告加载失败
+ static void logPlayAdsLoadFailure(String? error) {
+ logEvent('play_ads_loadFailure',
+ parameters: {
+ 'error': ObjUtil.getStr(error),
+ },
+ );
+ }
+
+ /// 广告展示成功
+ static void logPlayAdsShowSuccess(Ad ad) {
+ var mediation = '';
+ if (ad.responseInfo != null && ad.responseInfo!.responseExtras.isNotEmpty) {
+ mediation = ad.responseInfo!.responseExtras['mediation_group_name'];
+ }
+ logEvent('play_ads_showSuccess',
+ parameters: {
+ 'platform': ObjUtil.getStr(ad.responseInfo?.loadedAdapterResponseInfo?.adSourceName),
+ 'source': ObjUtil.getStr(ad.responseInfo?.loadedAdapterResponseInfo?.adSourceInstanceName),
+ 'unitName': ObjUtil.getStr(mediation),
+ },
+ );
+
+ }
+
+ /// 广告展示失败
+ static void logPlayAdsShowFailure(String? error) {
+ logEvent('play_ads_showFailure',
+ parameters: {
+ 'error': ObjUtil.getStr(error),
+ },
);
}
}
diff --git a/lib/firebase/firebase_options.dart b/lib/firebase/firebase_options.dart
index ec6df03..f20d44c 100644
--- a/lib/firebase/firebase_options.dart
+++ b/lib/firebase/firebase_options.dart
@@ -23,8 +23,8 @@ class DefaultFirebaseOptions {
);
}
switch (defaultTargetPlatform) {
- // case TargetPlatform.android:
- // return android;
+ case TargetPlatform.android:
+ return android;
case TargetPlatform.iOS:
return ios;
case TargetPlatform.macOS:
@@ -49,13 +49,13 @@ class DefaultFirebaseOptions {
}
}
- // static const FirebaseOptions android = FirebaseOptions(
- // apiKey: 'AIzaSyDP6CpkN3HMpCofXlToug-hadYpTLEgE0E',
- // appId: '1:318284530945:android:c7dd2abf520a9840250700',
- // messagingSenderId: '318284530945',
- // projectId: 'nowwallpaper',
- // storageBucket: 'nowwallpaper.appspot.com',
- // );
+ static const FirebaseOptions android = FirebaseOptions(
+ apiKey: 'AIzaSyCLEOFjenMfiZKdoKdGh9mylckpIpTn854',
+ appId: '1:280378307504:android:bcee5d63088dcbb7ca5c75',
+ messagingSenderId: '280378307504',
+ projectId: 'tonesnap-and',
+ storageBucket: 'tonesnap-and.appspot.com',
+ );
static const FirebaseOptions ios = FirebaseOptions(
apiKey: 'AIzaSyADeT4ReAayii_M9tl0GHR6tUlyVTXAaSU',
diff --git a/lib/firebase/firebase_remote_config_manager.dart b/lib/firebase/firebase_remote_config_manager.dart
index 16adbd8..df4a7a8 100644
--- a/lib/firebase/firebase_remote_config_manager.dart
+++ b/lib/firebase/firebase_remote_config_manager.dart
@@ -60,6 +60,12 @@ class FirebaseRemoteConfigManager {
LogUtil.e(e.toString());
}
}
+
+ // 获取 adMobLevelIDs
+ var adMobLevelIDs = allData['adMobLevelIDs']?.asString();
+ if (ObjUtil.isNotEmpty(adMobLevelIDs)) {
+ await MusicBox().putAdConfig(adMobLevelIDs!);
+ }
}
} catch (e) {
LogUtil.e(e.toString());
diff --git a/lib/global/app_config.dart b/lib/global/app_config.dart
index 7f4e180..c0fb46d 100644
--- a/lib/global/app_config.dart
+++ b/lib/global/app_config.dart
@@ -24,5 +24,269 @@ class AppConfig {
/// 所在区域,默认值
static String isoCode = 'HK';
+
+ /// iOS广告场景默认配置,level越小价值越高
+ static String adIOSDefaultConfig = '''{
+ "coldLoading": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/5757639118",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/5852437064",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "hotLoading": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/3414045794",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/9382223193",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "play": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/9827341094",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/5270553952",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "download": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/2060531710",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/1913192057",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "list": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/9600110380",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/6079667773",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "library": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/7785036659",
+ "ad": "AdMob",
+ "type": "Native"
+ }
+ ],
+ "playCut": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/8551323416",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/2140422769",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "search": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/8434368376",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/9505312439",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "searchResult": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/1645766617",
+ "ad": "AdMob",
+ "type": "Native"
+ }
+ ],
+ "backup": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/5987502154",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/5361482476",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ]
+ }''';
+
+ /// Android广告场景默认配置,level越小价值越高
+ static String adAndroidDefaultConfig = '''{
+ "coldLoading": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/2307556684",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/3395973732",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "hotLoading": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/2527048534",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/5763116779",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "play": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/4450035105",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/4279113904",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "download": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/3136953430",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/7368311678",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "list": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/1823871765",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/2536557320",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "library": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/5438052836",
+ "ad": "AdMob",
+ "type": "Native"
+ }
+ ],
+ "playCut": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/5727092792",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/8197708422",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "search": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/4971148979",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/8026787222",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ],
+ "searchResult": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/6848602773",
+ "ad": "AdMob",
+ "type": "Native"
+ }
+ ],
+ "backup": [
+ {
+ "level": 1,
+ "identifier": "ca-app-pub-5684307632319406/8718822297",
+ "ad": "AdMob",
+ "type": "Insert"
+ },
+ {
+ "level": 2,
+ "identifier": "ca-app-pub-5684307632319406/4517483713",
+ "ad": "AdMob",
+ "type": "Insert"
+ }
+ ]
+ }''';
}
diff --git a/lib/global/app_lifecycle_reactor.dart b/lib/global/app_lifecycle_reactor.dart
index 9904460..4709c52 100644
--- a/lib/global/app_lifecycle_reactor.dart
+++ b/lib/global/app_lifecycle_reactor.dart
@@ -3,8 +3,8 @@
// Description: 应用程序生命周期反应器
import 'package:google_mobile_ads/google_mobile_ads.dart';
-import 'package:tone_snap/ads/app_open_ad_manager.dart';
import 'package:tone_snap/ads/interstitial_ad_manager.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
class AppLifecycleReactor {
AppLifecycleReactor._();
@@ -23,7 +23,7 @@ class AppLifecycleReactor {
void _onAppStateChanged(AppState appState) {
// 如果应用程序正在恢复前台,尝试显示开屏广告
if (appState == AppState.foreground && !InterstitialAdManager().isShowingAd) {
- AppOpenAdManager().showAdIfAvailable();
+ InterstitialAdManager().showAdIfAvailable(AdScenes.hotLoading.name);
}
}
}
\ No newline at end of file
diff --git a/lib/global/download_manager.dart b/lib/global/download_manager.dart
index a42b801..775b630 100644
--- a/lib/global/download_manager.dart
+++ b/lib/global/download_manager.dart
@@ -5,11 +5,14 @@
import 'package:background_downloader/background_downloader.dart';
import 'package:dio/dio.dart';
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/data/api/music_api.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/player_model.dart';
import 'package:tone_snap/data/storage/offline_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/modules/sideb/offline/offline_controller.dart';
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_controller.dart';
import 'package:tone_snap/utils/date_util.dart';
@@ -49,6 +52,11 @@ class DownloadManager extends GetxController {
break;
case TaskStatus.complete:
LogUtil.d('音乐下载路径:${await update.task.filePath()}');
+ FirebaseAnalyticsManager.logPlayerBDownloadsuccessAction(
+ musicModel.videoId,
+ musicModel.title,
+ musicModel.subtitle,
+ );
musicModel.localPath = await update.task.filePath();
OfflineBox().add(musicModel);
downloadList.remove(musicModel);
@@ -63,14 +71,17 @@ class DownloadManager extends GetxController {
case TaskStatus.notFound:
BaseEasyLoading.toast('Download failed');
downloadList.remove(musicModel);
+ FirebaseAnalyticsManager.logPlayerBDownloadfailAction('404,找不到url');
break;
case TaskStatus.failed:
BaseEasyLoading.toast('Download failed');
downloadList.remove(musicModel);
+ FirebaseAnalyticsManager.logPlayerBDownloadfailAction('下载失败');
break;
case TaskStatus.canceled:
BaseEasyLoading.toast('Download cancelled');
downloadList.remove(musicModel);
+ FirebaseAnalyticsManager.logPlayerBDownloadfailAction('取消下载');
break;
case TaskStatus.waitingToRetry:
break;
@@ -89,37 +100,47 @@ class DownloadManager extends GetxController {
/// 下载文件
void downloadMusic(MusicModel? m) {
- if (m == null) return;
- MusicModel musicModel = m.copyWith();
- musicModel.taskStatus = TaskStatus.enqueued;
- musicModel.cancelToken = CancelToken();
- downloadList.add(musicModel);
- updateDownloadState();
+ FirebaseAnalyticsManager.logPlayerBDownloadClick(
+ m?.videoId,
+ m?.title,
+ m?.subtitle,
+ );
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.download.name,
+ onTap: () {
+ if (m == null) return;
+ MusicModel musicModel = m.copyWith();
+ musicModel.taskStatus = TaskStatus.enqueued;
+ musicModel.cancelToken = CancelToken();
+ downloadList.add(musicModel);
+ updateDownloadState();
- _getMusicUrl(musicModel, (url, mimeType) async {
- if (ObjUtil.isNotEmpty(url)) {
- String extension = mimeType ?? 'mp4';
- if (ObjUtil.isNotEmpty(mimeType)) {
- // 从 mimeType 中提取主类型和子类型
- String type = mimeType!.split(';')[0].trim();
- // 获取文件扩展名
- extension = type.split('/')[1];
- }
- final filename = '${musicModel.title}_${DateUtil.getNowTimestamp()}.$extension';
- final task = DownloadTask(
- taskId: musicModel.videoId,
- url: url!,
- filename: filename,
- directory: LocalPathUtil.getMusicDownloadDir(),
- updates: Updates.statusAndProgress,
- requiresWiFi: false,
- retries: 0,
- allowPause: false,
- metaData: '',
- );
- tq?.add(task);
- }
- });
+ _getMusicUrl(musicModel, (url, mimeType) async {
+ if (ObjUtil.isNotEmpty(url)) {
+ String extension = mimeType ?? 'mp4';
+ if (ObjUtil.isNotEmpty(mimeType)) {
+ // 从 mimeType 中提取主类型和子类型
+ String type = mimeType!.split(';')[0].trim();
+ // 获取文件扩展名
+ extension = type.split('/')[1];
+ }
+ final filename = '${musicModel.title}_${DateUtil.getNowTimestamp()}.$extension';
+ final task = DownloadTask(
+ taskId: musicModel.videoId,
+ url: url!,
+ filename: filename,
+ directory: LocalPathUtil.getMusicDownloadDir(),
+ updates: Updates.statusAndProgress,
+ requiresWiFi: false,
+ retries: 0,
+ allowPause: false,
+ metaData: '',
+ );
+ tq?.add(task);
+ }
+ });
+ },
+ );
}
Future _getMusicUrl(MusicModel musicModel, Function(String? url, String? mimeType) onTap) async {
@@ -134,6 +155,7 @@ class DownloadManager extends GetxController {
musicModel.taskStatus = TaskStatus.failed;
musicModel.cancelToken = null;
}
+ FirebaseAnalyticsManager.logPlayerBDownloadfailAction('${baseError.code},${baseError.message}');
downloadList.remove(musicModel);
updateDownloadState();
}
diff --git a/lib/global/network_connectivity_service.dart b/lib/global/network_connectivity_service.dart
index 446baf5..099d473 100644
--- a/lib/global/network_connectivity_service.dart
+++ b/lib/global/network_connectivity_service.dart
@@ -6,10 +6,9 @@ import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:get/get.dart';
-import 'package:tone_snap/ads/app_open_ad_manager.dart';
import 'package:tone_snap/ads/interstitial_ad_manager.dart';
+import 'package:tone_snap/ads/library_native_ad_manager.dart';
import 'package:tone_snap/firebase/firebase_remote_config_manager.dart';
-import 'package:tone_snap/modules/launch/launch_controller.dart';
import 'package:tone_snap/utils/log_util.dart';
class NetworkConnectivityService extends GetxService {
@@ -22,18 +21,14 @@ class NetworkConnectivityService extends GetxService {
super.onInit();
subscription = Connectivity().onConnectivityChanged.listen((List result) {
LogUtil.d('当前网络连接类型:$result');
- if (result.contains(ConnectivityResult.wifi) || result.contains(ConnectivityResult.mobile)) {
+ if (!result.contains(ConnectivityResult.none)) {
if (!isExecutedTask) {
isExecutedTask = true;
FirebaseRemoteConfigManager.getAll();
- if (Get.isRegistered()) {
- LaunchController.to.getIsoCode();
- }
-
- InterstitialAdManager().loadAd();
- AppOpenAdManager().loadAd();
+ InterstitialAdManager().loadAdAll();
+ // LibraryNativeAdManager().loadNativeAd();
}
}
});
diff --git a/lib/main.dart b/lib/main.dart
index b08c9bb..c7b224f 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -20,8 +20,6 @@ import 'package:tone_snap/modules/sideb/music_bar/music_bar_controller.dart';
import 'package:tone_snap/res/themes/app_themes.dart';
import 'package:tone_snap/routes/app_pages.dart';
import 'package:tone_snap/routes/app_routes.dart';
-import 'package:tone_snap/utils/file_util.dart';
-import 'package:tone_snap/utils/local_path_util.dart';
import 'package:tone_snap/utils/log_util.dart';
void main() async {
@@ -38,7 +36,11 @@ void main() async {
}
// 初始化广告 SDK
- MobileAds.instance.initialize();
+ try {
+ MobileAds.instance.initialize();
+ } catch (e) {
+ LogUtil.e("MobileAds initialization error: $e");
+ }
// 初始化Hive
await initHive();
@@ -59,10 +61,6 @@ void main() async {
systemNavigationBarIconBrightness: Brightness.light,
));
}
-
- // 删除缓存文件
- FileUtil.deleteAllFilesInDirectory(await LocalPathUtil.getRecordingsDir());
- FileUtil.deleteAllFilesInDirectory(await LocalPathUtil.getAssetsDir());
}
class MyApp extends StatelessWidget {
@@ -75,7 +73,9 @@ class MyApp extends StatelessWidget {
navigatorObservers = [
GetObserver((_) {
WidgetsBinding.instance.addPostFrameCallback((_) {
- if (Get.currentRoute == AppRoutes.playPage) {
+ if (Get.currentRoute == AppRoutes.playPage
+ || Get.currentRoute == AppRoutes.privacy
+ || Get.currentRoute == AppRoutes.terms) {
MusicBar().hide();
} else {
if (Get.isRegistered()) {
diff --git a/lib/modules/launch/launch_controller.dart b/lib/modules/launch/launch_controller.dart
index d110424..e6613ba 100644
--- a/lib/modules/launch/launch_controller.dart
+++ b/lib/modules/launch/launch_controller.dart
@@ -2,14 +2,12 @@ import 'dart:async';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
-import 'package:tone_snap/ads/app_open_ad_manager.dart';
-import 'package:tone_snap/data/api/tikustok_api.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/enum/app_side_enum.dart';
-import 'package:tone_snap/data/models/base_model.dart';
-import 'package:tone_snap/data/models/isocode_model.dart';
import 'package:tone_snap/data/storage/music_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/global/app_config.dart';
-import 'package:tone_snap/global/app_lifecycle_reactor.dart';
import 'package:tone_snap/global/network_connectivity_service.dart';
import 'package:tone_snap/modules/sideb/controllers/main_controller.dart';
import 'package:tone_snap/routes/app_routes.dart';
@@ -25,17 +23,12 @@ class LaunchController extends GetxController with GetSingleTickerProviderStateM
/// 进度每次变化值
var changeValue = 10;
- late AppLifecycleReactor _appLifecycleReactor;
-
@override
void onInit() {
super.onInit();
- _appLifecycleReactor = AppLifecycleReactor();
- _appLifecycleReactor.listenToAppStateChanges();
-
- Get.put(NetworkConnectivityService());
-
_startTimer();
+ Get.put(NetworkConnectivityService());
+ FirebaseAnalyticsManager.logLaunchPV();
}
@override
@@ -46,14 +39,12 @@ class LaunchController extends GetxController with GetSingleTickerProviderStateM
/// 开始定时器
void _startTimer() {
- _timer = Timer.periodic(Duration(milliseconds: changeValue), (Timer t) {
+ _timer = Timer.periodic(const Duration(milliseconds: 10), (Timer t) {
if (currentProcess.value + changeValue >= timeTotal) {
currentProcess.value = timeTotal;
- if (currentProcess >= timeTotal) {
- _stopTimer();
- _checkEnter();
- return;
- }
+ _stopTimer();
+ _checkEnter();
+ return;
}
currentProcess.value += changeValue;
});
@@ -67,7 +58,7 @@ class LaunchController extends GetxController with GetSingleTickerProviderStateM
/// 修改进度变化值
void editChangeValue() {
- changeValue = 3000;
+ changeValue = 200;
}
/// 校验开关和版本,决定进A还是B
@@ -75,18 +66,18 @@ class LaunchController extends GetxController with GetSingleTickerProviderStateM
bool isOpenedSideB = MusicBox().getIsOpenedSideB();
if (isOpenedSideB) {
LogUtil.d('进入过B面');
- _openSideB();
+ _openSideB('进入过B面');
} else {
bool enter = MusicBox().getEnter();
String versionCode = await MusicBox().getVersionCode();
final packageInfo = await PackageInfo.fromPlatform();
if (versionCode != packageInfo.version) {
LogUtil.d('版本不相同,进入B面');
- _openSideB();
+ _openSideB('版本不相同');
} else {
if (enter) {
LogUtil.d('开关:打开');
- _openSideB();
+ _openSideB('版本相同, 开关打开');
} else {
LogUtil.d('开关:关闭');
_openSideA();
@@ -96,26 +87,26 @@ class LaunchController extends GetxController with GetSingleTickerProviderStateM
}
void _openSideA() {
- AppOpenAdManager().showAdIfAvailable(onTap: () {
- AppConfig.appSideEnum = AppSideEnum.sideA;
- Get.offNamed(AppRoutes.initialA);
- });
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.coldLoading.name,
+ onTap: () {
+ AppConfig.appSideEnum = AppSideEnum.sideA;
+ Get.offNamed(AppRoutes.initialA);
+ },
+ );
+ FirebaseAnalyticsManager.logJumpEvent('A', '版本相同,开关关闭');
}
- void _openSideB() {
- AppOpenAdManager().showAdIfAvailable(onTap: () {
- AppConfig.appSideEnum = AppSideEnum.sideB;
- MainController.to.changeTheme();
- Get.offNamed(AppRoutes.initialB);
- MusicBox().putIsOpenedSideB(true);
- });
- }
-
- /// 获取所在区域
- Future getIsoCode() async {
- BaseModel? model = await TikUsTokApi.getIsoCode();
- if (model != null && model.success && model.data?.isoCode != null) {
- AppConfig.isoCode = model.data!.isoCode!;
- }
+ void _openSideB(String reason) {
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.coldLoading.name,
+ onTap: () {
+ AppConfig.appSideEnum = AppSideEnum.sideB;
+ MainController.to.changeTheme();
+ Get.offNamed(AppRoutes.initialB);
+ MusicBox().putIsOpenedSideB(true);
+ },
+ );
+ FirebaseAnalyticsManager.logJumpEvent('B', reason);
}
}
diff --git a/lib/modules/launch/launch_view.dart b/lib/modules/launch/launch_view.dart
index 435b700..fa85b00 100644
--- a/lib/modules/launch/launch_view.dart
+++ b/lib/modules/launch/launch_view.dart
@@ -4,7 +4,6 @@ import 'package:get/get.dart';
import 'package:step_progress_indicator/step_progress_indicator.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/modules/launch/launch_controller.dart';
-import 'package:tone_snap/res/themes/app_colors.dart';
class LaunchView extends StatelessWidget {
LaunchView({super.key});
diff --git a/lib/modules/sidea/change_voice/change_voice_controller.dart b/lib/modules/sidea/change_voice/change_voice_controller.dart
index c7f1d56..acec0d9 100644
--- a/lib/modules/sidea/change_voice/change_voice_controller.dart
+++ b/lib/modules/sidea/change_voice/change_voice_controller.dart
@@ -4,6 +4,7 @@ import 'package:ffmpeg_kit_flutter_audio/return_code.dart';
import 'package:get/get.dart';
import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/base_easyloading.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/data/storage/my_voice_box.dart';
import 'package:tone_snap/generated/assets.dart';
@@ -46,8 +47,6 @@ class ChangeVoiceController extends GetxController {
@override
void onInit() {
super.onInit();
- InterstitialAdManager().loadAd();
-
filePath = Get.arguments;
// playerController.setFilePath(filePath);
}
@@ -104,9 +103,10 @@ class ChangeVoiceController extends GetxController {
/// 保存
Future save() async {
- // 显示插页广告
InterstitialAdManager().showAdIfAvailable(
+ AdScenes.download.name,
onTap: () async {
+ await playerController.startPlay();
// 停止播放
if (playerController.isPlaying.value) playerController.stopPlay();
BaseEasyLoading.loading();
diff --git a/lib/modules/sidea/home/home_controller.dart b/lib/modules/sidea/home/home_controller.dart
index 4dc7258..ab13302 100644
--- a/lib/modules/sidea/home/home_controller.dart
+++ b/lib/modules/sidea/home/home_controller.dart
@@ -1,5 +1,7 @@
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/base_easyloading.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/generated/assets.dart';
import 'package:tone_snap/modules/sidea/controllers/player_controller.dart';
@@ -22,22 +24,32 @@ class HomeController extends GetxController {
];
void onTapItem(VoiceModel item) {
- InitialController.to.currentPlayVoiceModel.value = item;
- Get.toNamed(AppRoutes.playSound, arguments: item);
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ InitialController.to.currentPlayVoiceModel.value = item;
+ Get.toNamed(AppRoutes.playSound, arguments: item);
+ },
+ );
}
Future onTapPlayBarPlay(VoiceModel item) async {
if (isPlayItem(item)) {
await playerController.pausePlay();
} else {
- if (!identical(item, InitialController.to.currentPlayVoiceModel.value)) {
- BaseEasyLoading.loading();
- await playerController.setFilePath(item.path);
- BaseEasyLoading.dismiss();
- InitialController.to.currentPlayVoiceModel.value = item;
- InitialController.to.isFavourite.value = InitialController.to.getIsFavouriteModel() != null;
- }
- await playerController.startPlay();
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () async {
+ if (!identical(item, InitialController.to.currentPlayVoiceModel.value)) {
+ BaseEasyLoading.loading();
+ await playerController.setFilePath(item.path);
+ BaseEasyLoading.dismiss();
+ InitialController.to.currentPlayVoiceModel.value = item;
+ InitialController.to.isFavourite.value = InitialController.to.getIsFavouriteModel() != null;
+ }
+ await playerController.startPlay();
+ },
+ );
}
}
diff --git a/lib/modules/sidea/initial/initial_controller.dart b/lib/modules/sidea/initial/initial_controller.dart
index 278b9d8..5b1c5a4 100644
--- a/lib/modules/sidea/initial/initial_controller.dart
+++ b/lib/modules/sidea/initial/initial_controller.dart
@@ -4,6 +4,7 @@ import 'package:tone_snap/data/models/voice_model.dart';
import 'package:tone_snap/data/storage/favorite_box.dart';
import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/generated/assets.dart';
+import 'package:tone_snap/global/app_lifecycle_reactor.dart';
import 'package:tone_snap/global/app_tracking_transparency_manager.dart';
import 'package:tone_snap/modules/sidea/controllers/player_controller.dart';
import 'package:tone_snap/modules/sidea/favourite/favourite_controller.dart';
@@ -12,6 +13,8 @@ import 'package:tone_snap/modules/sidea/me/me_view.dart';
import 'package:tone_snap/modules/sidea/my_voice/my_voice_controller.dart';
import 'package:tone_snap/modules/sidea/settings/settings_view.dart';
import 'package:tone_snap/routes/app_routes.dart';
+import 'package:tone_snap/utils/file_util.dart';
+import 'package:tone_snap/utils/local_path_util.dart';
class InitialController extends GetxController {
static InitialController get to => Get.find();
@@ -29,17 +32,27 @@ class InitialController extends GetxController {
/// 是否收藏
var isFavourite = false.obs;
+ late AppLifecycleReactor _appLifecycleReactor;
+
@override
void onInit() {
super.onInit();
AppTrackingTransparencyManager().requestATT();
+
+ _appLifecycleReactor = AppLifecycleReactor();
+ _appLifecycleReactor.listenToAppStateChanges();
+
pageController = PageController(initialPage: currentIndex.value);
}
@override
void onReady() async {
super.onReady();
- _addEventLog();
+ // 删除缓存文件
+ FileUtil.deleteAllFilesInDirectory(await LocalPathUtil.getRecordingsDir());
+ FileUtil.deleteAllFilesInDirectory(await LocalPathUtil.getAssetsDir());
+
+ FirebaseAnalyticsManager.logHomeAPV();
}
@override
@@ -55,10 +68,10 @@ class InitialController extends GetxController {
} else {
currentIndex.value = index;
pageController.jumpToPage(index);
- if (index == 0) {
- _addEventLog();
- }
if (index == 2) _refreshMe();
+ if (index == 0) {
+ FirebaseAnalyticsManager.logHomeAPV();
+ }
}
}
@@ -95,11 +108,6 @@ class InitialController extends GetxController {
if (Get.isRegistered()) MyVoiceController.to.getData();
if (Get.isRegistered()) FavouriteController.to.getData();
}
-
- /// 埋点
- void _addEventLog() {
- FirebaseAnalyticsManager.logEvent(FirebaseAnalyticsManager.homeApv);
- }
}
class PageItem {
diff --git a/lib/modules/sideb/add_to_playlist_bottom_sheet/add_to_playlist_bottom_sheet_controller.dart b/lib/modules/sideb/add_to_playlist_bottom_sheet/add_to_playlist_bottom_sheet_controller.dart
index 7824d78..2a7fe91 100644
--- a/lib/modules/sideb/add_to_playlist_bottom_sheet/add_to_playlist_bottom_sheet_controller.dart
+++ b/lib/modules/sideb/add_to_playlist_bottom_sheet/add_to_playlist_bottom_sheet_controller.dart
@@ -1,4 +1,3 @@
-import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/components/dialog/add_playlist_dialog.dart';
@@ -19,7 +18,7 @@ class AddToPlaylistBottomSheetController extends GetxController {
}
void _getList() {
- playlists.value = PlaylistsBox().getReversedList();
+ playlists.value = PlaylistsBox().getList();
}
void setMusicModel(MusicModel musicModel) {
diff --git a/lib/modules/sideb/album_song_list/album_song_list_controller.dart b/lib/modules/sideb/album_song_list/album_song_list_controller.dart
index c9d6a94..c16b10a 100644
--- a/lib/modules/sideb/album_song_list/album_song_list_controller.dart
+++ b/lib/modules/sideb/album_song_list/album_song_list_controller.dart
@@ -1,12 +1,13 @@
-import 'dart:ffi';
-
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/api/music_api.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/models/browse_model.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/storage/collect_playlists_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/modules/sideb/collect_playlists/collect_playlists_controller.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
import 'package:tone_snap/routes/app_routes.dart';
@@ -103,6 +104,9 @@ class AlbumSongListController extends GetxController {
if (thumbnails != null && thumbnails.isNotEmpty) {
musicModel.coverUrl = thumbnails.last.url;
}
+ if (ObjUtil.isEmpty(musicModel.coverUrl)) {
+ musicModel.coverUrl = coverUrl.value;
+ }
var flexColumns = o.musicResponsiveListItemRenderer?.flexColumns;
if (flexColumns != null && flexColumns.isNotEmpty) {
@@ -142,21 +146,25 @@ class AlbumSongListController extends GetxController {
/// 点击播放全部歌曲
Future onTapPlayAll(bool isShuffle) async {
if (musicList.isNotEmpty) {
- int index = 0;
- if (isShuffle && musicList.length > 1) {
- final n = musicList.indexWhere((e) => e.videoId == musicPlayerController.getMusicModel()?.videoId);
- if (n != -1) {
- index = NumUtil.getRandomNumberExcludingCurrent(0, musicList.length, n);
- } else {
- index = NumUtil.getRandomNumber(0, musicList.length);
- }
- }
- for (var o in musicList) {
- if (ObjUtil.isEmpty(o.coverUrl)) {
- o.coverUrl = coverUrl.value;
- }
- }
- musicPlayerController.playMusic(musicList[index].videoId, playList: musicList);
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ int index = 0;
+ if (isShuffle && musicList.length > 1) {
+ final n = musicList.indexWhere((e) => e.videoId == musicPlayerController.getMusicModel()?.videoId);
+ if (n != -1) {
+ index = NumUtil.getRandomNumberExcludingCurrent(0, musicList.length, n);
+ } else {
+ index = NumUtil.getRandomNumber(0, musicList.length);
+ }
+ }
+ Get.toNamed(AppRoutes.playPage, arguments: {
+ 'videoId': musicList[index].videoId,
+ 'playList': musicList,
+ });
+ },
+ );
+ FirebaseAnalyticsManager.logSongClick('来自专辑/歌单');
}
}
@@ -186,9 +194,15 @@ class AlbumSongListController extends GetxController {
/// 点击列表
void onTapItem(MusicModel musicModel) {
- Get.toNamed(AppRoutes.playPage, arguments: {
- 'videoId': musicModel.videoId,
- 'playlistId': musicModel.playlistId,
- });
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ Get.toNamed(AppRoutes.playPage, arguments: {
+ 'videoId': musicModel.videoId,
+ 'playList': musicList,
+ });
+ },
+ );
+ FirebaseAnalyticsManager.logSongClick('来自专辑/歌单');
}
}
diff --git a/lib/modules/sideb/controllers/music_player_controller.dart b/lib/modules/sideb/controllers/music_player_controller.dart
index 8370b84..a8a6dbe 100644
--- a/lib/modules/sideb/controllers/music_player_controller.dart
+++ b/lib/modules/sideb/controllers/music_player_controller.dart
@@ -15,6 +15,7 @@ import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/player_model.dart';
import 'package:tone_snap/data/storage/music_box.dart';
import 'package:tone_snap/data/storage/offline_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/modules/sideb/music_bar/music_bar.dart';
import 'package:tone_snap/routes/app_routes.dart';
import 'package:tone_snap/utils/audio_util.dart';
@@ -67,6 +68,9 @@ class MusicPlayerController extends GetxController {
/// 当前播放的歌曲索引
var currentIndex = 0.obs;
+ /// 播放成功打点时的videoId,防止同一首歌播放状态监听重复打点
+ String? _logVideoId;
+
@override
void onInit() {
super.onInit();
@@ -100,15 +104,18 @@ class MusicPlayerController extends GetxController {
/// 播放歌曲
Future _startPlay() async {
if (playlist.isNotEmpty) {
+ FirebaseAnalyticsManager.logPlayerBpv(getMusicModel()?.videoId, getMusicModel()?.title, getMusicModel()?.subtitle);
resetPlaybackStatus();
Get.currentRoute == AppRoutes.playPage ? MusicBar().hide() : MusicBar().show();
try {
+ DateTime startLoadDateTime = DateTime.now();
var model = OfflineBox().getList().firstWhereOrNull((e) => e.videoId == getMusicModel()?.videoId);
if (model != null && ObjUtil.isNotEmpty(model.localPath)) {
// 有下载
LogUtil.d('读取下载路径=${model.localPath}');
if (!await File(model.localPath!).exists()) {
BaseEasyLoading.toast('file does not exist');
+ FirebaseAnalyticsManager.logPlayerBFailAction('本地文件不存在');
return;
}
await _player.setFilePath(model.localPath!);
@@ -136,9 +143,20 @@ class MusicPlayerController extends GetxController {
_cancelListening();
_addListening();
_player.play();
+
+ DateTime endLoadDateTime = DateTime.now();
+ // 获取毫秒差
+ int millisecondsDifference = endLoadDateTime.difference(startLoadDateTime).inMilliseconds;
+ FirebaseAnalyticsManager.logPlayerBDelayAction(
+ getMusicModel()?.videoId,
+ getMusicModel()?.title,
+ getMusicModel()?.subtitle,
+ '${millisecondsDifference}ms',
+ );
} catch (e) {
LogUtil.e('Play failed: $e');
BaseEasyLoading.toast('Play failed');
+ FirebaseAnalyticsManager.logPlayerBFailAction(e.toString());
}
}
}
@@ -200,6 +218,16 @@ class MusicPlayerController extends GetxController {
}
break;
}
+ if (isPlaying.value) {
+ if (_logVideoId != getMusicModel()?.videoId) {
+ _logVideoId = getMusicModel()?.videoId;
+ FirebaseAnalyticsManager.logPlayerBSuccessAction(
+ getMusicModel()?.videoId,
+ getMusicModel()?.title,
+ getMusicModel()?.subtitle,
+ );
+ }
+ }
});
}
diff --git a/lib/modules/sideb/custom_playlist/custom_playlist_controller.dart b/lib/modules/sideb/custom_playlist/custom_playlist_controller.dart
index c220ca2..472c1f3 100644
--- a/lib/modules/sideb/custom_playlist/custom_playlist_controller.dart
+++ b/lib/modules/sideb/custom_playlist/custom_playlist_controller.dart
@@ -1,9 +1,12 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/view_state_widget.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/playlist_model.dart';
import 'package:tone_snap/data/storage/playlists_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
import 'package:tone_snap/modules/sideb/more_playlist_bottom_sheet/more_playlist_bottom_sheet_view.dart';
import 'package:tone_snap/routes/app_routes.dart';
@@ -64,16 +67,25 @@ class CustomPlaylistController extends GetxController {
Future onTapPlayAll(bool isShuffle) async {
if (musicList.isNotEmpty) {
- int index = 0;
- if (isShuffle && musicList.length > 1) {
- final n = musicList.indexWhere((e) => e.videoId == musicPlayerController.getMusicModel()?.videoId);
- if (n != -1) {
- index = NumUtil.getRandomNumberExcludingCurrent(0, musicList.length, n);
- } else {
- index = NumUtil.getRandomNumber(0, musicList.length);
- }
- }
- musicPlayerController.playMusic(musicList[index].videoId, playList: musicList);
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ int index = 0;
+ if (isShuffle && musicList.length > 1) {
+ final n = musicList.indexWhere((e) => e.videoId == musicPlayerController.getMusicModel()?.videoId);
+ if (n != -1) {
+ index = NumUtil.getRandomNumberExcludingCurrent(0, musicList.length, n);
+ } else {
+ index = NumUtil.getRandomNumber(0, musicList.length);
+ }
+ }
+ Get.toNamed(AppRoutes.playPage, arguments: {
+ 'videoId': musicList[index].videoId,
+ 'playList': musicList,
+ });
+ },
+ );
+ FirebaseAnalyticsManager.logSongClick('来自自建歌单列表');
}
}
@@ -103,9 +115,15 @@ class CustomPlaylistController extends GetxController {
}
void onTapItem(MusicModel musicModel) {
- Get.toNamed(AppRoutes.playPage, arguments: {
- 'videoId': musicModel.videoId,
- 'playList': musicList.toList(),
- });
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ Get.toNamed(AppRoutes.playPage, arguments: {
+ 'videoId': musicModel.videoId,
+ 'playList': musicList,
+ });
+ },
+ );
+ FirebaseAnalyticsManager.logSongClick('来自自建歌单列表');
}
}
diff --git a/lib/modules/sideb/home/home_controller.dart b/lib/modules/sideb/home/home_controller.dart
index 7157a12..87b8650 100644
--- a/lib/modules/sideb/home/home_controller.dart
+++ b/lib/modules/sideb/home/home_controller.dart
@@ -1,13 +1,21 @@
import 'package:easy_refresh/easy_refresh.dart';
-import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/api/music_api.dart';
+import 'package:tone_snap/data/api/tikustok_api.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/enum/music_type.dart';
+import 'package:tone_snap/data/models/base_model.dart';
import 'package:tone_snap/data/models/browse_group_model.dart';
import 'package:tone_snap/data/models/browse_model.dart';
+import 'package:tone_snap/data/models/isocode_model.dart';
import 'package:tone_snap/data/models/music_model.dart';
+import 'package:tone_snap/data/network/base_error.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
+import 'package:tone_snap/global/app_config.dart';
import 'package:tone_snap/routes/app_routes.dart';
+import 'package:tone_snap/utils/obj_util.dart';
class HomeController extends GetxController {
static HomeController get to => Get.find();
@@ -42,13 +50,29 @@ class HomeController extends GetxController {
firstBrowse();
}
+ /// 获取所在区域
+ Future _getIsoCode() async {
+ BaseModel? model = await TikUsTokApi.getIsoCode();
+ if (model != null && model.success && model.data?.isoCode != null) {
+ AppConfig.isoCode = model.data!.isoCode!;
+ }
+ }
+
/// 首次请求
Future firstBrowse() async {
+ await _getIsoCode();
Map queryParameters = {
'prettyPrint': false,
'browseId': 'FEmusic_home'
};
- BrowseModel? browseModel = await MusicApi.browse(queryParameters: queryParameters, formJson: BrowseModel.fromMap);
+ BrowseModel? browseModel = await MusicApi.browse(
+ queryParameters: queryParameters,
+ formJson: BrowseModel.fromMap,
+ fail: (BaseError baseError) {
+ refreshController.finishRefresh(IndicatorResult.fail);
+ FirebaseAnalyticsManager.logHomeBModuleFailAction('${baseError.code},${baseError.message}');
+ },
+ );
if (browseModel != null) {
refreshController.finishRefresh();
@@ -65,8 +89,11 @@ class HomeController extends GetxController {
clickTrackingParams = continuations[0].nextContinuationData?.clickTrackingParams;
}
_reBrowse(continuation: continuation, clickTrackingParams: clickTrackingParams);
- } else {
- refreshController.finishRefresh(IndicatorResult.fail);
+ if (groupList.isNotEmpty) {
+ FirebaseAnalyticsManager.logHomeBModuleShowsuccesAction();
+ } else {
+ FirebaseAnalyticsManager.logHomeBModuleFailAction('无数据');
+ }
}
viewState.value = groupList.isNotEmpty ? ViewState.normal : ViewState.empty;
}
@@ -80,7 +107,11 @@ class HomeController extends GetxController {
'itct': clickTrackingParams,
'prettyPrint': false
};
- BrowseModel? browseModel = await MusicApi.browse(visitorData: visitorData, queryParameters: queryParameters, formJson: BrowseModel.fromMap);
+ BrowseModel? browseModel = await MusicApi.browse(
+ visitorData: visitorData,
+ queryParameters: queryParameters,
+ formJson: BrowseModel.fromMap,
+ );
if (browseModel != null) {
_extractAssemblyData(browseModel);
@@ -191,6 +222,7 @@ class HomeController extends GetxController {
}
void openSearch() {
+ FirebaseAnalyticsManager.logSearchFromAction('来自首页');
Get.toNamed(AppRoutes.searchResult);
}
@@ -198,11 +230,18 @@ class HomeController extends GetxController {
Get.toNamed(AppRoutes.setting);
}
- void openPlayPage(MusicModel musicModel) {
- Get.toNamed(AppRoutes.playPage, arguments: {
- 'videoId': musicModel.videoId,
- 'playlistId': musicModel.playlistId,
- });
+ void openPlayPage(BrowseGroupModel browseGroupModel, MusicModel musicModel) {
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ Get.toNamed(AppRoutes.playPage, arguments: {
+ 'videoId': musicModel.videoId,
+ 'playlistId': musicModel.playlistId,
+ });
+ },
+ );
+ FirebaseAnalyticsManager.logHomeBModuleClick(ObjUtil.getStr(browseGroupModel.groupTitle));
+ FirebaseAnalyticsManager.logSongClick('来自首页');
}
void openAlbumSong(BrowseGroupModel browseGroupModel, MusicModel musicModel) {
@@ -214,5 +253,6 @@ class HomeController extends GetxController {
'title': musicModel.title,
'subtitle': musicModel.subtitle,
});
+ FirebaseAnalyticsManager.logHomeBModuleClick(ObjUtil.getStr(browseGroupModel.groupTitle));
}
}
diff --git a/lib/modules/sideb/home/home_view.dart b/lib/modules/sideb/home/home_view.dart
index 80f5991..ff057c2 100644
--- a/lib/modules/sideb/home/home_view.dart
+++ b/lib/modules/sideb/home/home_view.dart
@@ -154,7 +154,7 @@ class HomeView extends GetView {
final musicModel = browseGroupModel.browseList![index];
return BrowseItemAtv(
musicModel: musicModel,
- onTap: () => controller.openPlayPage(musicModel),
+ onTap: () => controller.openPlayPage(browseGroupModel, musicModel),
);
},
),
diff --git a/lib/modules/sideb/initial/initial_controller.dart b/lib/modules/sideb/initial/initial_controller.dart
index 77b81e3..87c9e49 100644
--- a/lib/modules/sideb/initial/initial_controller.dart
+++ b/lib/modules/sideb/initial/initial_controller.dart
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/generated/assets.dart';
+import 'package:tone_snap/global/app_lifecycle_reactor.dart';
import 'package:tone_snap/global/app_tracking_transparency_manager.dart';
import 'package:tone_snap/modules/sideb/home/home_view.dart';
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_controller.dart';
@@ -17,11 +19,19 @@ class InitialController extends GetxController {
];
var currentIndex = 0.obs;
+ late AppLifecycleReactor _appLifecycleReactor;
+
@override
void onInit() {
super.onInit();
AppTrackingTransparencyManager().requestATT();
+
+ _appLifecycleReactor = AppLifecycleReactor();
+ _appLifecycleReactor.listenToAppStateChanges();
+
pageController = PageController(initialPage: currentIndex.value);
+
+ FirebaseAnalyticsManager.logHomeBPV();
}
@override
@@ -33,12 +43,18 @@ class InitialController extends GetxController {
Future onBottomAppBarItemChanged(int index) async {
currentIndex.value = index;
pageController.jumpToPage(index);
-
+ if (index == 0) {
+ FirebaseAnalyticsManager.logHomeBPV();
+ }
+ if (index == 1) {
+ FirebaseAnalyticsManager.logSearchPV();
+ }
if (index == 2) {
if (Get.isRegistered()) {
PersonalMusicLibraryController.to.refreshLoveSongs();
PersonalMusicLibraryController.to.refreshOffline();
}
+ FirebaseAnalyticsManager.logMeBPV();
}
}
}
diff --git a/lib/modules/sideb/love_songs/love_songs_controller.dart b/lib/modules/sideb/love_songs/love_songs_controller.dart
index 1a8b65d..78e2d82 100644
--- a/lib/modules/sideb/love_songs/love_songs_controller.dart
+++ b/lib/modules/sideb/love_songs/love_songs_controller.dart
@@ -1,7 +1,10 @@
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/view_state_widget.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/storage/love_songs_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/routes/app_routes.dart';
class LoveSongsController extends GetxController {
@@ -21,9 +24,15 @@ class LoveSongsController extends GetxController {
}
void onTapItem(MusicModel musicModel) {
- Get.toNamed(AppRoutes.playPage, arguments: {
- 'videoId': musicModel.videoId,
- 'playList': LoveSongsBox().getReversedList(),
- });
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ Get.toNamed(AppRoutes.playPage, arguments: {
+ 'videoId': musicModel.videoId,
+ 'playList': loveList,
+ });
+ },
+ );
+ FirebaseAnalyticsManager.logSongClick('来自收藏列表');
}
}
diff --git a/lib/modules/sideb/more_bottom_sheet/more_bottom_sheet_controller.dart b/lib/modules/sideb/more_bottom_sheet/more_bottom_sheet_controller.dart
index 4426e78..5ad8c5a 100644
--- a/lib/modules/sideb/more_bottom_sheet/more_bottom_sheet_controller.dart
+++ b/lib/modules/sideb/more_bottom_sheet/more_bottom_sheet_controller.dart
@@ -6,6 +6,7 @@ import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/storage/love_songs_box.dart';
import 'package:tone_snap/data/storage/offline_box.dart';
import 'package:tone_snap/data/storage/playlists_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/global/download_manager.dart';
import 'package:tone_snap/modules/sideb/add_to_playlist_bottom_sheet/add_to_playlist_bottom_sheet_view.dart';
import 'package:tone_snap/modules/sideb/custom_playlist/custom_playlist_controller.dart';
@@ -33,9 +34,19 @@ class MoreBottomSheetController extends GetxController {
void onTapLove() async {
if (ObjUtil.isEmpty(musicModel.videoId)) return;
if (isLove.value) {
+ FirebaseAnalyticsManager.logPlayerBUnloveClick(
+ musicModel.videoId,
+ musicModel.title,
+ musicModel.subtitle,
+ );
await LoveSongsBox().delete(musicModel.videoId!);
BaseEasyLoading.toast('Removed');
} else {
+ FirebaseAnalyticsManager.logPlayerBLoveClick(
+ musicModel.videoId,
+ musicModel.title,
+ musicModel.subtitle,
+ );
await LoveSongsBox().add(musicModel.copyWith());
BaseEasyLoading.toast('Collected');
}
diff --git a/lib/modules/sideb/music_bar/music_bar_controller.dart b/lib/modules/sideb/music_bar/music_bar_controller.dart
index 517062b..06bbdeb 100644
--- a/lib/modules/sideb/music_bar/music_bar_controller.dart
+++ b/lib/modules/sideb/music_bar/music_bar_controller.dart
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/routes/app_routes.dart';
class MusicBarController extends GetxController with GetSingleTickerProviderStateMixin {
@@ -28,8 +30,13 @@ class MusicBarController extends GetxController with GetSingleTickerProviderStat
/// 打开播放页面
void openPlayPage() {
- Get.toNamed(AppRoutes.playPage, arguments: {
- 'isFormMusicBar': true,
- });
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ Get.toNamed(AppRoutes.playPage, arguments: {
+ 'isFormMusicBar': true,
+ });
+ },
+ );
}
}
diff --git a/lib/modules/sideb/offline/offline_controller.dart b/lib/modules/sideb/offline/offline_controller.dart
index d263f68..05fa962 100644
--- a/lib/modules/sideb/offline/offline_controller.dart
+++ b/lib/modules/sideb/offline/offline_controller.dart
@@ -1,7 +1,10 @@
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/view_state_widget.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/storage/offline_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/routes/app_routes.dart';
class OfflineController extends GetxController {
@@ -21,9 +24,15 @@ class OfflineController extends GetxController {
}
void onTapItem(MusicModel model) {
- Get.toNamed(AppRoutes.playPage, arguments: {
- 'videoId': model.videoId,
- 'playList': OfflineBox().getReversedList(),
- });
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ Get.toNamed(AppRoutes.playPage, arguments: {
+ 'videoId': model.videoId,
+ 'playList': offlineList,
+ });
+ },
+ );
+ FirebaseAnalyticsManager.logSongClick('来自离线列表');
}
}
diff --git a/lib/modules/sideb/personal_music_library/personal_music_library_controller.dart b/lib/modules/sideb/personal_music_library/personal_music_library_controller.dart
index fe9c389..897e1ff 100644
--- a/lib/modules/sideb/personal_music_library/personal_music_library_controller.dart
+++ b/lib/modules/sideb/personal_music_library/personal_music_library_controller.dart
@@ -1,11 +1,16 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/storage/love_songs_box.dart';
import 'package:tone_snap/data/storage/offline_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/modules/sideb/collect_playlists/collect_playlists_view.dart';
import 'package:tone_snap/modules/sideb/playlists/playlists_view.dart';
import 'package:tone_snap/routes/app_routes.dart';
+import '../../../ads/library_native_ad_manager.dart';
+
class PersonalMusicLibraryController extends GetxController with GetSingleTickerProviderStateMixin {
static PersonalMusicLibraryController get to => Get.find();
var loveSongsCoverUrl = ''.obs;
@@ -32,19 +37,37 @@ class PersonalMusicLibraryController extends GetxController with GetSingleTicker
@override
void onClose() {
tabController.dispose();
+ LibraryNativeAdManager().nativeAd?.dispose();
super.onClose();
}
void onTapModule(int index) {
+ String routeName;
if (index == 0) {
- Get.toNamed(AppRoutes.loveSongs);
- }
- if (index == 1) {
- Get.toNamed(AppRoutes.artists);
- }
- if (index == 2) {
- Get.toNamed(AppRoutes.offline);
+ routeName = AppRoutes.loveSongs;
+ FirebaseAnalyticsManager.logLibraryClick(
+ '曲库页面',
+ '收藏歌曲',
+ );
+ } else if (index == 1) {
+ routeName = AppRoutes.artists;
+ FirebaseAnalyticsManager.logLibraryClick(
+ '曲库页面',
+ '收藏艺术家',
+ );
+ } else {
+ routeName = AppRoutes.offline;
+ FirebaseAnalyticsManager.logLibraryClick(
+ '曲库页面',
+ '离线列表',
+ );
}
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.list.name,
+ onTap: () {
+ Get.toNamed(routeName);
+ },
+ );
}
void refreshLoveSongs() {
diff --git a/lib/modules/sideb/personal_music_library/personal_music_library_view.dart b/lib/modules/sideb/personal_music_library/personal_music_library_view.dart
index 4663608..53339c5 100644
--- a/lib/modules/sideb/personal_music_library/personal_music_library_view.dart
+++ b/lib/modules/sideb/personal_music_library/personal_music_library_view.dart
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
+import 'package:google_mobile_ads/google_mobile_ads.dart';
+import 'package:tone_snap/ads/library_native_ad_manager.dart';
import 'package:tone_snap/components/keep_alive_wrapper.dart';
import 'package:tone_snap/components/my_custom_indicator.dart';
import 'package:tone_snap/components/network_image_widget.dart';
@@ -20,12 +22,14 @@ class PersonalMusicLibraryView extends GetView {
return Stack(
children: [
_buildPageBg(),
- SafeArea(
+ Padding(
+ padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitle(),
_buildModule(),
+ // _buildAdWidget(),
_buildTabBar(),
_buildTabBarView(),
],
@@ -57,9 +61,25 @@ class PersonalMusicLibraryView extends GetView {
);
}
+ Widget _buildAdWidget() {
+ return Obx(() {
+ if (LibraryNativeAdManager().nativeAdIsLoaded.value
+ && LibraryNativeAdManager().nativeAd != null) {
+ return Container(
+ width: 1.sw,
+ height: 320.h,
+ padding: EdgeInsets.symmetric(horizontal: 16.w),
+ child: AdWidget(ad: LibraryNativeAdManager().nativeAd!),
+ );
+ } else {
+ return Container();
+ }
+ });
+ }
+
Widget _buildModule() {
return Padding(
- padding: EdgeInsets.symmetric(vertical: 28.h, horizontal: 16.w),
+ padding: EdgeInsets.symmetric(vertical: 10.h, horizontal: 16.w),
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
diff --git a/lib/modules/sideb/play_page/play_page_controller.dart b/lib/modules/sideb/play_page/play_page_controller.dart
index 64d4dc8..f48fc62 100644
--- a/lib/modules/sideb/play_page/play_page_controller.dart
+++ b/lib/modules/sideb/play_page/play_page_controller.dart
@@ -1,12 +1,15 @@
import 'package:background_downloader/background_downloader.dart';
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/base_easyloading.dart';
import 'package:tone_snap/components/dialog/remind_dialog.dart';
import 'package:tone_snap/data/api/music_api.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/next_model.dart';
import 'package:tone_snap/data/storage/love_songs_box.dart';
import 'package:tone_snap/data/storage/offline_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/global/download_manager.dart';
import 'package:tone_snap/modules/sideb/controllers/music_player_controller.dart';
import 'package:tone_snap/modules/sideb/personal_music_library/personal_music_library_controller.dart';
@@ -30,9 +33,13 @@ class PlayPageController extends GetxController {
musicPlayerController.playlist.clear();
musicPlayerController.resetPlaybackStatus();
playList = await _next(videoId, playlistId);
+ if (playList.isNotEmpty) {
+ FirebaseAnalyticsManager.logPlayerBList();
+ }
}
musicPlayerController.playMusic(videoId, playList: playList);
}
+ FirebaseAnalyticsManager.logPlayerBImp();
}
/// 获取播放列表
@@ -91,9 +98,19 @@ class PlayPageController extends GetxController {
if (ObjUtil.isEmpty(musicPlayerController.getMusicModel()?.videoId)) return;
final isLove = LoveSongsBox().checkLove(musicPlayerController.getMusicModel()!.videoId);
if (isLove) {
+ FirebaseAnalyticsManager.logPlayerBUnloveClick(
+ musicPlayerController.getMusicModel()?.videoId,
+ musicPlayerController.getMusicModel()?.title,
+ musicPlayerController.getMusicModel()?.subtitle,
+ );
await LoveSongsBox().delete(musicPlayerController.getMusicModel()!.videoId!);
BaseEasyLoading.toast('Removed');
} else {
+ FirebaseAnalyticsManager.logPlayerBLoveClick(
+ musicPlayerController.getMusicModel()?.videoId,
+ musicPlayerController.getMusicModel()?.title,
+ musicPlayerController.getMusicModel()?.subtitle,
+ );
await LoveSongsBox().add(musicPlayerController.getMusicModel()!.copyWith());
BaseEasyLoading.toast('Collected');
}
@@ -103,6 +120,7 @@ class PlayPageController extends GetxController {
update([loveStateId]);
}
+ /// 下载
void onTapDownload() {
if (OfflineBox().checkDownloaded(musicPlayerController.getMusicModel()?.videoId)) {
Get.dialog(
@@ -141,4 +159,24 @@ class PlayPageController extends GetxController {
musicPlayerController.currentIndex.value--;
}
}
+
+ /// 点击上一首
+ void onTapPreviousTrack() {
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.playCut.name,
+ onTap: () {
+ musicPlayerController.previousTrack();
+ },
+ );
+ }
+
+ /// 点击下一首
+ void onTapNextTrack() {
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.playCut.name,
+ onTap: () {
+ musicPlayerController.nextTrack();
+ },
+ );
+ }
}
\ No newline at end of file
diff --git a/lib/modules/sideb/play_page/play_page_view.dart b/lib/modules/sideb/play_page/play_page_view.dart
index e8ddbc1..78c1c11 100644
--- a/lib/modules/sideb/play_page/play_page_view.dart
+++ b/lib/modules/sideb/play_page/play_page_view.dart
@@ -355,7 +355,7 @@ class PlayPageView extends StatelessWidget {
child: Material(
color: Colors.transparent,
child: InkWell(
- onTap: musicPlayerController.previousTrack,
+ onTap: controller.onTapPreviousTrack,
child: Padding(
padding: const EdgeInsets.all(10).w,
child: Image.asset(
@@ -411,7 +411,7 @@ class PlayPageView extends StatelessWidget {
child: Material(
color: Colors.transparent,
child: InkWell(
- onTap: musicPlayerController.nextTrack,
+ onTap: controller.onTapNextTrack,
child: Padding(
padding: const EdgeInsets.all(10).w,
child: Image.asset(
diff --git a/lib/modules/sideb/playlists/playlists_controller.dart b/lib/modules/sideb/playlists/playlists_controller.dart
index d152c0b..d3feda7 100644
--- a/lib/modules/sideb/playlists/playlists_controller.dart
+++ b/lib/modules/sideb/playlists/playlists_controller.dart
@@ -4,6 +4,7 @@ import 'package:tone_snap/components/dialog/add_playlist_dialog.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/models/playlist_model.dart';
import 'package:tone_snap/data/storage/playlists_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
class PlaylistsController extends GetxController {
static PlaylistsController get to => Get.find();
@@ -24,7 +25,7 @@ class PlaylistsController extends GetxController {
}
void getList() {
- playLists.value = PlaylistsBox().getReversedList();
+ playLists.value = PlaylistsBox().getList();
viewState.value = playLists.isNotEmpty ? ViewState.normal : ViewState.empty;
}
@@ -35,6 +36,7 @@ class PlaylistsController extends GetxController {
onTap: (value) async {
await PlaylistsBox().add(value);
getList();
+ FirebaseAnalyticsManager.logCreateList();
},
),
);
diff --git a/lib/modules/sideb/search_music/search_music_controller.dart b/lib/modules/sideb/search_music/search_music_controller.dart
index 4dc90d1..c7bf937 100644
--- a/lib/modules/sideb/search_music/search_music_controller.dart
+++ b/lib/modules/sideb/search_music/search_music_controller.dart
@@ -1,5 +1,6 @@
import 'package:get/get.dart';
import 'package:tone_snap/data/storage/music_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/routes/app_routes.dart';
class SearchMusicController extends GetxController {
@@ -27,6 +28,7 @@ class SearchMusicController extends GetxController {
}
void openSearch() {
+ FirebaseAnalyticsManager.logSearchFromAction('来自搜索页面');
Get.toNamed(AppRoutes.searchResult);
}
}
diff --git a/lib/modules/sideb/search_result/search_result_controller.dart b/lib/modules/sideb/search_result/search_result_controller.dart
index 5196583..75bf2d2 100644
--- a/lib/modules/sideb/search_result/search_result_controller.dart
+++ b/lib/modules/sideb/search_result/search_result_controller.dart
@@ -1,14 +1,18 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/api/music_api.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/enum/music_type.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/search_result_model.dart';
import 'package:tone_snap/data/models/search_result_tabbar_model.dart';
import 'package:tone_snap/data/models/search_suggestions_model.dart';
+import 'package:tone_snap/data/network/base_error.dart';
import 'package:tone_snap/data/storage/music_box.dart';
+import 'package:tone_snap/firebase/firebase_analytics_manager.dart';
import 'package:tone_snap/modules/sideb/search_music/search_music_controller.dart';
import 'package:tone_snap/modules/sideb/search_result_child/search_result_child_controller.dart';
import 'package:tone_snap/modules/sideb/search_result_child/search_result_child_view.dart';
@@ -83,6 +87,9 @@ class SearchResultController extends GetxController with GetTickerProviderStateM
}
}
searchSuggestionsViewState.value = ViewState.normal;
+ if (searchSuggestionsList.isNotEmpty) {
+ FirebaseAnalyticsManager.logSearchSugShow();
+ }
}
void cleanTextEditingController() {
@@ -114,6 +121,8 @@ class SearchResultController extends GetxController with GetTickerProviderStateM
if (ObjUtil.isEmpty(textEditingController.text)) {
return;
}
+ FirebaseAnalyticsManager.logSearchSugClick(value);
+ await _cleanTab();
if (isSearchSuggestionsItem) {
textEditingController.text = value;
}
@@ -121,11 +130,19 @@ class SearchResultController extends GetxController with GetTickerProviderStateM
if (Get.isRegistered()) {
SearchMusicController.to.getList();
}
- await _cleanTab();
searchResultViewState.value = ViewState.loading;
await _searchPreviewResult(value);
- searchResultViewState.value = tabs.length > 1 ? ViewState.normal : ViewState.empty;
- tabController.value = TabController(length: tabs.length, vsync: this);
+ if (tabs.isNotEmpty) {
+ FirebaseAnalyticsManager.logSearchResultPV();
+ FirebaseAnalyticsManager.logSearchResultsuccessAction();
+ }
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.search.name,
+ onTap: () {
+ searchResultViewState.value = tabs.isNotEmpty ? ViewState.normal : ViewState.empty;
+ tabController.value = TabController(length: tabs.length, vsync: this);
+ },
+ );
}
Future _searchPreviewResult(String keyword) async {
@@ -133,7 +150,13 @@ class SearchResultController extends GetxController with GetTickerProviderStateM
'prettyPrint': false,
'query': keyword
};
- SearchResultModel? searchResultModel = await MusicApi.search(queryParameters: queryParameters, formJson: SearchResultModel.fromMap);
+ SearchResultModel? searchResultModel = await MusicApi.search(
+ queryParameters: queryParameters,
+ formJson: SearchResultModel.fromMap,
+ fail: (BaseError baseError) {
+ FirebaseAnalyticsManager.logSearchResultfailAction('${baseError.code},${baseError.message}');
+ },
+ );
if (searchResultModel != null) {
var tabs = searchResultModel.contents?.tabbedSearchResultsRenderer?.tabs;
if (tabs != null && tabs.isNotEmpty) {
@@ -266,6 +289,9 @@ class SearchResultController extends GetxController with GetTickerProviderStateM
}
}
}
+ if (this.tabs.isEmpty) {
+ FirebaseAnalyticsManager.logSearchResultfailAction('无数据');
+ }
}
}
}
diff --git a/lib/modules/sideb/search_result_child/search_result_child_controller.dart b/lib/modules/sideb/search_result_child/search_result_child_controller.dart
index 300af09..9f9d933 100644
--- a/lib/modules/sideb/search_result_child/search_result_child_controller.dart
+++ b/lib/modules/sideb/search_result_child/search_result_child_controller.dart
@@ -1,8 +1,10 @@
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
import 'package:tone_snap/components/view_state_widget.dart';
import 'package:tone_snap/data/api/music_api.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/enum/music_type.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/data/models/search_result_model.dart';
@@ -183,10 +185,15 @@ class SearchResultChildController extends GetxController {
void onTapItem(MusicModel musicModel) {
if (musicModel.musicType == MusicType.musicVideoTypeAtv.name) {
- Get.toNamed(AppRoutes.playPage, arguments: {
- 'videoId': musicModel.videoId,
- 'playlistId': musicModel.playlistId,
- });
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ Get.toNamed(AppRoutes.playPage, arguments: {
+ 'videoId': musicModel.videoId,
+ 'playlistId': musicModel.playlistId,
+ });
+ },
+ );
} else if (musicModel.musicType == MusicType.musicPageTypeAlbum.name
|| musicModel.musicType == MusicType.musicPageTypePlaylist.name) {
Get.toNamed(AppRoutes.albumSongList, arguments: {
diff --git a/lib/modules/sideb/search_result_child_optimum/search_result_child_optimum_controller.dart b/lib/modules/sideb/search_result_child_optimum/search_result_child_optimum_controller.dart
index d80c53c..91133e2 100644
--- a/lib/modules/sideb/search_result_child_optimum/search_result_child_optimum_controller.dart
+++ b/lib/modules/sideb/search_result_child_optimum/search_result_child_optimum_controller.dart
@@ -1,4 +1,6 @@
import 'package:get/get.dart';
+import 'package:tone_snap/ads/interstitial_ad_manager.dart';
+import 'package:tone_snap/data/enum/ad_scenes.dart';
import 'package:tone_snap/data/enum/music_type.dart';
import 'package:tone_snap/data/models/music_model.dart';
import 'package:tone_snap/routes/app_routes.dart';
@@ -6,10 +8,15 @@ import 'package:tone_snap/routes/app_routes.dart';
class SearchResultChildOptimumController extends GetxController {
void onTapItem(MusicModel musicModel) {
if (musicModel.musicType == MusicType.musicVideoTypeAtv.name) {
- Get.toNamed(AppRoutes.playPage, arguments: {
- 'videoId': musicModel.videoId,
- 'playlistId': musicModel.playlistId,
- });
+ InterstitialAdManager().showAdIfAvailable(
+ AdScenes.play.name,
+ onTap: () {
+ Get.toNamed(AppRoutes.playPage, arguments: {
+ 'videoId': musicModel.videoId,
+ 'playlistId': musicModel.playlistId,
+ });
+ },
+ );
} else if (musicModel.musicType == MusicType.musicPageTypeAlbum.name
|| musicModel.musicType == MusicType.musicPageTypePlaylist.name) {
Get.toNamed(AppRoutes.albumSongList, arguments: {
diff --git a/pubspec.yaml b/pubspec.yaml
index 06e587b..ae65efd 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
-version: 1.0.4+14
+version: 1.0.5+15
environment:
sdk: '>=3.4.1 <4.0.0'
@@ -100,7 +100,7 @@ dependencies:
google_mobile_ads: ^5.1.0
# Firebase
- firebase_core: ^3.2.0
+ firebase_core: ^3.3.0
firebase_analytics: ^11.2.0
firebase_crashlytics: ^4.0.3
firebase_remote_config: ^5.0.3