Просмотр исходного кода

add:添加iOS推送机制。

zhoukun 5 месяцев назад
Родитель
Сommit
57781949cd

+ 130 - 10
ios/Runner/AppDelegate.swift

@@ -1,17 +1,137 @@
 import Flutter
 import UIKit
 import BackgroundTasks
+import UserNotifications
 
 @main
 @objc class AppDelegate: FlutterAppDelegate {
-  override func application(
-    _ application: UIApplication,
-    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
-  ) -> Bool {
-    GeneratedPluginRegistrant.register(with: self)
-    BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.shishi.dingwei.refresh", using: nil) { task in
-      // 处理后台任务
-    }
-    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
-  }
+    private var pushChannel: FlutterMethodChannel?
+    private var notificationDelegate: UNUserNotificationCenterDelegate?
+    
+    
+    override func application(
+        _ application: UIApplication,
+        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+    ) -> Bool {
+        GeneratedPluginRegistrant.register(with: self)
+        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.shishi.dingwei.refresh", using: nil) { task in
+            // 处理后台任务
+        }
+        ///设置推送
+        setupPushNotifications();
+        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+    }
+    
+    /// 设置推送通知
+    func setupPushNotifications() {
+        let controller = window?.rootViewController as! FlutterViewController
+        
+        // 创建推送通道
+        pushChannel = FlutterMethodChannel(
+            name: "com.example.app/push_notification",
+            binaryMessenger: controller.binaryMessenger
+        )
+        
+        // 设置方法调用处理
+        pushChannel?.setMethodCallHandler { [weak self] call, result in
+            switch call.method {
+            case "getDeviceToken":
+                self?.getDeviceToken(result: result)
+            case "requestPermission":
+                self?.requestNotificationPermission(result: result)
+            default:
+                result(FlutterMethodNotImplemented)
+            }
+        }
+        
+        // 创建单独的通知代理
+        let delegate = NotificationDelegate(pushChannel: pushChannel!)
+        UNUserNotificationCenter.current().delegate = delegate
+        self.notificationDelegate = delegate
+        
+        // 请求推送权限
+        UNUserNotificationCenter.current().requestAuthorization(
+            options: [.alert, .sound, .badge]
+        ) { granted, error in
+            if granted {
+                DispatchQueue.main.async {
+                    UIApplication.shared.registerForRemoteNotifications()
+                }
+            } else if let error = error {
+                print("Error: \(error.localizedDescription)")
+            }
+        }
+    }
+    
+    
+    /// 获取设备令牌
+    private func getDeviceToken(result: @escaping FlutterResult) {
+        if let token = UserDefaults.standard.string(forKey: "deviceToken") {
+            result(token)
+        } else {
+            result(nil)
+        }
+    }
+    
+    /// 请求推送权限
+    private func requestNotificationPermission(result: @escaping FlutterResult) {
+        UNUserNotificationCenter.current().requestAuthorization(
+            options: [.alert, .sound, .badge]
+        ) { granted, error in
+            DispatchQueue.main.async {
+                if let error = error {
+                    result(FlutterError(code: "notification_error", message: error.localizedDescription, details: nil))
+                } else {
+                    result(granted)
+                }
+            }
+        }
+    }
+    
+    /// 处理接收到的推送令牌
+    override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
+        let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
+        UserDefaults.standard.set(token, forKey: "deviceToken")
+        
+        // 将令牌发送到Flutter
+        pushChannel?.invokeMethod("onTokenReceived", arguments: token)
+        print("APNs token: \(token)")
+    }
+    
+    /// 处理推送注册失败
+    override func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
+        print("Failed to register for remote notifications: \(error.localizedDescription)")
+        pushChannel?.invokeMethod("onTokenError", arguments: error.localizedDescription)
+    }
+}
+
+// 单独的通知代理类
+class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
+    private let pushChannel: FlutterMethodChannel
+    
+    init(pushChannel: FlutterMethodChannel) {
+        self.pushChannel = pushChannel
+        super.init()
+    }
+    
+    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+        let userInfo = notification.request.content.userInfo
+        print("Foreground notification received: \(userInfo)")
+        
+        // 将通知内容发送到Flutter
+        pushChannel.invokeMethod("onNotificationReceived", arguments: userInfo)
+        
+        // 显示通知
+        completionHandler([.alert, .sound, .badge])
+    }
+    
+    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+        let userInfo = response.notification.request.content.userInfo
+        print("Notification tapped: \(userInfo)")
+        
+        // 将点击事件发送到Flutter
+        pushChannel.invokeMethod("onNotificationTapped", arguments: userInfo)
+        
+        completionHandler()
+    }
 }

+ 2 - 0
ios/Runner/Info.plist

@@ -57,6 +57,8 @@
 	<string>通过位置访问,您可以与您信任的好友共享准确的位置数据</string>
 	<key>NSUserTrackingUsageDescription</key>
 	<string>为给您更精准的提供个性化内容,请您允许我们获取广告信息</string>
+    <key>NSUserNotificationUsageDescription</key>
+    <string>通知可能包括提醒,声音和图标标记。可在“设置”中配置相关功能</string>
 	<key>UIApplicationSupportsIndirectInputEvents</key>
 	<true/>
 	<key>UIBackgroundModes</key>

+ 2 - 0
ios/Runner/Runner.entitlements

@@ -2,6 +2,8 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
+    <key>aps-environment</key>
+    <string>development</string> <!-- 开发环境使用development,生产环境使用production -->
 	<key>com.apple.developer.associated-domains</key>
 	<array>
 		<string>applinks:help.wechat.com</string>

+ 6 - 0
lib/data/api/atmob_api.dart

@@ -11,6 +11,7 @@ import 'package:location/data/api/request/friends_operation_request.dart';
 import 'package:location/data/api/request/login_request.dart';
 import 'package:location/data/api/request/member_list_request.dart';
 import 'package:location/data/api/request/message_request.dart';
+import 'package:location/data/api/request/notification_report_request.dart';
 import 'package:location/data/api/request/operation_friend_request.dart';
 import 'package:location/data/api/request/order_status_request.dart';
 import 'package:location/data/api/request/query_track_request.dart';
@@ -215,4 +216,9 @@ abstract class AtmobApi {
   @POST("/s/v1/location/track/daily/query")
   Future<BaseResponse<TrackDailyResponse>> trackDailyQuery(
       @Body() QueryTrackRequest request, @DioOptions() RequestOptions options);
+
+  //上报推送信息-中台
+  @POST("/central/notification/v1/notification/report")
+  Future<BaseResponse> notificationReport(
+      @Body() NotificationReportRequest request);
 }

+ 38 - 0
lib/data/api/atmob_api.g.dart

@@ -1624,6 +1624,44 @@ class _AtmobApi implements AtmobApi {
     return _value;
   }
 
+  @override
+  Future<BaseResponse<dynamic>> notificationReport(
+      NotificationReportRequest request) async {
+    final _extra = <String, dynamic>{};
+    final queryParameters = <String, dynamic>{};
+    final _headers = <String, dynamic>{};
+    final _data = <String, dynamic>{};
+    _data.addAll(request.toJson());
+    final _options = _setStreamType<BaseResponse<dynamic>>(Options(
+      method: 'POST',
+      headers: _headers,
+      extra: _extra,
+    )
+        .compose(
+          _dio.options,
+          '/central/notification/v1/notification/report',
+          queryParameters: queryParameters,
+          data: _data,
+        )
+        .copyWith(
+            baseUrl: _combineBaseUrls(
+          _dio.options.baseUrl,
+          baseUrl,
+        )));
+    final _result = await _dio.fetch<Map<String, dynamic>>(_options);
+    late BaseResponse<dynamic> _value;
+    try {
+      _value = BaseResponse<dynamic>.fromJson(
+        _result.data!,
+        (json) => json as dynamic,
+      );
+    } on Object catch (e, s) {
+      errorLogger?.logError(e, s, _options);
+      rethrow;
+    }
+    return _value;
+  }
+
   RequestOptions newRequestOptions(Object? options) {
     if (options is RequestOptions) {
       return options as RequestOptions;

+ 23 - 0
lib/data/api/request/notification_report_request.dart

@@ -0,0 +1,23 @@
+
+
+
+
+import 'package:json_annotation/json_annotation.dart';
+
+import '../../../base/app_base_request.dart';
+
+part 'notification_report_request.g.dart';
+
+@JsonSerializable()
+class NotificationReportRequest extends AppBaseRequest {
+  @JsonKey(name: 'deviceToken')
+  String deviceToken;
+
+  NotificationReportRequest({required this.deviceToken});
+
+  factory NotificationReportRequest.fromJson(Map<String, dynamic> json) =>
+      _$NotificationReportRequestFromJson(json);
+
+  @override
+  Map<String, dynamic> toJson() => _$NotificationReportRequestToJson(this);
+}

+ 71 - 0
lib/data/api/request/notification_report_request.g.dart

@@ -0,0 +1,71 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'notification_report_request.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+NotificationReportRequest _$NotificationReportRequestFromJson(
+        Map<String, dynamic> json) =>
+    NotificationReportRequest(
+      deviceToken: json['deviceToken'] as String,
+    )
+      ..appPlatform = (json['appPlatform'] as num).toInt()
+      ..os = json['os'] as String
+      ..osVersion = json['osVersion'] as String
+      ..packageName = json['packageName'] as String?
+      ..appVersionName = json['appVersionName'] as String?
+      ..appVersionCode = (json['appVersionCode'] as num?)?.toInt()
+      ..channelName = json['channelName'] as String?
+      ..appId = (json['appId'] as num?)?.toInt()
+      ..tgPlatform = (json['tgPlatform'] as num?)?.toInt()
+      ..oaid = json['oaid'] as String?
+      ..aaid = json['aaid'] as String?
+      ..androidId = json['androidId'] as String?
+      ..imei = json['imei'] as String?
+      ..simImei0 = json['simImei0'] as String?
+      ..simImei1 = json['simImei1'] as String?
+      ..mac = json['mac'] as String?
+      ..idfa = json['idfa'] as String?
+      ..idfv = json['idfv'] as String?
+      ..machineId = json['machineId'] as String?
+      ..brand = json['brand'] as String?
+      ..model = json['model'] as String?
+      ..wifiName = json['wifiName'] as String?
+      ..region = json['region'] as String?
+      ..locLng = (json['locLng'] as num?)?.toDouble()
+      ..locLat = (json['locLat'] as num?)?.toDouble()
+      ..authToken = json['authToken'] as String?;
+
+Map<String, dynamic> _$NotificationReportRequestToJson(
+        NotificationReportRequest instance) =>
+    <String, dynamic>{
+      'appPlatform': instance.appPlatform,
+      'os': instance.os,
+      'osVersion': instance.osVersion,
+      'packageName': instance.packageName,
+      'appVersionName': instance.appVersionName,
+      'appVersionCode': instance.appVersionCode,
+      'channelName': instance.channelName,
+      'appId': instance.appId,
+      'tgPlatform': instance.tgPlatform,
+      'oaid': instance.oaid,
+      'aaid': instance.aaid,
+      'androidId': instance.androidId,
+      'imei': instance.imei,
+      'simImei0': instance.simImei0,
+      'simImei1': instance.simImei1,
+      'mac': instance.mac,
+      'idfa': instance.idfa,
+      'idfv': instance.idfv,
+      'machineId': instance.machineId,
+      'brand': instance.brand,
+      'model': instance.model,
+      'wifiName': instance.wifiName,
+      'region': instance.region,
+      'locLng': instance.locLng,
+      'locLat': instance.locLat,
+      'authToken': instance.authToken,
+      'deviceToken': instance.deviceToken,
+    };

+ 16 - 0
lib/data/repositories/account_repository.dart

@@ -15,6 +15,7 @@ import 'package:location/data/repositories/friends_repository.dart';
 import 'package:location/data/repositories/message_repository.dart';
 import 'package:location/data/repositories/urgent_contact_repository.dart';
 import 'package:location/di/get_it.dart';
+import 'package:location/push_notification/ios_push_notification_service.dart';
 import 'package:location/resource/string.gen.dart';
 import 'package:location/socket/atmob_location_client.dart';
 import 'package:location/utils/async_util.dart';
@@ -23,6 +24,7 @@ import 'package:location/utils/http_handler.dart';
 import 'package:location/utils/mmkv_util.dart';
 
 import '../../sdk/map/map_helper.dart';
+import '../api/request/notification_report_request.dart';
 import '../api/request/user_avatar_update_request.dart';
 import '../api/response/login_response.dart';
 import '../api/response/member_status_response.dart';
@@ -131,6 +133,8 @@ class AccountRepository {
     messageRepository.refreshFriendWaitingCount();
     messageRepository.refreshUnreadMessage();
     urgentContactRepository.requestUrgentContactList();
+    //上传推送toekn
+    onRequestNotificationReport();
   }
 
   void logout() {
@@ -225,6 +229,18 @@ class AccountRepository {
   Future<void> userClear() {
     return atmobApi.userClear(AppBaseRequest()).then(HttpHandler.handle(true));
   }
+
+  ///请求推送的数据
+  Future<void> onRequestNotificationReport() async {
+    // 初始化推送服务
+    var tokenStr = await IosPushNotificationService.getDeviceToken();
+    print("tokenStrsfsdf---${tokenStr}");
+    return atmobApi
+        .notificationReport(NotificationReportRequest(deviceToken: tokenStr as String))
+        .then(HttpHandler.handle(false))
+        .then((response) {})
+        .catchError((_) {});
+  }
 }
 
 class RequestCodeTooOftenException implements Exception {

+ 38 - 0
lib/main.dart

@@ -7,6 +7,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
 import 'package:flutter_screenutil/flutter_screenutil.dart';
 import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
 import 'package:get/get.dart';
+import 'package:location/push_notification/ios_push_notification_service.dart';
 import 'package:location/resource/colors.gen.dart';
 import 'package:location/resource/string.gen.dart';
 import 'package:location/resource/string_source.dart';
@@ -44,6 +45,11 @@ void main() {
     //隐私相关:系统参数&第三方sdk初始化
     await PrivacyCompliance.ensurePolicyGranted(AppInitTask());
 
+    ///实现推送
+    if (Platform.isIOS) {
+      initPushNotification();
+    }
+
     //檢查地址
     checkEnv();
   });
@@ -201,3 +207,35 @@ class AppCommonConfig {
   // 多语言配置
   static Translations translations = StringResource();
 }
+
+///加入推送功能
+void initPushNotification() {
+  WidgetsFlutterBinding.ensureInitialized();
+
+  // 初始化推送服务
+  IosPushNotificationService.initialize();
+
+  // 设置回调
+  IosPushNotificationService.onTokenReceived = (token) {
+    print('Device token: $token');
+    // 可以将令牌发送到服务器
+  };
+
+  IosPushNotificationService.onTokenError = (error) {
+    print('Token error: $error');
+  };
+
+  IosPushNotificationService.onNotificationReceived = (data) {
+    print('Received notification: $data');
+    //PushNotificationService.handleNotificationPushChick(data);
+    // 处理前台收到的通知
+  };
+
+  IosPushNotificationService.onNotificationTapped = (data) {
+    //{skipType: 2, aps: {alert: {subtitle: Your friend Emergency Help - it is recommended to contact this friend immediately. , title: LocateX}, sound: default}}
+    print('Notification tapped: $data');
+    IosPushNotificationService.handleNotificationPushChick(data);
+    // 处理通知点击事件
+    ///埋点
+  };
+}

+ 86 - 0
lib/push_notification/ios_push_notification_service.dart

@@ -0,0 +1,86 @@
+import 'dart:convert';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:get/get.dart';
+import 'package:get/get_core/src/get_main.dart';
+
+
+class IosPushNotificationService {
+  static const MethodChannel _channel = MethodChannel('com.example.app/push_notification');
+
+  // 回调函数
+  static Function(String)? onTokenReceived;
+  static Function(String)? onTokenError;
+  static Function(Map<String, dynamic>)? onNotificationReceived;
+  static Function(Map<String, dynamic>)? onNotificationTapped;
+
+  // 初始化推送服务
+  static void initialize() {
+    _channel.setMethodCallHandler((call) async {
+      switch (call.method) {
+        case 'onTokenReceived':
+          onTokenReceived?.call(call.arguments);
+          break;
+        case 'onTokenError':
+          onTokenError?.call(call.arguments);
+          break;
+        case 'onNotificationReceived':
+          if (call.arguments is Map) {
+            onNotificationReceived?.call(Map<String, dynamic>.from(call.arguments));
+          }
+          break;
+        case 'onNotificationTapped':
+          if (call.arguments is Map) {
+            onNotificationTapped?.call(Map<String, dynamic>.from(call.arguments));
+          }
+          break;
+        default:
+          throw PlatformException(
+            code: 'UNIMPLEMENTED',
+            message: 'Method not implemented',
+            details: null,
+          );
+      }
+    });
+  }
+
+  // 请求推送权限
+  static Future<bool> requestPermission() async {
+    try {
+      final bool? granted = await _channel.invokeMethod('requestPermission');
+      return granted ?? false;
+    } on PlatformException catch (e) {
+      print('Failed to request permission: ${e.message}');
+      return false;
+    }
+  }
+
+  // 获取设备令牌
+  static Future<String?> getDeviceToken() async {
+    try {
+      final String? token = await _channel.invokeMethod('getDeviceToken');
+      print("Strdefdjifjd---${token}");
+      return token;
+    } on PlatformException catch (e) {
+      print('Failed to get device token: ${e.message}');
+      return null;
+    }
+  }
+
+  ///处理推送各种点击 skipType 0-首页 1-好友申请页面 2-好友消息页面
+  static void handleNotificationPushChick(Map<String,dynamic> pushDict) {
+    int skipType = pushDict["skipType"];
+    // skipType = 0;
+    // if (skipType == 0) {
+    //   Get.until((route) => route.isFirst);
+    //   // 在其他页面或组件中
+    //   final mainController = Get.find<MainController>();
+    //   // 调用方法
+    //   mainController.onTabClick(0); // 切换到第一个Tab
+    // } else if (skipType == 1) {
+    //   MessagePage.start(initialIndex: 0);
+    // } else if (skipType == 2) {
+    //   MessagePage.start(initialIndex: 1);
+    // }
+  }
+}