mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:28:41 +00:00
#213 for android finished
This commit is contained in:
parent
52280c55de
commit
3b777f7130
32 changed files with 2100 additions and 914 deletions
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
protoc --proto_path="./lib/src/model/protobuf/backup/" --dart_out="./lib/src/model/protobuf/backup/" "backup.proto"
|
||||
|
||||
protoc --proto_path="./lib/src/model/protobuf/push_notification/" --dart_out="./lib/src/model/protobuf/push_notification/" "push_notification.proto"
|
||||
protoc --proto_path="./lib/src/model/protobuf/push_notification/" --swift_out="./ios/NotificationService/" "push_notification.proto"
|
||||
|
||||
|
||||
SRC_DIR="../twonly-server/twonly/src/"
|
||||
|
|
|
|||
|
|
@ -5,419 +5,417 @@
|
|||
// Created by Tobi on 03.04.25.
|
||||
//
|
||||
|
||||
import UserNotifications
|
||||
import CryptoKit
|
||||
import Foundation
|
||||
// import UserNotifications
|
||||
// import CryptoKit
|
||||
// import Foundation
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
// class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
var bestAttemptContent: UNMutableNotificationContent?
|
||||
// var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
// var bestAttemptContent: UNMutableNotificationContent?
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
self.contentHandler = contentHandler
|
||||
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||
// override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
// self.contentHandler = contentHandler
|
||||
// bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||
|
||||
if let bestAttemptContent = bestAttemptContent {
|
||||
// if let bestAttemptContent = bestAttemptContent {
|
||||
|
||||
guard let _ = bestAttemptContent.userInfo as? [String: Any],
|
||||
let push_data = bestAttemptContent.userInfo["push_data"] as? String else {
|
||||
return contentHandler(bestAttemptContent);
|
||||
}
|
||||
// guard let _ = bestAttemptContent.userInfo as? [String: Any],
|
||||
// let push_data = bestAttemptContent.userInfo["push_data"] as? String else {
|
||||
// return contentHandler(bestAttemptContent);
|
||||
// }
|
||||
|
||||
let data = getPushNotificationData(pushDataJson: push_data)
|
||||
// let data = getPushNotificationData(pushDataJson: push_data)
|
||||
|
||||
if data != nil {
|
||||
if data!.title == "blocked" {
|
||||
NSLog("Block message because user is blocked!")
|
||||
// https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.usernotifications.filtering
|
||||
return contentHandler(UNNotificationContent())
|
||||
}
|
||||
bestAttemptContent.title = data!.title;
|
||||
bestAttemptContent.body = data!.body;
|
||||
bestAttemptContent.threadIdentifier = String(format: "%d", data!.notificationId)
|
||||
} else {
|
||||
bestAttemptContent.title = "\(bestAttemptContent.title)"
|
||||
}
|
||||
// if data != nil {
|
||||
// if data!.title == "blocked" {
|
||||
// NSLog("Block message because user is blocked!")
|
||||
// // https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.usernotifications.filtering
|
||||
// return contentHandler(UNNotificationContent())
|
||||
// }
|
||||
// bestAttemptContent.title = data!.title;
|
||||
// bestAttemptContent.body = data!.body;
|
||||
// bestAttemptContent.threadIdentifier = String(format: "%d", data!.notificationId)
|
||||
// } else {
|
||||
// bestAttemptContent.title = "\(bestAttemptContent.title)"
|
||||
// }
|
||||
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
// contentHandler(bestAttemptContent)
|
||||
// }
|
||||
// }
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
// Called just before the extension will be terminated by the system.
|
||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||||
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
// override func serviceExtensionTimeWillExpire() {
|
||||
// // Called just before the extension will be terminated by the system.
|
||||
// // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||||
// if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
|
||||
// contentHandler(bestAttemptContent)
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
// enum PushKind: String, Codable {
|
||||
// case text
|
||||
// case twonly
|
||||
// case video
|
||||
// case image
|
||||
// case contactRequest
|
||||
// case acceptRequest
|
||||
// case storedMediaFile
|
||||
// case reaction
|
||||
// case testNotification
|
||||
// case reopenedMedia
|
||||
// case reactionToVideo
|
||||
// case reactionToText
|
||||
// case reactionToImage
|
||||
// case response
|
||||
// }
|
||||
|
||||
enum PushKind: String, Codable {
|
||||
case text
|
||||
case twonly
|
||||
case video
|
||||
case image
|
||||
case contactRequest
|
||||
case acceptRequest
|
||||
case storedMediaFile
|
||||
case reaction
|
||||
case testNotification
|
||||
case reopenedMedia
|
||||
case reactionToVideo
|
||||
case reactionToText
|
||||
case reactionToImage
|
||||
case response
|
||||
}
|
||||
// import CryptoKit
|
||||
// import Foundation
|
||||
// import Security
|
||||
|
||||
import CryptoKit
|
||||
import Foundation
|
||||
import Security
|
||||
// func getPushNotificationData(pushDataJson: String) -> (title: String, body: String, notificationId: Int)? {
|
||||
// // Decode the pushDataJson
|
||||
// guard let pushData = decodePushData(pushDataJson) else {
|
||||
// NSLog("Failed to decode push data")
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func getPushNotificationData(pushDataJson: String) -> (title: String, body: String, notificationId: Int)? {
|
||||
// Decode the pushDataJson
|
||||
guard let pushData = decodePushData(pushDataJson) else {
|
||||
NSLog("Failed to decode push data")
|
||||
return nil
|
||||
}
|
||||
// var pushKind: PushKind?
|
||||
// var displayName: String?
|
||||
// var fromUserId: Int?
|
||||
// var blocked: Bool?
|
||||
|
||||
var pushKind: PushKind?
|
||||
var displayName: String?
|
||||
var fromUserId: Int?
|
||||
var blocked: Bool?
|
||||
// // Check the keyId
|
||||
// if pushData.keyId == 0 {
|
||||
// let key = "InsecureOnlyUsedForAddingContact".data(using: .utf8)!.map { Int($0) }
|
||||
// pushKind = tryDecryptMessage(key: key, pushData: pushData)
|
||||
// } else {
|
||||
// let pushKeys = getPushKey()
|
||||
// if pushKeys != nil {
|
||||
// for (userId, userKeys) in pushKeys! {
|
||||
// for key in userKeys.keys {
|
||||
// if key.id == pushData.keyId {
|
||||
// pushKind = tryDecryptMessage(key: key.key, pushData: pushData)
|
||||
// if pushKind != nil {
|
||||
// displayName = userKeys.displayName
|
||||
// fromUserId = userId
|
||||
// blocked = userKeys.blocked
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // Found correct key and user
|
||||
// if displayName != nil { break }
|
||||
// }
|
||||
// } else {
|
||||
// NSLog("pushKeys are empty")
|
||||
// }
|
||||
// }
|
||||
|
||||
// Check the keyId
|
||||
if pushData.keyId == 0 {
|
||||
let key = "InsecureOnlyUsedForAddingContact".data(using: .utf8)!.map { Int($0) }
|
||||
pushKind = tryDecryptMessage(key: key, pushData: pushData)
|
||||
} else {
|
||||
let pushKeys = getPushKey()
|
||||
if pushKeys != nil {
|
||||
for (userId, userKeys) in pushKeys! {
|
||||
for key in userKeys.keys {
|
||||
if key.id == pushData.keyId {
|
||||
pushKind = tryDecryptMessage(key: key.key, pushData: pushData)
|
||||
if pushKind != nil {
|
||||
displayName = userKeys.displayName
|
||||
fromUserId = userId
|
||||
blocked = userKeys.blocked
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// Found correct key and user
|
||||
if displayName != nil { break }
|
||||
}
|
||||
} else {
|
||||
NSLog("pushKeys are empty")
|
||||
}
|
||||
}
|
||||
// if blocked == true {
|
||||
// return ("blocked", "blocked", 0)
|
||||
// }
|
||||
|
||||
if blocked == true {
|
||||
return ("blocked", "blocked", 0)
|
||||
}
|
||||
// // Handle the push notification based on the pushKind
|
||||
// if let pushKind = pushKind {
|
||||
|
||||
// Handle the push notification based on the pushKind
|
||||
if let pushKind = pushKind {
|
||||
// if pushKind == .testNotification {
|
||||
// return ("Test Notification", "This is a test notification.", 0)
|
||||
// } else if displayName != nil && fromUserId != nil {
|
||||
// return (displayName!, getPushNotificationText(pushKind: pushKind), fromUserId!)
|
||||
// } else {
|
||||
// return ("", getPushNotificationTextWithoutUserId(pushKind: pushKind), 1)
|
||||
// }
|
||||
|
||||
if pushKind == .testNotification {
|
||||
return ("Test Notification", "This is a test notification.", 0)
|
||||
} else if displayName != nil && fromUserId != nil {
|
||||
return (displayName!, getPushNotificationText(pushKind: pushKind), fromUserId!)
|
||||
} else {
|
||||
return ("", getPushNotificationTextWithoutUserId(pushKind: pushKind), 1)
|
||||
}
|
||||
// } else {
|
||||
// NSLog("Failed to decrypt message or pushKind is nil")
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
} else {
|
||||
NSLog("Failed to decrypt message or pushKind is nil")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func tryDecryptMessage(key: [Int], pushData: PushNotification) -> PushKind? {
|
||||
// // Convert the key from [Int] to Data
|
||||
// let keyData = Data(key.map { UInt8($0) }) // Convert Int to UInt8
|
||||
|
||||
func tryDecryptMessage(key: [Int], pushData: PushNotification) -> PushKind? {
|
||||
// Convert the key from [Int] to Data
|
||||
let keyData = Data(key.map { UInt8($0) }) // Convert Int to UInt8
|
||||
// guard let nonceData = Data(base64Encoded: pushData.nonce),
|
||||
// let cipherTextData = Data(base64Encoded: pushData.cipherText),
|
||||
// let macData = Data(base64Encoded: pushData.mac) else {
|
||||
// NSLog("Failed to decode base64 strings")
|
||||
// return nil
|
||||
// }
|
||||
|
||||
guard let nonceData = Data(base64Encoded: pushData.nonce),
|
||||
let cipherTextData = Data(base64Encoded: pushData.cipherText),
|
||||
let macData = Data(base64Encoded: pushData.mac) else {
|
||||
NSLog("Failed to decode base64 strings")
|
||||
return nil
|
||||
}
|
||||
// do {
|
||||
// // Create a nonce for ChaChaPoly
|
||||
// let nonce = try ChaChaPoly.Nonce(data: nonceData)
|
||||
|
||||
do {
|
||||
// Create a nonce for ChaChaPoly
|
||||
let nonce = try ChaChaPoly.Nonce(data: nonceData)
|
||||
// // Create a sealed box for ChaChaPoly
|
||||
// let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: cipherTextData, tag: macData)
|
||||
|
||||
// Create a sealed box for ChaChaPoly
|
||||
let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: cipherTextData, tag: macData)
|
||||
// // Decrypt the data using the key
|
||||
// let decryptedData = try ChaChaPoly.open(sealedBox, using: SymmetricKey(data: keyData))
|
||||
|
||||
// Decrypt the data using the key
|
||||
let decryptedData = try ChaChaPoly.open(sealedBox, using: SymmetricKey(data: keyData))
|
||||
// // Convert decrypted data to a string
|
||||
// if let decryptedMessage = String(data: decryptedData, encoding: .utf8) {
|
||||
// NSLog("Decrypted message: \(decryptedMessage)")
|
||||
|
||||
// Convert decrypted data to a string
|
||||
if let decryptedMessage = String(data: decryptedData, encoding: .utf8) {
|
||||
NSLog("Decrypted message: \(decryptedMessage)")
|
||||
// // Here you can determine the PushKind based on the decrypted message
|
||||
// return determinePushKind(from: decryptedMessage)
|
||||
// }
|
||||
// } catch {
|
||||
// NSLog("Decryption failed: \(error)")
|
||||
// }
|
||||
|
||||
// Here you can determine the PushKind based on the decrypted message
|
||||
return determinePushKind(from: decryptedMessage)
|
||||
}
|
||||
} catch {
|
||||
NSLog("Decryption failed: \(error)")
|
||||
}
|
||||
// return nil
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
// // Placeholder function to determine PushKind from the decrypted message
|
||||
// func determinePushKind(from message: String) -> PushKind? {
|
||||
// // Implement your logic to determine the PushKind based on the message content
|
||||
// // For example, you might check for specific keywords or formats in the message
|
||||
// // This is just a placeholder implementation
|
||||
// if message.contains("text") {
|
||||
// return .text
|
||||
// } else if message.contains("video") {
|
||||
// return .video
|
||||
// } else if message.contains("image") {
|
||||
// return .image
|
||||
// } else if message.contains("twonly") {
|
||||
// return .twonly
|
||||
// } else if message.contains("contactRequest") {
|
||||
// return .contactRequest
|
||||
// } else if message.contains("acceptRequest") {
|
||||
// return .acceptRequest
|
||||
// } else if message.contains("storedMediaFile") {
|
||||
// return .storedMediaFile
|
||||
// } else if message.contains("reaction") {
|
||||
// return .reaction
|
||||
// } else if message.contains("testNotification") {
|
||||
// return .testNotification
|
||||
// } else if message.contains("reopenedMedia") {
|
||||
// return .reopenedMedia
|
||||
// } else if message.contains("reactionToVideo") {
|
||||
// return .reactionToVideo
|
||||
// } else if message.contains("reactionToText") {
|
||||
// return .reactionToText
|
||||
// } else if message.contains("reactionToImage") {
|
||||
// return .reactionToImage
|
||||
// } else if message.contains("response") {
|
||||
// return .response
|
||||
// } else {
|
||||
// return nil // Unknown PushKind
|
||||
// }
|
||||
// }
|
||||
|
||||
// Placeholder function to determine PushKind from the decrypted message
|
||||
func determinePushKind(from message: String) -> PushKind? {
|
||||
// Implement your logic to determine the PushKind based on the message content
|
||||
// For example, you might check for specific keywords or formats in the message
|
||||
// This is just a placeholder implementation
|
||||
if message.contains("text") {
|
||||
return .text
|
||||
} else if message.contains("video") {
|
||||
return .video
|
||||
} else if message.contains("image") {
|
||||
return .image
|
||||
} else if message.contains("twonly") {
|
||||
return .twonly
|
||||
} else if message.contains("contactRequest") {
|
||||
return .contactRequest
|
||||
} else if message.contains("acceptRequest") {
|
||||
return .acceptRequest
|
||||
} else if message.contains("storedMediaFile") {
|
||||
return .storedMediaFile
|
||||
} else if message.contains("reaction") {
|
||||
return .reaction
|
||||
} else if message.contains("testNotification") {
|
||||
return .testNotification
|
||||
} else if message.contains("reopenedMedia") {
|
||||
return .reopenedMedia
|
||||
} else if message.contains("reactionToVideo") {
|
||||
return .reactionToVideo
|
||||
} else if message.contains("reactionToText") {
|
||||
return .reactionToText
|
||||
} else if message.contains("reactionToImage") {
|
||||
return .reactionToImage
|
||||
} else if message.contains("response") {
|
||||
return .response
|
||||
} else {
|
||||
return nil // Unknown PushKind
|
||||
}
|
||||
}
|
||||
// func decodePushData(_ json: String) -> PushNotification? {
|
||||
// // First, decode the base64 string
|
||||
// guard let base64Data = Data(base64Encoded: json) else {
|
||||
// NSLog("Failed to decode base64 string")
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // Convert the base64 decoded data to a JSON string
|
||||
// guard let jsonString = String(data: base64Data, encoding: .utf8) else {
|
||||
// NSLog("Failed to convert base64 data to JSON string")
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func decodePushData(_ json: String) -> PushNotification? {
|
||||
// First, decode the base64 string
|
||||
guard let base64Data = Data(base64Encoded: json) else {
|
||||
NSLog("Failed to decode base64 string")
|
||||
return nil
|
||||
}
|
||||
// // Convert the JSON string to Data
|
||||
// guard let jsonData = jsonString.data(using: .utf8) else {
|
||||
// NSLog("Failed to convert JSON string to Data")
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// Convert the base64 decoded data to a JSON string
|
||||
guard let jsonString = String(data: base64Data, encoding: .utf8) else {
|
||||
NSLog("Failed to convert base64 data to JSON string")
|
||||
return nil
|
||||
}
|
||||
// do {
|
||||
// // Use JSONDecoder to decode the JSON data into a PushNotification instance
|
||||
// let decoder = JSONDecoder()
|
||||
// let pushNotification = try decoder.decode(PushNotification.self, from: jsonData)
|
||||
// return pushNotification
|
||||
// } catch {
|
||||
// NSLog("Error decoding JSON: \(error)")
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
|
||||
// Convert the JSON string to Data
|
||||
guard let jsonData = jsonString.data(using: .utf8) else {
|
||||
NSLog("Failed to convert JSON string to Data")
|
||||
return nil
|
||||
}
|
||||
// struct PushNotification: Codable {
|
||||
// let keyId: Int
|
||||
// let nonce: String
|
||||
// let cipherText: String
|
||||
// let mac: String
|
||||
|
||||
do {
|
||||
// Use JSONDecoder to decode the JSON data into a PushNotification instance
|
||||
let decoder = JSONDecoder()
|
||||
let pushNotification = try decoder.decode(PushNotification.self, from: jsonData)
|
||||
return pushNotification
|
||||
} catch {
|
||||
NSLog("Error decoding JSON: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// // You can add custom coding keys if the JSON keys differ from the property names
|
||||
// enum CodingKeys: String, CodingKey {
|
||||
// case keyId
|
||||
// case nonce
|
||||
// case cipherText
|
||||
// case mac
|
||||
// }
|
||||
// }
|
||||
|
||||
struct PushNotification: Codable {
|
||||
let keyId: Int
|
||||
let nonce: String
|
||||
let cipherText: String
|
||||
let mac: String
|
||||
// struct PushKeyMeta: Codable {
|
||||
// let id: Int
|
||||
// let key: [Int]
|
||||
// let createdAt: Date
|
||||
|
||||
// You can add custom coding keys if the JSON keys differ from the property names
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case keyId
|
||||
case nonce
|
||||
case cipherText
|
||||
case mac
|
||||
}
|
||||
}
|
||||
// enum CodingKeys: String, CodingKey {
|
||||
// case id
|
||||
// case key
|
||||
// case createdAt
|
||||
// }
|
||||
// }
|
||||
|
||||
struct PushKeyMeta: Codable {
|
||||
let id: Int
|
||||
let key: [Int]
|
||||
let createdAt: Date
|
||||
// struct PushUser: Codable {
|
||||
// let displayName: String
|
||||
// let keys: [PushKeyMeta]
|
||||
// let blocked: Bool?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case key
|
||||
case createdAt
|
||||
}
|
||||
}
|
||||
// enum CodingKeys: String, CodingKey {
|
||||
// case displayName
|
||||
// case keys
|
||||
// case blocked
|
||||
// }
|
||||
// }
|
||||
|
||||
struct PushUser: Codable {
|
||||
let displayName: String
|
||||
let keys: [PushKeyMeta]
|
||||
let blocked: Bool?
|
||||
// func getPushKey() -> [Int: PushUser]? {
|
||||
// // Retrieve the data from secure storage (Keychain)
|
||||
// guard let data = readFromKeychain(key: "receivingPushKeys") else {
|
||||
// NSLog("No data found for key: receivingPushKeys")
|
||||
// return nil
|
||||
// }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case displayName
|
||||
case keys
|
||||
case blocked
|
||||
}
|
||||
}
|
||||
// do {
|
||||
// // Decode the JSON data into a dictionary
|
||||
// let jsonData = data.data(using: .utf8)!
|
||||
// let jsonMap = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any]
|
||||
|
||||
func getPushKey() -> [Int: PushUser]? {
|
||||
// Retrieve the data from secure storage (Keychain)
|
||||
guard let data = readFromKeychain(key: "receivingPushKeys") else {
|
||||
NSLog("No data found for key: receivingPushKeys")
|
||||
return nil
|
||||
}
|
||||
// var pushKeys: [Int: PushUser] = [:]
|
||||
|
||||
do {
|
||||
// Decode the JSON data into a dictionary
|
||||
let jsonData = data.data(using: .utf8)!
|
||||
let jsonMap = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any]
|
||||
// // Iterate through the JSON map and decode each PushUser
|
||||
// for (key, value) in jsonMap ?? [:] {
|
||||
// if let userData = try? JSONSerialization.data(withJSONObject: value, options: []),
|
||||
// let pushUser = try? JSONDecoder().decode(PushUser.self, from: userData) {
|
||||
// pushKeys[Int(key)!] = pushUser
|
||||
// }
|
||||
// }
|
||||
|
||||
var pushKeys: [Int: PushUser] = [:]
|
||||
// return pushKeys
|
||||
// } catch {
|
||||
// NSLog("Error decoding JSON: \(error)")
|
||||
// return nil
|
||||
// }
|
||||
// }
|
||||
|
||||
// Iterate through the JSON map and decode each PushUser
|
||||
for (key, value) in jsonMap ?? [:] {
|
||||
if let userData = try? JSONSerialization.data(withJSONObject: value, options: []),
|
||||
let pushUser = try? JSONDecoder().decode(PushUser.self, from: userData) {
|
||||
pushKeys[Int(key)!] = pushUser
|
||||
}
|
||||
}
|
||||
// // Helper function to read from Keychain
|
||||
// func readFromKeychain(key: String) -> String? {
|
||||
// let query: [String: Any] = [
|
||||
// kSecClass as String: kSecClassGenericPassword,
|
||||
// kSecAttrAccount as String: key,
|
||||
// kSecReturnData as String: kCFBooleanTrue!,
|
||||
// kSecMatchLimit as String: kSecMatchLimitOne,
|
||||
// kSecAttrAccessGroup as String: "CN332ZUGRP.eu.twonly.shared" // Use your access group
|
||||
// ]
|
||||
|
||||
return pushKeys
|
||||
} catch {
|
||||
NSLog("Error decoding JSON: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// var dataTypeRef: AnyObject? = nil
|
||||
// let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
||||
|
||||
// Helper function to read from Keychain
|
||||
func readFromKeychain(key: String) -> String? {
|
||||
let query: [String: Any] = [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrAccount as String: key,
|
||||
kSecReturnData as String: kCFBooleanTrue!,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||
kSecAttrAccessGroup as String: "CN332ZUGRP.eu.twonly.shared" // Use your access group
|
||||
]
|
||||
// if status == errSecSuccess {
|
||||
// if let data = dataTypeRef as? Data {
|
||||
// return String(data: data, encoding: .utf8)
|
||||
// }
|
||||
// }
|
||||
|
||||
var dataTypeRef: AnyObject? = nil
|
||||
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
if status == errSecSuccess {
|
||||
if let data = dataTypeRef as? Data {
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
// func getPushNotificationText(pushKind: PushKind) -> String {
|
||||
// let systemLanguage = Locale.current.languageCode ?? "en" // Get the current system language
|
||||
|
||||
return nil
|
||||
}
|
||||
// var pushNotificationText: [PushKind: String] = [:]
|
||||
|
||||
func getPushNotificationText(pushKind: PushKind) -> String {
|
||||
let systemLanguage = Locale.current.languageCode ?? "en" // Get the current system language
|
||||
// // Define the messages based on the system language
|
||||
// if systemLanguage.contains("de") { // German
|
||||
// pushNotificationText = [
|
||||
// .text: "hat dir eine Nachricht gesendet.",
|
||||
// .twonly: "hat dir ein twonly gesendet.",
|
||||
// .video: "hat dir ein Video gesendet.",
|
||||
// .image: "hat dir ein Bild gesendet.",
|
||||
// .contactRequest: "möchte sich mit dir vernetzen.",
|
||||
// .acceptRequest: "ist jetzt mit dir vernetzt.",
|
||||
// .storedMediaFile: "hat dein Bild gespeichert.",
|
||||
// .reaction: "hat auf dein Bild reagiert.",
|
||||
// .testNotification: "Das ist eine Testbenachrichtigung.",
|
||||
// .reopenedMedia: "hat dein Bild erneut geöffnet.",
|
||||
// .reactionToVideo: "hat auf dein Video reagiert.",
|
||||
// .reactionToText: "hat auf deinen Text reagiert.",
|
||||
// .reactionToImage: "hat auf dein Bild reagiert.",
|
||||
// .response: "hat dir geantwortet."
|
||||
// ]
|
||||
// } else { // Default to English
|
||||
// pushNotificationText = [
|
||||
// .text: "has sent you a message.",
|
||||
// .twonly: "has sent you a twonly.",
|
||||
// .video: "has sent you a video.",
|
||||
// .image: "has sent you an image.",
|
||||
// .contactRequest: "wants to connect with you.",
|
||||
// .acceptRequest: "is now connected with you.",
|
||||
// .storedMediaFile: "has stored your image.",
|
||||
// .reaction: "has reacted to your image.",
|
||||
// .testNotification: "This is a test notification.",
|
||||
// .reopenedMedia: "has reopened your image.",
|
||||
// .reactionToVideo: "has reacted to your video.",
|
||||
// .reactionToText: "has reacted to your text.",
|
||||
// .reactionToImage: "has reacted to your image.",
|
||||
// .response: "has responded."
|
||||
// ]
|
||||
// }
|
||||
|
||||
var pushNotificationText: [PushKind: String] = [:]
|
||||
// // Return the corresponding message or an empty string if not found
|
||||
// return pushNotificationText[pushKind] ?? ""
|
||||
// }
|
||||
|
||||
// Define the messages based on the system language
|
||||
if systemLanguage.contains("de") { // German
|
||||
pushNotificationText = [
|
||||
.text: "hat dir eine Nachricht gesendet.",
|
||||
.twonly: "hat dir ein twonly gesendet.",
|
||||
.video: "hat dir ein Video gesendet.",
|
||||
.image: "hat dir ein Bild gesendet.",
|
||||
.contactRequest: "möchte sich mit dir vernetzen.",
|
||||
.acceptRequest: "ist jetzt mit dir vernetzt.",
|
||||
.storedMediaFile: "hat dein Bild gespeichert.",
|
||||
.reaction: "hat auf dein Bild reagiert.",
|
||||
.testNotification: "Das ist eine Testbenachrichtigung.",
|
||||
.reopenedMedia: "hat dein Bild erneut geöffnet.",
|
||||
.reactionToVideo: "hat auf dein Video reagiert.",
|
||||
.reactionToText: "hat auf deinen Text reagiert.",
|
||||
.reactionToImage: "hat auf dein Bild reagiert.",
|
||||
.response: "hat dir geantwortet."
|
||||
]
|
||||
} else { // Default to English
|
||||
pushNotificationText = [
|
||||
.text: "has sent you a message.",
|
||||
.twonly: "has sent you a twonly.",
|
||||
.video: "has sent you a video.",
|
||||
.image: "has sent you an image.",
|
||||
.contactRequest: "wants to connect with you.",
|
||||
.acceptRequest: "is now connected with you.",
|
||||
.storedMediaFile: "has stored your image.",
|
||||
.reaction: "has reacted to your image.",
|
||||
.testNotification: "This is a test notification.",
|
||||
.reopenedMedia: "has reopened your image.",
|
||||
.reactionToVideo: "has reacted to your video.",
|
||||
.reactionToText: "has reacted to your text.",
|
||||
.reactionToImage: "has reacted to your image.",
|
||||
.response: "has responded."
|
||||
]
|
||||
}
|
||||
// func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String {
|
||||
// let systemLanguage = Locale.current.languageCode ?? "en" // Get the current system language
|
||||
|
||||
// Return the corresponding message or an empty string if not found
|
||||
return pushNotificationText[pushKind] ?? ""
|
||||
}
|
||||
// var pushNotificationText: [PushKind: String] = [:]
|
||||
|
||||
func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String {
|
||||
let systemLanguage = Locale.current.languageCode ?? "en" // Get the current system language
|
||||
// // Define the messages based on the system language
|
||||
// if systemLanguage.contains("de") { // German
|
||||
// pushNotificationText = [
|
||||
// .text: "Du hast eine Nachricht erhalten.",
|
||||
// .twonly: "Du hast ein twonly erhalten.",
|
||||
// .video: "Du hast ein Video erhalten.",
|
||||
// .image: "Du hast ein Bild erhalten.",
|
||||
// .contactRequest: "Du hast eine Kontaktanfrage erhalten.",
|
||||
// .acceptRequest: "Deine Kontaktanfrage wurde angenommen.",
|
||||
// .storedMediaFile: "Dein Bild wurde gespeichert.",
|
||||
// .reaction: "Du hast eine Reaktion auf dein Bild erhalten.",
|
||||
// .testNotification: "Das ist eine Testbenachrichtigung.",
|
||||
// .reopenedMedia: "hat dein Bild erneut geöffnet.",
|
||||
// .reactionToVideo: "Du hast eine Reaktion auf dein Video erhalten.",
|
||||
// .reactionToText: "Du hast eine Reaktion auf deinen Text erhalten.",
|
||||
// .reactionToImage: "Du hast eine Reaktion auf dein Bild erhalten.",
|
||||
// .response: "Du hast eine Antwort erhalten."
|
||||
// ]
|
||||
// } else { // Default to English
|
||||
// pushNotificationText = [
|
||||
// .text: "You got a message.",
|
||||
// .twonly: "You got a twonly.",
|
||||
// .video: "You got a video.",
|
||||
// .image: "You got an image.",
|
||||
// .contactRequest: "You got a contact request.",
|
||||
// .acceptRequest: "Your contact request has been accepted.",
|
||||
// .storedMediaFile: "Your image has been saved.",
|
||||
// .reaction: "You got a reaction to your image.",
|
||||
// .testNotification: "This is a test notification.",
|
||||
// .reopenedMedia: "has reopened your image.",
|
||||
// .reactionToVideo: "You got a reaction to your video.",
|
||||
// .reactionToText: "You got a reaction to your text.",
|
||||
// .reactionToImage: "You got a reaction to your image.",
|
||||
// .response: "You got a response."
|
||||
// ]
|
||||
// }
|
||||
|
||||
var pushNotificationText: [PushKind: String] = [:]
|
||||
|
||||
// Define the messages based on the system language
|
||||
if systemLanguage.contains("de") { // German
|
||||
pushNotificationText = [
|
||||
.text: "Du hast eine Nachricht erhalten.",
|
||||
.twonly: "Du hast ein twonly erhalten.",
|
||||
.video: "Du hast ein Video erhalten.",
|
||||
.image: "Du hast ein Bild erhalten.",
|
||||
.contactRequest: "Du hast eine Kontaktanfrage erhalten.",
|
||||
.acceptRequest: "Deine Kontaktanfrage wurde angenommen.",
|
||||
.storedMediaFile: "Dein Bild wurde gespeichert.",
|
||||
.reaction: "Du hast eine Reaktion auf dein Bild erhalten.",
|
||||
.testNotification: "Das ist eine Testbenachrichtigung.",
|
||||
.reopenedMedia: "hat dein Bild erneut geöffnet.",
|
||||
.reactionToVideo: "Du hast eine Reaktion auf dein Video erhalten.",
|
||||
.reactionToText: "Du hast eine Reaktion auf deinen Text erhalten.",
|
||||
.reactionToImage: "Du hast eine Reaktion auf dein Bild erhalten.",
|
||||
.response: "Du hast eine Antwort erhalten."
|
||||
]
|
||||
} else { // Default to English
|
||||
pushNotificationText = [
|
||||
.text: "You got a message.",
|
||||
.twonly: "You got a twonly.",
|
||||
.video: "You got a video.",
|
||||
.image: "You got an image.",
|
||||
.contactRequest: "You got a contact request.",
|
||||
.acceptRequest: "Your contact request has been accepted.",
|
||||
.storedMediaFile: "Your image has been saved.",
|
||||
.reaction: "You got a reaction to your image.",
|
||||
.testNotification: "This is a test notification.",
|
||||
.reopenedMedia: "has reopened your image.",
|
||||
.reactionToVideo: "You got a reaction to your video.",
|
||||
.reactionToText: "You got a reaction to your text.",
|
||||
.reactionToImage: "You got a reaction to your image.",
|
||||
.response: "You got a response."
|
||||
]
|
||||
}
|
||||
|
||||
// Return the corresponding message or an empty string if not found
|
||||
return pushNotificationText[pushKind] ?? ""
|
||||
}
|
||||
// // Return the corresponding message or an empty string if not found
|
||||
// return pushNotificationText[pushKind] ?? ""
|
||||
// }
|
||||
|
|
|
|||
467
ios/NotificationService/push_notification.pb.swift
Normal file
467
ios/NotificationService/push_notification.pb.swift
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
// DO NOT EDIT.
|
||||
// swift-format-ignore-file
|
||||
// swiftlint:disable all
|
||||
//
|
||||
// Generated by the Swift generator plugin for the protocol buffer compiler.
|
||||
// Source: push_notification.proto
|
||||
//
|
||||
// For information on using the generated types, please see the documentation:
|
||||
// https://github.com/apple/swift-protobuf/
|
||||
|
||||
import Foundation
|
||||
import SwiftProtobuf
|
||||
|
||||
// If the compiler emits an error on this type, it is because this file
|
||||
// was generated by a version of the `protoc` Swift plug-in that is
|
||||
// incompatible with the version of SwiftProtobuf to which you are linking.
|
||||
// Please ensure that you are building against the same version of the API
|
||||
// that was used to generate this file.
|
||||
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
|
||||
struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
|
||||
typealias Version = _2
|
||||
}
|
||||
|
||||
enum PushKind: SwiftProtobuf.Enum, Swift.CaseIterable {
|
||||
typealias RawValue = Int
|
||||
case reaction // = 0
|
||||
case response // = 1
|
||||
case text // = 2
|
||||
case video // = 3
|
||||
case twonly // = 4
|
||||
case image // = 5
|
||||
case contactRequest // = 6
|
||||
case acceptRequest // = 7
|
||||
case storedMediaFile // = 8
|
||||
case testNotification // = 9
|
||||
case reopenedMedia // = 10
|
||||
case reactionToVideo // = 11
|
||||
case reactionToText // = 12
|
||||
case reactionToImage // = 13
|
||||
case UNRECOGNIZED(Int)
|
||||
|
||||
init() {
|
||||
self = .reaction
|
||||
}
|
||||
|
||||
init?(rawValue: Int) {
|
||||
switch rawValue {
|
||||
case 0: self = .reaction
|
||||
case 1: self = .response
|
||||
case 2: self = .text
|
||||
case 3: self = .video
|
||||
case 4: self = .twonly
|
||||
case 5: self = .image
|
||||
case 6: self = .contactRequest
|
||||
case 7: self = .acceptRequest
|
||||
case 8: self = .storedMediaFile
|
||||
case 9: self = .testNotification
|
||||
case 10: self = .reopenedMedia
|
||||
case 11: self = .reactionToVideo
|
||||
case 12: self = .reactionToText
|
||||
case 13: self = .reactionToImage
|
||||
default: self = .UNRECOGNIZED(rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
var rawValue: Int {
|
||||
switch self {
|
||||
case .reaction: return 0
|
||||
case .response: return 1
|
||||
case .text: return 2
|
||||
case .video: return 3
|
||||
case .twonly: return 4
|
||||
case .image: return 5
|
||||
case .contactRequest: return 6
|
||||
case .acceptRequest: return 7
|
||||
case .storedMediaFile: return 8
|
||||
case .testNotification: return 9
|
||||
case .reopenedMedia: return 10
|
||||
case .reactionToVideo: return 11
|
||||
case .reactionToText: return 12
|
||||
case .reactionToImage: return 13
|
||||
case .UNRECOGNIZED(let i): return i
|
||||
}
|
||||
}
|
||||
|
||||
// The compiler won't synthesize support with the UNRECOGNIZED case.
|
||||
static let allCases: [PushKind] = [
|
||||
.reaction,
|
||||
.response,
|
||||
.text,
|
||||
.video,
|
||||
.twonly,
|
||||
.image,
|
||||
.contactRequest,
|
||||
.acceptRequest,
|
||||
.storedMediaFile,
|
||||
.testNotification,
|
||||
.reopenedMedia,
|
||||
.reactionToVideo,
|
||||
.reactionToText,
|
||||
.reactionToImage,
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
struct EncryptedPushNotification: @unchecked Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var keyID: Int64 = 0
|
||||
|
||||
var nonce: Data = Data()
|
||||
|
||||
var ciphertext: Data = Data()
|
||||
|
||||
var mac: Data = Data()
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
struct PushNotification: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var kind: PushKind = .reaction
|
||||
|
||||
var messageID: Int64 {
|
||||
get {return _messageID ?? 0}
|
||||
set {_messageID = newValue}
|
||||
}
|
||||
/// Returns true if `messageID` has been explicitly set.
|
||||
var hasMessageID: Bool {return self._messageID != nil}
|
||||
/// Clears the value of `messageID`. Subsequent reads from it will return its default value.
|
||||
mutating func clearMessageID() {self._messageID = nil}
|
||||
|
||||
var reactionContent: String {
|
||||
get {return _reactionContent ?? String()}
|
||||
set {_reactionContent = newValue}
|
||||
}
|
||||
/// Returns true if `reactionContent` has been explicitly set.
|
||||
var hasReactionContent: Bool {return self._reactionContent != nil}
|
||||
/// Clears the value of `reactionContent`. Subsequent reads from it will return its default value.
|
||||
mutating func clearReactionContent() {self._reactionContent = nil}
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _messageID: Int64? = nil
|
||||
fileprivate var _reactionContent: String? = nil
|
||||
}
|
||||
|
||||
struct PushUsers: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var users: [PushUser] = []
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
struct PushUser: Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var userID: Int64 = 0
|
||||
|
||||
var displayName: String = String()
|
||||
|
||||
var blocked: Bool = false
|
||||
|
||||
var lastMessageID: Int64 {
|
||||
get {return _lastMessageID ?? 0}
|
||||
set {_lastMessageID = newValue}
|
||||
}
|
||||
/// Returns true if `lastMessageID` has been explicitly set.
|
||||
var hasLastMessageID: Bool {return self._lastMessageID != nil}
|
||||
/// Clears the value of `lastMessageID`. Subsequent reads from it will return its default value.
|
||||
mutating func clearLastMessageID() {self._lastMessageID = nil}
|
||||
|
||||
var pushKeys: [PushKey] = []
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
|
||||
fileprivate var _lastMessageID: Int64? = nil
|
||||
}
|
||||
|
||||
struct PushKey: @unchecked Sendable {
|
||||
// SwiftProtobuf.Message conformance is added in an extension below. See the
|
||||
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
|
||||
// methods supported on all messages.
|
||||
|
||||
var id: Int64 = 0
|
||||
|
||||
var key: Data = Data()
|
||||
|
||||
var createdAtUnixTimestamp: Int64 = 0
|
||||
|
||||
var unknownFields = SwiftProtobuf.UnknownStorage()
|
||||
|
||||
init() {}
|
||||
}
|
||||
|
||||
// MARK: - Code below here is support for the SwiftProtobuf runtime.
|
||||
|
||||
extension PushKind: SwiftProtobuf._ProtoNameProviding {
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
0: .same(proto: "reaction"),
|
||||
1: .same(proto: "response"),
|
||||
2: .same(proto: "text"),
|
||||
3: .same(proto: "video"),
|
||||
4: .same(proto: "twonly"),
|
||||
5: .same(proto: "image"),
|
||||
6: .same(proto: "contactRequest"),
|
||||
7: .same(proto: "acceptRequest"),
|
||||
8: .same(proto: "storedMediaFile"),
|
||||
9: .same(proto: "testNotification"),
|
||||
10: .same(proto: "reopenedMedia"),
|
||||
11: .same(proto: "reactionToVideo"),
|
||||
12: .same(proto: "reactionToText"),
|
||||
13: .same(proto: "reactionToImage"),
|
||||
]
|
||||
}
|
||||
|
||||
extension EncryptedPushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "EncryptedPushNotification"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "keyId"),
|
||||
2: .same(proto: "nonce"),
|
||||
3: .same(proto: "ciphertext"),
|
||||
4: .same(proto: "mac"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularInt64Field(value: &self.keyID) }()
|
||||
case 2: try { try decoder.decodeSingularBytesField(value: &self.nonce) }()
|
||||
case 3: try { try decoder.decodeSingularBytesField(value: &self.ciphertext) }()
|
||||
case 4: try { try decoder.decodeSingularBytesField(value: &self.mac) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.keyID != 0 {
|
||||
try visitor.visitSingularInt64Field(value: self.keyID, fieldNumber: 1)
|
||||
}
|
||||
if !self.nonce.isEmpty {
|
||||
try visitor.visitSingularBytesField(value: self.nonce, fieldNumber: 2)
|
||||
}
|
||||
if !self.ciphertext.isEmpty {
|
||||
try visitor.visitSingularBytesField(value: self.ciphertext, fieldNumber: 3)
|
||||
}
|
||||
if !self.mac.isEmpty {
|
||||
try visitor.visitSingularBytesField(value: self.mac, fieldNumber: 4)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: EncryptedPushNotification, rhs: EncryptedPushNotification) -> Bool {
|
||||
if lhs.keyID != rhs.keyID {return false}
|
||||
if lhs.nonce != rhs.nonce {return false}
|
||||
if lhs.ciphertext != rhs.ciphertext {return false}
|
||||
if lhs.mac != rhs.mac {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension PushNotification: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "PushNotification"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "kind"),
|
||||
2: .same(proto: "messageId"),
|
||||
3: .same(proto: "reactionContent"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularEnumField(value: &self.kind) }()
|
||||
case 2: try { try decoder.decodeSingularInt64Field(value: &self._messageID) }()
|
||||
case 3: try { try decoder.decodeSingularStringField(value: &self._reactionContent) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if self.kind != .reaction {
|
||||
try visitor.visitSingularEnumField(value: self.kind, fieldNumber: 1)
|
||||
}
|
||||
try { if let v = self._messageID {
|
||||
try visitor.visitSingularInt64Field(value: v, fieldNumber: 2)
|
||||
} }()
|
||||
try { if let v = self._reactionContent {
|
||||
try visitor.visitSingularStringField(value: v, fieldNumber: 3)
|
||||
} }()
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: PushNotification, rhs: PushNotification) -> Bool {
|
||||
if lhs.kind != rhs.kind {return false}
|
||||
if lhs._messageID != rhs._messageID {return false}
|
||||
if lhs._reactionContent != rhs._reactionContent {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension PushUsers: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "PushUsers"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "users"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeRepeatedMessageField(value: &self.users) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if !self.users.isEmpty {
|
||||
try visitor.visitRepeatedMessageField(value: self.users, fieldNumber: 1)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: PushUsers, rhs: PushUsers) -> Bool {
|
||||
if lhs.users != rhs.users {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension PushUser: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "PushUser"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "userId"),
|
||||
2: .same(proto: "displayName"),
|
||||
3: .same(proto: "blocked"),
|
||||
4: .same(proto: "lastMessageId"),
|
||||
5: .same(proto: "pushKeys"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularInt64Field(value: &self.userID) }()
|
||||
case 2: try { try decoder.decodeSingularStringField(value: &self.displayName) }()
|
||||
case 3: try { try decoder.decodeSingularBoolField(value: &self.blocked) }()
|
||||
case 4: try { try decoder.decodeSingularInt64Field(value: &self._lastMessageID) }()
|
||||
case 5: try { try decoder.decodeRepeatedMessageField(value: &self.pushKeys) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every if/case branch local when no optimizations
|
||||
// are enabled. https://github.com/apple/swift-protobuf/issues/1034 and
|
||||
// https://github.com/apple/swift-protobuf/issues/1182
|
||||
if self.userID != 0 {
|
||||
try visitor.visitSingularInt64Field(value: self.userID, fieldNumber: 1)
|
||||
}
|
||||
if !self.displayName.isEmpty {
|
||||
try visitor.visitSingularStringField(value: self.displayName, fieldNumber: 2)
|
||||
}
|
||||
if self.blocked != false {
|
||||
try visitor.visitSingularBoolField(value: self.blocked, fieldNumber: 3)
|
||||
}
|
||||
try { if let v = self._lastMessageID {
|
||||
try visitor.visitSingularInt64Field(value: v, fieldNumber: 4)
|
||||
} }()
|
||||
if !self.pushKeys.isEmpty {
|
||||
try visitor.visitRepeatedMessageField(value: self.pushKeys, fieldNumber: 5)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: PushUser, rhs: PushUser) -> Bool {
|
||||
if lhs.userID != rhs.userID {return false}
|
||||
if lhs.displayName != rhs.displayName {return false}
|
||||
if lhs.blocked != rhs.blocked {return false}
|
||||
if lhs._lastMessageID != rhs._lastMessageID {return false}
|
||||
if lhs.pushKeys != rhs.pushKeys {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension PushKey: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
|
||||
static let protoMessageName: String = "PushKey"
|
||||
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
|
||||
1: .same(proto: "id"),
|
||||
2: .same(proto: "key"),
|
||||
3: .same(proto: "createdAtUnixTimestamp"),
|
||||
]
|
||||
|
||||
mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
|
||||
while let fieldNumber = try decoder.nextFieldNumber() {
|
||||
// The use of inline closures is to circumvent an issue where the compiler
|
||||
// allocates stack space for every case branch when no optimizations are
|
||||
// enabled. https://github.com/apple/swift-protobuf/issues/1034
|
||||
switch fieldNumber {
|
||||
case 1: try { try decoder.decodeSingularInt64Field(value: &self.id) }()
|
||||
case 2: try { try decoder.decodeSingularBytesField(value: &self.key) }()
|
||||
case 3: try { try decoder.decodeSingularInt64Field(value: &self.createdAtUnixTimestamp) }()
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
|
||||
if self.id != 0 {
|
||||
try visitor.visitSingularInt64Field(value: self.id, fieldNumber: 1)
|
||||
}
|
||||
if !self.key.isEmpty {
|
||||
try visitor.visitSingularBytesField(value: self.key, fieldNumber: 2)
|
||||
}
|
||||
if self.createdAtUnixTimestamp != 0 {
|
||||
try visitor.visitSingularInt64Field(value: self.createdAtUnixTimestamp, fieldNumber: 3)
|
||||
}
|
||||
try unknownFields.traverse(visitor: &visitor)
|
||||
}
|
||||
|
||||
static func ==(lhs: PushKey, rhs: PushKey) -> Bool {
|
||||
if lhs.id != rhs.id {return false}
|
||||
if lhs.key != rhs.key {return false}
|
||||
if lhs.createdAtUnixTimestamp != rhs.createdAtUnixTimestamp {return false}
|
||||
if lhs.unknownFields != rhs.unknownFields {return false}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ pod 'FirebaseMessaging', :modular_headers => true
|
|||
pod 'FirebaseCoreInternal', :modular_headers => true
|
||||
pod 'GoogleUtilities', :modular_headers => true
|
||||
pod 'FirebaseCore', :modular_headers => true
|
||||
pod 'SwiftProtobuf'
|
||||
# pod 'sqlite3', :modular_headers => true
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -216,6 +216,7 @@ PODS:
|
|||
- sqlite3/math
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- SwiftProtobuf (1.30.0)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- video_compress (0.3.0):
|
||||
|
|
@ -253,6 +254,7 @@ DEPENDENCIES:
|
|||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||
- SwiftProtobuf
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- video_compress (from `.symlinks/plugins/video_compress/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
|
|
@ -276,6 +278,7 @@ SPEC REPOS:
|
|||
- SDWebImage
|
||||
- SDWebImageWebPCoder
|
||||
- sqlite3
|
||||
- SwiftProtobuf
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
background_downloader:
|
||||
|
|
@ -372,10 +375,11 @@ SPEC CHECKSUMS:
|
|||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
|
||||
sqlite3_flutter_libs: 74334e3ef2dbdb7d37e50859bb45da43935779c4
|
||||
SwiftProtobuf: 3697407f0d5b23bedeba9c2eaaf3ec6fdff69349
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
video_compress: f2133a07762889d67f0711ac831faa26f956980e
|
||||
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
||||
|
||||
PODFILE CHECKSUM: 3e94c12f4f6904137d1449e3b100fda499ccd32d
|
||||
PODFILE CHECKSUM: a01f0821a361ca6708e29b1299e8becf492a8a71
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import 'package:twonly/src/localization/generated/app_localizations.dart';
|
|||
import 'package:twonly/src/providers/connection.provider.dart';
|
||||
import 'package:twonly/src/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/services/api/media_upload.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:twonly/src/views/onboarding/onboarding.view.dart';
|
||||
import 'package:twonly/src/views/home.view.dart';
|
||||
|
|
@ -36,7 +35,6 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
|||
globalCallbackConnectionState = (update) {
|
||||
context.read<CustomChangeProvider>().updateConnectionState(update);
|
||||
setUserPlan();
|
||||
setupNotificationWithUsers();
|
||||
};
|
||||
|
||||
initAsync();
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:twonly/src/providers/connection.provider.dart';
|
||||
import 'package:twonly/src/providers/settings.provider.dart';
|
||||
import 'package:twonly/src/services/fcm.service.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
||||
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
|
|
|||
|
|
@ -5,4 +5,7 @@ class SecureStorageKeys {
|
|||
static const String googleFcm = "google_fcm";
|
||||
static const String userData = "userData";
|
||||
static const String twonlySafeLastBackupHash = "twonly_safe_last_backup_hash";
|
||||
|
||||
static const String receivingPushKeys = "receiving_pus_keys";
|
||||
static const String sendingPushKeys = "sending_pus_keys";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:drift/drift.dart';
|
||||
import 'package:twonly/src/database/tables/contacts_table.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
|
||||
part 'contacts_dao.g.dart';
|
||||
|
||||
|
|
@ -113,7 +113,11 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
|||
|
||||
Future newMessageExchange(int userId) {
|
||||
return updateContact(
|
||||
userId, ContactsCompanion(lastMessageExchange: Value(DateTime.now())));
|
||||
userId,
|
||||
ContactsCompanion(
|
||||
lastMessageExchange: Value(DateTime.now()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Stream<List<Contact>> watchNotAcceptedContacts() {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
|||
static const ErrorCode InvalidSignedPreKey = ErrorCode._(1027, _omitEnumNames ? '' : 'InvalidSignedPreKey');
|
||||
static const ErrorCode UserIdNotFound = ErrorCode._(1028, _omitEnumNames ? '' : 'UserIdNotFound');
|
||||
static const ErrorCode UserIdAlreadyTaken = ErrorCode._(1029, _omitEnumNames ? '' : 'UserIdAlreadyTaken');
|
||||
static const ErrorCode AppVersionOutdated = ErrorCode._(1030, _omitEnumNames ? '' : 'AppVersionOutdated');
|
||||
|
||||
static const $core.List<ErrorCode> values = <ErrorCode> [
|
||||
Unknown,
|
||||
|
|
@ -80,6 +81,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
|||
InvalidSignedPreKey,
|
||||
UserIdNotFound,
|
||||
UserIdAlreadyTaken,
|
||||
AppVersionOutdated,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ const ErrorCode$json = {
|
|||
{'1': 'InvalidSignedPreKey', '2': 1027},
|
||||
{'1': 'UserIdNotFound', '2': 1028},
|
||||
{'1': 'UserIdAlreadyTaken', '2': 1029},
|
||||
{'1': 'AppVersionOutdated', '2': 1030},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -68,5 +69,5 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode(
|
|||
'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q'
|
||||
'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIIIEhgKE0ludmFsaWRTaW'
|
||||
'duZWRQcmVLZXkQgwgSEwoOVXNlcklkTm90Rm91bmQQhAgSFwoSVXNlcklkQWxyZWFkeVRha2Vu'
|
||||
'EIUI');
|
||||
'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCA==');
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,415 @@
|
|||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: push_notification.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'push_notification.pbenum.dart';
|
||||
|
||||
export 'push_notification.pbenum.dart';
|
||||
|
||||
class EncryptedPushNotification extends $pb.GeneratedMessage {
|
||||
factory EncryptedPushNotification({
|
||||
$fixnum.Int64? keyId,
|
||||
$core.List<$core.int>? nonce,
|
||||
$core.List<$core.int>? ciphertext,
|
||||
$core.List<$core.int>? mac,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (keyId != null) {
|
||||
$result.keyId = keyId;
|
||||
}
|
||||
if (nonce != null) {
|
||||
$result.nonce = nonce;
|
||||
}
|
||||
if (ciphertext != null) {
|
||||
$result.ciphertext = ciphertext;
|
||||
}
|
||||
if (mac != null) {
|
||||
$result.mac = mac;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
EncryptedPushNotification._() : super();
|
||||
factory EncryptedPushNotification.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory EncryptedPushNotification.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EncryptedPushNotification', createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'keyId', protoName: 'keyId')
|
||||
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'nonce', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'ciphertext', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'mac', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
EncryptedPushNotification clone() => EncryptedPushNotification()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
EncryptedPushNotification copyWith(void Function(EncryptedPushNotification) updates) => super.copyWith((message) => updates(message as EncryptedPushNotification)) as EncryptedPushNotification;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static EncryptedPushNotification create() => EncryptedPushNotification._();
|
||||
EncryptedPushNotification createEmptyInstance() => create();
|
||||
static $pb.PbList<EncryptedPushNotification> createRepeated() => $pb.PbList<EncryptedPushNotification>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static EncryptedPushNotification getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EncryptedPushNotification>(create);
|
||||
static EncryptedPushNotification? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get keyId => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set keyId($fixnum.Int64 v) { $_setInt64(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasKeyId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearKeyId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get nonce => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set nonce($core.List<$core.int> v) { $_setBytes(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasNonce() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearNonce() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get ciphertext => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set ciphertext($core.List<$core.int> v) { $_setBytes(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasCiphertext() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearCiphertext() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get mac => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set mac($core.List<$core.int> v) { $_setBytes(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasMac() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearMac() => clearField(4);
|
||||
}
|
||||
|
||||
class PushNotification extends $pb.GeneratedMessage {
|
||||
factory PushNotification({
|
||||
PushKind? kind,
|
||||
$fixnum.Int64? messageId,
|
||||
$core.String? reactionContent,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (kind != null) {
|
||||
$result.kind = kind;
|
||||
}
|
||||
if (messageId != null) {
|
||||
$result.messageId = messageId;
|
||||
}
|
||||
if (reactionContent != null) {
|
||||
$result.reactionContent = reactionContent;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
PushNotification._() : super();
|
||||
factory PushNotification.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory PushNotification.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PushNotification', createEmptyInstance: create)
|
||||
..e<PushKind>(1, _omitFieldNames ? '' : 'kind', $pb.PbFieldType.OE, defaultOrMaker: PushKind.reaction, valueOf: PushKind.valueOf, enumValues: PushKind.values)
|
||||
..aInt64(2, _omitFieldNames ? '' : 'messageId', protoName: 'messageId')
|
||||
..aOS(3, _omitFieldNames ? '' : 'reactionContent', protoName: 'reactionContent')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
PushNotification clone() => PushNotification()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
PushNotification copyWith(void Function(PushNotification) updates) => super.copyWith((message) => updates(message as PushNotification)) as PushNotification;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PushNotification create() => PushNotification._();
|
||||
PushNotification createEmptyInstance() => create();
|
||||
static $pb.PbList<PushNotification> createRepeated() => $pb.PbList<PushNotification>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PushNotification getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PushNotification>(create);
|
||||
static PushNotification? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
PushKind get kind => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set kind(PushKind v) { setField(1, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasKind() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearKind() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$fixnum.Int64 get messageId => $_getI64(1);
|
||||
@$pb.TagNumber(2)
|
||||
set messageId($fixnum.Int64 v) { $_setInt64(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasMessageId() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearMessageId() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get reactionContent => $_getSZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set reactionContent($core.String v) { $_setString(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasReactionContent() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearReactionContent() => clearField(3);
|
||||
}
|
||||
|
||||
class PushUsers extends $pb.GeneratedMessage {
|
||||
factory PushUsers({
|
||||
$core.Iterable<PushUser>? users,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (users != null) {
|
||||
$result.users.addAll(users);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
PushUsers._() : super();
|
||||
factory PushUsers.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory PushUsers.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PushUsers', createEmptyInstance: create)
|
||||
..pc<PushUser>(1, _omitFieldNames ? '' : 'users', $pb.PbFieldType.PM, subBuilder: PushUser.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
PushUsers clone() => PushUsers()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
PushUsers copyWith(void Function(PushUsers) updates) => super.copyWith((message) => updates(message as PushUsers)) as PushUsers;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PushUsers create() => PushUsers._();
|
||||
PushUsers createEmptyInstance() => create();
|
||||
static $pb.PbList<PushUsers> createRepeated() => $pb.PbList<PushUsers>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PushUsers getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PushUsers>(create);
|
||||
static PushUsers? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<PushUser> get users => $_getList(0);
|
||||
}
|
||||
|
||||
class PushUser extends $pb.GeneratedMessage {
|
||||
factory PushUser({
|
||||
$fixnum.Int64? userId,
|
||||
$core.String? displayName,
|
||||
$core.bool? blocked,
|
||||
$fixnum.Int64? lastMessageId,
|
||||
$core.Iterable<PushKey>? pushKeys,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (userId != null) {
|
||||
$result.userId = userId;
|
||||
}
|
||||
if (displayName != null) {
|
||||
$result.displayName = displayName;
|
||||
}
|
||||
if (blocked != null) {
|
||||
$result.blocked = blocked;
|
||||
}
|
||||
if (lastMessageId != null) {
|
||||
$result.lastMessageId = lastMessageId;
|
||||
}
|
||||
if (pushKeys != null) {
|
||||
$result.pushKeys.addAll(pushKeys);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
PushUser._() : super();
|
||||
factory PushUser.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory PushUser.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PushUser', createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'userId', protoName: 'userId')
|
||||
..aOS(2, _omitFieldNames ? '' : 'displayName', protoName: 'displayName')
|
||||
..aOB(3, _omitFieldNames ? '' : 'blocked')
|
||||
..aInt64(4, _omitFieldNames ? '' : 'lastMessageId', protoName: 'lastMessageId')
|
||||
..pc<PushKey>(5, _omitFieldNames ? '' : 'pushKeys', $pb.PbFieldType.PM, protoName: 'pushKeys', subBuilder: PushKey.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
PushUser clone() => PushUser()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
PushUser copyWith(void Function(PushUser) updates) => super.copyWith((message) => updates(message as PushUser)) as PushUser;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PushUser create() => PushUser._();
|
||||
PushUser createEmptyInstance() => create();
|
||||
static $pb.PbList<PushUser> createRepeated() => $pb.PbList<PushUser>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PushUser getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PushUser>(create);
|
||||
static PushUser? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get userId => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set userId($fixnum.Int64 v) { $_setInt64(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasUserId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearUserId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get displayName => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set displayName($core.String v) { $_setString(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasDisplayName() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearDisplayName() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool get blocked => $_getBF(2);
|
||||
@$pb.TagNumber(3)
|
||||
set blocked($core.bool v) { $_setBool(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasBlocked() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearBlocked() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$fixnum.Int64 get lastMessageId => $_getI64(3);
|
||||
@$pb.TagNumber(4)
|
||||
set lastMessageId($fixnum.Int64 v) { $_setInt64(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasLastMessageId() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearLastMessageId() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.List<PushKey> get pushKeys => $_getList(4);
|
||||
}
|
||||
|
||||
class PushKey extends $pb.GeneratedMessage {
|
||||
factory PushKey({
|
||||
$fixnum.Int64? id,
|
||||
$core.List<$core.int>? key,
|
||||
$fixnum.Int64? createdAtUnixTimestamp,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (id != null) {
|
||||
$result.id = id;
|
||||
}
|
||||
if (key != null) {
|
||||
$result.key = key;
|
||||
}
|
||||
if (createdAtUnixTimestamp != null) {
|
||||
$result.createdAtUnixTimestamp = createdAtUnixTimestamp;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
PushKey._() : super();
|
||||
factory PushKey.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory PushKey.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'PushKey', createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'id')
|
||||
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'key', $pb.PbFieldType.OY)
|
||||
..aInt64(3, _omitFieldNames ? '' : 'createdAtUnixTimestamp', protoName: 'createdAtUnixTimestamp')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
PushKey clone() => PushKey()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
PushKey copyWith(void Function(PushKey) updates) => super.copyWith((message) => updates(message as PushKey)) as PushKey;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PushKey create() => PushKey._();
|
||||
PushKey createEmptyInstance() => create();
|
||||
static $pb.PbList<PushKey> createRepeated() => $pb.PbList<PushKey>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static PushKey getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<PushKey>(create);
|
||||
static PushKey? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get id => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set id($fixnum.Int64 v) { $_setInt64(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearId() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get key => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set key($core.List<$core.int> v) { $_setBytes(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasKey() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearKey() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$fixnum.Int64 get createdAtUnixTimestamp => $_getI64(2);
|
||||
@$pb.TagNumber(3)
|
||||
set createdAtUnixTimestamp($fixnum.Int64 v) { $_setInt64(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasCreatedAtUnixTimestamp() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearCreatedAtUnixTimestamp() => clearField(3);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: push_notification.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class PushKind extends $pb.ProtobufEnum {
|
||||
static const PushKind reaction = PushKind._(0, _omitEnumNames ? '' : 'reaction');
|
||||
static const PushKind response = PushKind._(1, _omitEnumNames ? '' : 'response');
|
||||
static const PushKind text = PushKind._(2, _omitEnumNames ? '' : 'text');
|
||||
static const PushKind video = PushKind._(3, _omitEnumNames ? '' : 'video');
|
||||
static const PushKind twonly = PushKind._(4, _omitEnumNames ? '' : 'twonly');
|
||||
static const PushKind image = PushKind._(5, _omitEnumNames ? '' : 'image');
|
||||
static const PushKind contactRequest = PushKind._(6, _omitEnumNames ? '' : 'contactRequest');
|
||||
static const PushKind acceptRequest = PushKind._(7, _omitEnumNames ? '' : 'acceptRequest');
|
||||
static const PushKind storedMediaFile = PushKind._(8, _omitEnumNames ? '' : 'storedMediaFile');
|
||||
static const PushKind testNotification = PushKind._(9, _omitEnumNames ? '' : 'testNotification');
|
||||
static const PushKind reopenedMedia = PushKind._(10, _omitEnumNames ? '' : 'reopenedMedia');
|
||||
static const PushKind reactionToVideo = PushKind._(11, _omitEnumNames ? '' : 'reactionToVideo');
|
||||
static const PushKind reactionToText = PushKind._(12, _omitEnumNames ? '' : 'reactionToText');
|
||||
static const PushKind reactionToImage = PushKind._(13, _omitEnumNames ? '' : 'reactionToImage');
|
||||
|
||||
static const $core.List<PushKind> values = <PushKind> [
|
||||
reaction,
|
||||
response,
|
||||
text,
|
||||
video,
|
||||
twonly,
|
||||
image,
|
||||
contactRequest,
|
||||
acceptRequest,
|
||||
storedMediaFile,
|
||||
testNotification,
|
||||
reopenedMedia,
|
||||
reactionToVideo,
|
||||
reactionToText,
|
||||
reactionToImage,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, PushKind> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static PushKind? valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const PushKind._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
|
||||
const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: push_notification.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
|
||||
@$core.Deprecated('Use pushKindDescriptor instead')
|
||||
const PushKind$json = {
|
||||
'1': 'PushKind',
|
||||
'2': [
|
||||
{'1': 'reaction', '2': 0},
|
||||
{'1': 'response', '2': 1},
|
||||
{'1': 'text', '2': 2},
|
||||
{'1': 'video', '2': 3},
|
||||
{'1': 'twonly', '2': 4},
|
||||
{'1': 'image', '2': 5},
|
||||
{'1': 'contactRequest', '2': 6},
|
||||
{'1': 'acceptRequest', '2': 7},
|
||||
{'1': 'storedMediaFile', '2': 8},
|
||||
{'1': 'testNotification', '2': 9},
|
||||
{'1': 'reopenedMedia', '2': 10},
|
||||
{'1': 'reactionToVideo', '2': 11},
|
||||
{'1': 'reactionToText', '2': 12},
|
||||
{'1': 'reactionToImage', '2': 13},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PushKind`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List pushKindDescriptor = $convert.base64Decode(
|
||||
'CghQdXNoS2luZBIMCghyZWFjdGlvbhAAEgwKCHJlc3BvbnNlEAESCAoEdGV4dBACEgkKBXZpZG'
|
||||
'VvEAMSCgoGdHdvbmx5EAQSCQoFaW1hZ2UQBRISCg5jb250YWN0UmVxdWVzdBAGEhEKDWFjY2Vw'
|
||||
'dFJlcXVlc3QQBxITCg9zdG9yZWRNZWRpYUZpbGUQCBIUChB0ZXN0Tm90aWZpY2F0aW9uEAkSEQ'
|
||||
'oNcmVvcGVuZWRNZWRpYRAKEhMKD3JlYWN0aW9uVG9WaWRlbxALEhIKDnJlYWN0aW9uVG9UZXh0'
|
||||
'EAwSEwoPcmVhY3Rpb25Ub0ltYWdlEA0=');
|
||||
|
||||
@$core.Deprecated('Use encryptedPushNotificationDescriptor instead')
|
||||
const EncryptedPushNotification$json = {
|
||||
'1': 'EncryptedPushNotification',
|
||||
'2': [
|
||||
{'1': 'keyId', '3': 1, '4': 1, '5': 3, '10': 'keyId'},
|
||||
{'1': 'nonce', '3': 2, '4': 1, '5': 12, '10': 'nonce'},
|
||||
{'1': 'ciphertext', '3': 3, '4': 1, '5': 12, '10': 'ciphertext'},
|
||||
{'1': 'mac', '3': 4, '4': 1, '5': 12, '10': 'mac'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `EncryptedPushNotification`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List encryptedPushNotificationDescriptor = $convert.base64Decode(
|
||||
'ChlFbmNyeXB0ZWRQdXNoTm90aWZpY2F0aW9uEhQKBWtleUlkGAEgASgDUgVrZXlJZBIUCgVub2'
|
||||
'5jZRgCIAEoDFIFbm9uY2USHgoKY2lwaGVydGV4dBgDIAEoDFIKY2lwaGVydGV4dBIQCgNtYWMY'
|
||||
'BCABKAxSA21hYw==');
|
||||
|
||||
@$core.Deprecated('Use pushNotificationDescriptor instead')
|
||||
const PushNotification$json = {
|
||||
'1': 'PushNotification',
|
||||
'2': [
|
||||
{'1': 'kind', '3': 1, '4': 1, '5': 14, '6': '.PushKind', '10': 'kind'},
|
||||
{'1': 'messageId', '3': 2, '4': 1, '5': 3, '9': 0, '10': 'messageId', '17': true},
|
||||
{'1': 'reactionContent', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'reactionContent', '17': true},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_messageId'},
|
||||
{'1': '_reactionContent'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PushNotification`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List pushNotificationDescriptor = $convert.base64Decode(
|
||||
'ChBQdXNoTm90aWZpY2F0aW9uEh0KBGtpbmQYASABKA4yCS5QdXNoS2luZFIEa2luZBIhCgltZX'
|
||||
'NzYWdlSWQYAiABKANIAFIJbWVzc2FnZUlkiAEBEi0KD3JlYWN0aW9uQ29udGVudBgDIAEoCUgB'
|
||||
'Ug9yZWFjdGlvbkNvbnRlbnSIAQFCDAoKX21lc3NhZ2VJZEISChBfcmVhY3Rpb25Db250ZW50');
|
||||
|
||||
@$core.Deprecated('Use pushUsersDescriptor instead')
|
||||
const PushUsers$json = {
|
||||
'1': 'PushUsers',
|
||||
'2': [
|
||||
{'1': 'users', '3': 1, '4': 3, '5': 11, '6': '.PushUser', '10': 'users'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PushUsers`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List pushUsersDescriptor = $convert.base64Decode(
|
||||
'CglQdXNoVXNlcnMSHwoFdXNlcnMYASADKAsyCS5QdXNoVXNlclIFdXNlcnM=');
|
||||
|
||||
@$core.Deprecated('Use pushUserDescriptor instead')
|
||||
const PushUser$json = {
|
||||
'1': 'PushUser',
|
||||
'2': [
|
||||
{'1': 'userId', '3': 1, '4': 1, '5': 3, '10': 'userId'},
|
||||
{'1': 'displayName', '3': 2, '4': 1, '5': 9, '10': 'displayName'},
|
||||
{'1': 'blocked', '3': 3, '4': 1, '5': 8, '10': 'blocked'},
|
||||
{'1': 'lastMessageId', '3': 4, '4': 1, '5': 3, '9': 0, '10': 'lastMessageId', '17': true},
|
||||
{'1': 'pushKeys', '3': 5, '4': 3, '5': 11, '6': '.PushKey', '10': 'pushKeys'},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_lastMessageId'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PushUser`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List pushUserDescriptor = $convert.base64Decode(
|
||||
'CghQdXNoVXNlchIWCgZ1c2VySWQYASABKANSBnVzZXJJZBIgCgtkaXNwbGF5TmFtZRgCIAEoCV'
|
||||
'ILZGlzcGxheU5hbWUSGAoHYmxvY2tlZBgDIAEoCFIHYmxvY2tlZBIpCg1sYXN0TWVzc2FnZUlk'
|
||||
'GAQgASgDSABSDWxhc3RNZXNzYWdlSWSIAQESJAoIcHVzaEtleXMYBSADKAsyCC5QdXNoS2V5Ug'
|
||||
'hwdXNoS2V5c0IQCg5fbGFzdE1lc3NhZ2VJZA==');
|
||||
|
||||
@$core.Deprecated('Use pushKeyDescriptor instead')
|
||||
const PushKey$json = {
|
||||
'1': 'PushKey',
|
||||
'2': [
|
||||
{'1': 'id', '3': 1, '4': 1, '5': 3, '10': 'id'},
|
||||
{'1': 'key', '3': 2, '4': 1, '5': 12, '10': 'key'},
|
||||
{'1': 'createdAtUnixTimestamp', '3': 3, '4': 1, '5': 3, '10': 'createdAtUnixTimestamp'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `PushKey`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List pushKeyDescriptor = $convert.base64Decode(
|
||||
'CgdQdXNoS2V5Eg4KAmlkGAEgASgDUgJpZBIQCgNrZXkYAiABKAxSA2tleRI2ChZjcmVhdGVkQX'
|
||||
'RVbml4VGltZXN0YW1wGAMgASgDUhZjcmVhdGVkQXRVbml4VGltZXN0YW1w');
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: push_notification.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
export 'push_notification.pb.dart';
|
||||
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
syntax = "proto3";
|
||||
|
||||
message EncryptedPushNotification {
|
||||
int64 keyId = 1;
|
||||
bytes nonce = 2;
|
||||
bytes ciphertext = 3;
|
||||
bytes mac = 4;
|
||||
}
|
||||
|
||||
enum PushKind {
|
||||
reaction = 0;
|
||||
response = 1;
|
||||
text = 2;
|
||||
video = 3;
|
||||
twonly = 4;
|
||||
image = 5;
|
||||
contactRequest = 6;
|
||||
acceptRequest = 7;
|
||||
storedMediaFile = 8;
|
||||
testNotification = 9;
|
||||
reopenedMedia = 10;
|
||||
reactionToVideo = 11;
|
||||
reactionToText = 12;
|
||||
reactionToImage = 13;
|
||||
};
|
||||
|
||||
message PushNotification {
|
||||
PushKind kind = 1;
|
||||
optional int64 messageId = 2;
|
||||
optional string reactionContent = 3;
|
||||
}
|
||||
|
||||
|
||||
message PushUsers {
|
||||
repeated PushUser users = 1;
|
||||
}
|
||||
|
||||
message PushUser {
|
||||
int64 userId = 1;
|
||||
string displayName = 2;
|
||||
bool blocked = 3;
|
||||
optional int64 lastMessageId = 4;
|
||||
repeated PushKey pushKeys = 5;
|
||||
}
|
||||
|
||||
message PushKey {
|
||||
int64 id = 1;
|
||||
bytes key = 2;
|
||||
int64 createdAtUnixTimestamp = 3;
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import 'package:twonly/src/services/api/utils.dart';
|
|||
import 'package:twonly/src/services/api/media_download.dart';
|
||||
import 'package:twonly/src/services/api/media_upload.dart';
|
||||
import 'package:twonly/src/services/api/server_messages.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||
import 'package:twonly/src/services/signal/prekeys.signal.dart';
|
||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||
|
|
@ -92,6 +93,7 @@ class ApiService {
|
|||
notifyContactsAboutProfileChange();
|
||||
twonlyDB.markUpdated();
|
||||
syncFlameCounters();
|
||||
setupNotificationWithUsers();
|
||||
signalHandleNewServerConnection();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ import 'package:twonly/src/database/twonly_database.dart';
|
|||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
||||
import 'package:twonly/src/services/api/media_download.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
|
@ -539,17 +540,24 @@ Future handleMediaUpload(MediaUpload media) async {
|
|||
);
|
||||
|
||||
if (encryptedBytes == null) continue;
|
||||
|
||||
var messageOnSuccess = TextMessage()
|
||||
..body = encryptedBytes
|
||||
..userId = Int64(message.contactId);
|
||||
|
||||
final pushKind = (media.metadata!.isRealTwonly)
|
||||
? PushKind.twonly
|
||||
: (media.metadata!.isVideo)
|
||||
? PushKind.video
|
||||
: PushKind.image;
|
||||
|
||||
var messageOnSuccess = TextMessage()
|
||||
..body = encryptedBytes
|
||||
..userId = Int64(message.contactId);
|
||||
|
||||
var pushData = await getPushData(message.contactId, pushKind);
|
||||
final pushData = await getPushData(
|
||||
message.contactId,
|
||||
PushNotification(
|
||||
messageId: Int64(message.messageId),
|
||||
kind: pushKind,
|
||||
),
|
||||
);
|
||||
if (pushData != null) {
|
||||
messageOnSuccess.pushData = pushData.toList();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/model/json/userdata.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart';
|
||||
import 'package:twonly/src/services/api/utils.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
|
|
@ -116,14 +118,15 @@ Future sendRetransmitMessage(int retransId) async {
|
|||
|
||||
// encrypts and stores the message and then sends it in the background
|
||||
Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg,
|
||||
{PushKind? pushKind, bool willNotGetACKByUser = false}) async {
|
||||
{PushNotification? pushNotification,
|
||||
bool willNotGetACKByUser = false}) async {
|
||||
if (gIsDemoUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
Uint8List? pushData;
|
||||
if (pushKind != null) {
|
||||
pushData = await getPushData(userId, pushKind);
|
||||
if (pushNotification != null) {
|
||||
pushData = await getPushData(userId, pushNotification);
|
||||
}
|
||||
|
||||
int? retransId = await twonlyDB.messageRetransmissionDao.insertRetransmission(
|
||||
|
|
@ -158,7 +161,7 @@ Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg,
|
|||
Future sendTextMessage(
|
||||
int target,
|
||||
TextMessageContent content,
|
||||
PushKind? pushKind,
|
||||
PushNotification? pushNotification,
|
||||
) async {
|
||||
DateTime messageSendAt = DateTime.now();
|
||||
|
||||
|
|
@ -178,6 +181,10 @@ Future sendTextMessage(
|
|||
|
||||
if (messageId == null) return;
|
||||
|
||||
if (pushNotification != null && !pushNotification.hasReactionContent()) {
|
||||
pushNotification.messageId = Int64(messageId);
|
||||
}
|
||||
|
||||
MessageJson msg = MessageJson(
|
||||
kind: MessageKind.textMessage,
|
||||
messageId: messageId,
|
||||
|
|
@ -185,14 +192,22 @@ Future sendTextMessage(
|
|||
timestamp: messageSendAt,
|
||||
);
|
||||
|
||||
await encryptAndSendMessageAsync(messageId, target, msg, pushKind: pushKind);
|
||||
await encryptAndSendMessageAsync(
|
||||
messageId,
|
||||
target,
|
||||
msg,
|
||||
pushNotification: pushNotification,
|
||||
);
|
||||
}
|
||||
|
||||
Future notifyContactAboutOpeningMessage(
|
||||
int fromUserId,
|
||||
List<int> messageOtherIds,
|
||||
) async {
|
||||
int biggestMessageId = messageOtherIds.first;
|
||||
|
||||
for (final messageOtherId in messageOtherIds) {
|
||||
if (messageOtherId > biggestMessageId) biggestMessageId = messageOtherId;
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
fromUserId,
|
||||
|
|
@ -204,6 +219,7 @@ Future notifyContactAboutOpeningMessage(
|
|||
),
|
||||
);
|
||||
}
|
||||
await updateLastMessageId(fromUserId, biggestMessageId);
|
||||
}
|
||||
|
||||
Future notifyContactsAboutProfileChange() async {
|
||||
|
|
@ -216,8 +232,12 @@ Future notifyContactsAboutProfileChange() async {
|
|||
|
||||
for (Contact contact in contacts) {
|
||||
if (contact.myAvatarCounter < user.avatarCounter) {
|
||||
twonlyDB.contactsDao.updateContact(contact.userId,
|
||||
ContactsCompanion(myAvatarCounter: Value(user.avatarCounter)));
|
||||
twonlyDB.contactsDao.updateContact(
|
||||
contact.userId,
|
||||
ContactsCompanion(
|
||||
myAvatarCounter: Value(user.avatarCounter),
|
||||
),
|
||||
);
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
contact.userId,
|
||||
|
|
|
|||
|
|
@ -9,19 +9,21 @@ import 'package:twonly/src/database/tables/messages_table.dart';
|
|||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart'
|
||||
as client;
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pbserver.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart';
|
||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||
as server;
|
||||
import 'package:twonly/src/services/api/messages.dart';
|
||||
import 'package:twonly/src/services/api/utils.dart';
|
||||
import 'package:twonly/src/services/api/media_download.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
||||
import 'package:twonly/src/services/signal/encryption.signal.dart';
|
||||
import 'package:twonly/src/services/signal/identity.signal.dart';
|
||||
import 'package:twonly/src/services/signal/prekeys.signal.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||
|
||||
final lockHandleServerMessage = Mutex();
|
||||
|
||||
|
|
@ -272,6 +274,14 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
|||
if (content is TextMessageContent) {
|
||||
responseToMessageId = content.responseToMessageId;
|
||||
responseToOtherMessageId = content.responseToOtherMessageId;
|
||||
|
||||
if (responseToMessageId != null ||
|
||||
responseToOtherMessageId != null) {
|
||||
// reactions are shown in the notification directly...
|
||||
if (isEmoji(content.text)) {
|
||||
openedAt = DateTime.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (content is ReopenedMediaFileContent) {
|
||||
responseToMessageId = content.messageId;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:firebase_messaging/firebase_messaging.dart';
|
|||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||
import 'package:twonly/src/services/notification.background.service.dart';
|
||||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import '../../firebase_options.dart';
|
||||
|
|
|
|||
|
|
@ -1,422 +0,0 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/model/json/message.dart' as my;
|
||||
import 'package:twonly/src/services/api/messages.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
class PushUser {
|
||||
String displayName;
|
||||
bool blocked;
|
||||
List<PushKeyMeta> keys;
|
||||
|
||||
PushUser({
|
||||
required this.displayName,
|
||||
required this.blocked,
|
||||
required this.keys,
|
||||
});
|
||||
|
||||
// Factory method to create a User from JSON
|
||||
factory PushUser.fromJson(Map<String, dynamic> json) {
|
||||
return PushUser(
|
||||
displayName: json['displayName'],
|
||||
blocked: json['blocked'] ?? false,
|
||||
keys: (json['keys'] as List)
|
||||
.map((keyJson) => PushKeyMeta.fromJson(keyJson))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
// Method to convert User to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'displayName': displayName,
|
||||
'blocked': blocked,
|
||||
'keys': keys.map((key) => key.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class PushKeyMeta {
|
||||
int id;
|
||||
List<int> key;
|
||||
DateTime createdAt;
|
||||
|
||||
PushKeyMeta({
|
||||
required this.id,
|
||||
required this.key,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
// Factory method to create Keys from JSON
|
||||
factory PushKeyMeta.fromJson(Map<String, dynamic> json) {
|
||||
return PushKeyMeta(
|
||||
id: json['id'],
|
||||
key: List<int>.from(json['key']),
|
||||
createdAt: DateTime.fromMillisecondsSinceEpoch(json['createdAt']),
|
||||
);
|
||||
}
|
||||
|
||||
// Method to convert Keys to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'key': key,
|
||||
'createdAt': createdAt.millisecondsSinceEpoch, // Store as timestamp
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// This function must be called after the database is setup
|
||||
Future setupNotificationWithUsers({bool force = false}) async {
|
||||
var pushKeys = await getPushKeys("receivingPushKeys");
|
||||
|
||||
var wasChanged = false;
|
||||
|
||||
final random = Random.secure();
|
||||
|
||||
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
||||
for (final contact in contacts) {
|
||||
if (pushKeys.containsKey(contact.userId)) {
|
||||
// make it harder to predict the change of the key
|
||||
final timeBefore =
|
||||
DateTime.now().subtract(Duration(days: 5 + random.nextInt(5)));
|
||||
final lastKey = pushKeys[contact.userId]!.keys.last;
|
||||
if (force || lastKey.createdAt.isBefore(timeBefore)) {
|
||||
final pushKey = PushKeyMeta(
|
||||
id: lastKey.id + 1,
|
||||
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
await sendNewPushKey(contact.userId, pushKey);
|
||||
pushKeys[contact.userId]!.keys.add(pushKey);
|
||||
pushKeys[contact.userId]!.displayName = getContactDisplayName(contact);
|
||||
wasChanged = true;
|
||||
}
|
||||
} else {
|
||||
/// Insert a new pushuser
|
||||
final pushKey = PushKeyMeta(
|
||||
id: 1,
|
||||
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
await sendNewPushKey(contact.userId, pushKey);
|
||||
final pushUser = PushUser(
|
||||
displayName: getContactDisplayName(contact),
|
||||
blocked: contact.blocked,
|
||||
keys: [pushKey],
|
||||
);
|
||||
pushKeys[contact.userId] = pushUser;
|
||||
wasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (wasChanged) {
|
||||
await setPushKeys("receivingPushKeys", pushKeys);
|
||||
}
|
||||
}
|
||||
|
||||
Future sendNewPushKey(int userId, PushKeyMeta pushKey) async {
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
userId,
|
||||
my.MessageJson(
|
||||
kind: MessageKind.pushKey,
|
||||
content: my.PushKeyContent(keyId: pushKey.id, key: pushKey.key),
|
||||
timestamp: pushKey.createdAt,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future updatePushUser(Contact contact) async {
|
||||
var receivingPushKeys = await getPushKeys("receivingPushKeys");
|
||||
|
||||
if (receivingPushKeys[contact.userId] == null) {
|
||||
receivingPushKeys[contact.userId] = PushUser(
|
||||
displayName: getContactDisplayName(contact),
|
||||
keys: [],
|
||||
blocked: contact.blocked,
|
||||
);
|
||||
} else {
|
||||
receivingPushKeys[contact.userId]!.displayName =
|
||||
getContactDisplayName(contact);
|
||||
receivingPushKeys[contact.userId]!.blocked = contact.blocked;
|
||||
}
|
||||
|
||||
await setPushKeys("receivingPushKeys", receivingPushKeys);
|
||||
}
|
||||
|
||||
Future handleNewPushKey(int fromUserId, my.PushKeyContent pushKey) async {
|
||||
var pushKeys = await getPushKeys("sendingPushKeys");
|
||||
|
||||
if (pushKeys[fromUserId] == null) {
|
||||
pushKeys[fromUserId] = PushUser(displayName: "-", keys: [], blocked: false);
|
||||
}
|
||||
|
||||
// only store the newest key...
|
||||
pushKeys[fromUserId]!.keys = [
|
||||
PushKeyMeta(
|
||||
id: pushKey.keyId,
|
||||
key: pushKey.key,
|
||||
createdAt: DateTime.now(),
|
||||
),
|
||||
];
|
||||
|
||||
await setPushKeys("sendingPushKeys", pushKeys);
|
||||
}
|
||||
|
||||
enum PushKind {
|
||||
reaction,
|
||||
text,
|
||||
video,
|
||||
twonly,
|
||||
image,
|
||||
contactRequest,
|
||||
acceptRequest,
|
||||
storedMediaFile,
|
||||
testNotification,
|
||||
reopenedMedia,
|
||||
reactionToVideo,
|
||||
reactionToText,
|
||||
reactionToImage,
|
||||
response,
|
||||
}
|
||||
|
||||
extension PushKindExtension on PushKind {
|
||||
String get name => toString().split('.').last;
|
||||
|
||||
static PushKind fromString(String name) {
|
||||
return PushKind.values.firstWhere((e) => e.name == name);
|
||||
}
|
||||
}
|
||||
|
||||
class PushNotification {
|
||||
final int keyId;
|
||||
final List<int> nonce;
|
||||
final List<int> cipherText;
|
||||
final List<int> mac;
|
||||
|
||||
PushNotification({
|
||||
required this.keyId,
|
||||
required this.nonce,
|
||||
required this.cipherText,
|
||||
required this.mac,
|
||||
});
|
||||
|
||||
// Convert a PushNotification instance to a Map
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'keyId': keyId,
|
||||
'nonce': base64Encode(nonce),
|
||||
'cipherText': base64Encode(cipherText),
|
||||
'mac': base64Encode(mac),
|
||||
};
|
||||
}
|
||||
|
||||
// Create a PushNotification instance from a Map
|
||||
factory PushNotification.fromJson(Map<String, dynamic> json) {
|
||||
return PushNotification(
|
||||
keyId: json['keyId'],
|
||||
nonce: base64Decode(json['nonce']),
|
||||
cipherText: base64Decode(json['cipherText']),
|
||||
mac: base64Decode(json['mac']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// this will trigger a push notification
|
||||
/// push notification only containing the message kind and username
|
||||
Future<Uint8List?> getPushData(int toUserId, PushKind kind) async {
|
||||
final Map<int, PushUser> pushKeys = await getPushKeys("sendingPushKeys");
|
||||
|
||||
List<int> key = "InsecureOnlyUsedForAddingContact".codeUnits;
|
||||
int keyId = 0;
|
||||
|
||||
if (pushKeys[toUserId] == null) {
|
||||
// user does not have send any push keys
|
||||
// only allow accept request and contactrequest to be send in an insecure way :/
|
||||
// In future find a better way, e.g. use the signal protocol in a native way..
|
||||
if (kind != PushKind.acceptRequest &&
|
||||
kind != PushKind.contactRequest &&
|
||||
kind != PushKind.testNotification) {
|
||||
// this will be enforced after every app uses this system... :/
|
||||
// return null;
|
||||
Log.error("Using insecure key as the receiver does not send a push key!");
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
toUserId,
|
||||
my.MessageJson(
|
||||
kind: MessageKind.requestPushKey,
|
||||
content: my.MessageContent(),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
key = pushKeys[toUserId]!.keys.last.key;
|
||||
keyId = pushKeys[toUserId]!.keys.last.id;
|
||||
} catch (e) {
|
||||
Log.error("No push notification key found for user $toUserId");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final chacha20 = Chacha20.poly1305Aead();
|
||||
final nonce = chacha20.newNonce();
|
||||
final secretBox = await chacha20.encrypt(
|
||||
kind.name.codeUnits,
|
||||
secretKey: SecretKeyData(key),
|
||||
nonce: nonce,
|
||||
);
|
||||
final res = PushNotification(
|
||||
keyId: keyId,
|
||||
nonce: nonce,
|
||||
cipherText: secretBox.cipherText,
|
||||
mac: secretBox.mac.bytes,
|
||||
);
|
||||
return Utf8Encoder().convert(jsonEncode(res.toJson()));
|
||||
}
|
||||
|
||||
Future<Map<int, PushUser>> getPushKeys(String storageKey) async {
|
||||
var storage = FlutterSecureStorage();
|
||||
String? pushKeysJson = await storage.read(
|
||||
key: storageKey,
|
||||
iOptions: IOSOptions(
|
||||
groupId: "CN332ZUGRP.eu.twonly.shared",
|
||||
synchronizable: false,
|
||||
accessibility: KeychainAccessibility.first_unlock,
|
||||
),
|
||||
);
|
||||
Map<int, PushUser> pushKeys = <int, PushUser>{};
|
||||
if (pushKeysJson != null) {
|
||||
Map<String, dynamic> jsonMap = jsonDecode(pushKeysJson);
|
||||
jsonMap.forEach((key, value) {
|
||||
pushKeys[int.parse(key)] = PushUser.fromJson(value);
|
||||
});
|
||||
}
|
||||
return pushKeys;
|
||||
}
|
||||
|
||||
Future setPushKeys(String storageKey, Map<int, PushUser> pushKeys) async {
|
||||
var storage = FlutterSecureStorage();
|
||||
Map<String, dynamic> jsonToSend = {};
|
||||
pushKeys.forEach((key, value) {
|
||||
jsonToSend[key.toString()] = value.toJson();
|
||||
});
|
||||
|
||||
await storage.delete(
|
||||
key: storageKey,
|
||||
iOptions: IOSOptions(
|
||||
groupId: "CN332ZUGRP.eu.twonly.shared",
|
||||
synchronizable: false,
|
||||
accessibility: KeychainAccessibility.first_unlock,
|
||||
),
|
||||
);
|
||||
|
||||
String jsonString = jsonEncode(jsonToSend);
|
||||
await storage.write(
|
||||
key: storageKey,
|
||||
value: jsonString,
|
||||
iOptions: IOSOptions(
|
||||
groupId: "CN332ZUGRP.eu.twonly.shared",
|
||||
synchronizable: false,
|
||||
accessibility: KeychainAccessibility.first_unlock,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final StreamController<NotificationResponse> selectNotificationStream =
|
||||
StreamController<NotificationResponse>.broadcast();
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void notificationTapBackground(NotificationResponse notificationResponse) {
|
||||
// ignore: avoid_print
|
||||
print('notification(${notificationResponse.id}) action tapped: '
|
||||
'${notificationResponse.actionId} with'
|
||||
' payload: ${notificationResponse.payload}');
|
||||
if (notificationResponse.input?.isNotEmpty ?? false) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'notification action tapped with input: ${notificationResponse.input}');
|
||||
}
|
||||
}
|
||||
|
||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
int id = 0;
|
||||
|
||||
Future<void> setupPushNotification() async {
|
||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||
AndroidInitializationSettings("ic_launcher_foreground");
|
||||
|
||||
final List<DarwinNotificationCategory> darwinNotificationCategories =
|
||||
<DarwinNotificationCategory>[];
|
||||
|
||||
/// Note: permissions aren't requested here just to demonstrate that can be
|
||||
/// done later
|
||||
final DarwinInitializationSettings initializationSettingsDarwin =
|
||||
DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
requestProvisionalPermission: false,
|
||||
notificationCategories: darwinNotificationCategories,
|
||||
);
|
||||
|
||||
final InitializationSettings initializationSettings = InitializationSettings(
|
||||
android: initializationSettingsAndroid,
|
||||
iOS: initializationSettingsDarwin,
|
||||
);
|
||||
|
||||
await flutterLocalNotificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: selectNotificationStream.add,
|
||||
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
||||
);
|
||||
}
|
||||
|
||||
Future createPushAvatars() async {
|
||||
if (!Platform.isAndroid) {
|
||||
return; // avatars currently only shown in Android...
|
||||
}
|
||||
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
||||
|
||||
for (final contact in contacts) {
|
||||
if (contact.avatarSvg == null) return null;
|
||||
|
||||
final PictureInfo pictureInfo =
|
||||
await vg.loadPicture(SvgStringLoader(contact.avatarSvg!), null);
|
||||
|
||||
final ui.Image image = await pictureInfo.picture.toImage(300, 300);
|
||||
|
||||
final ByteData? byteData =
|
||||
await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
final Uint8List pngBytes = byteData!.buffer.asUint8List();
|
||||
|
||||
// Get the directory to save the image
|
||||
final directory = await getApplicationCacheDirectory();
|
||||
final avatarsDirectory = Directory('${directory.path}/avatars');
|
||||
|
||||
// Create the avatars directory if it does not exist
|
||||
if (!await avatarsDirectory.exists()) {
|
||||
await avatarsDirectory.create(recursive: true);
|
||||
}
|
||||
final filePath = '${avatarsDirectory.path}/${contact.userId}.png';
|
||||
await File(filePath).writeAsBytes(pngBytes);
|
||||
pictureInfo.picture.dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,9 @@ import 'dart:math';
|
|||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||
|
|
@ -24,7 +26,9 @@ Future customLocalPushNotification(String title, String msg) async {
|
|||
const DarwinNotificationDetails darwinNotificationDetails =
|
||||
DarwinNotificationDetails();
|
||||
const NotificationDetails notificationDetails = NotificationDetails(
|
||||
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
||||
android: androidNotificationDetails,
|
||||
iOS: darwinNotificationDetails,
|
||||
);
|
||||
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
999999 + Random.secure().nextInt(9999),
|
||||
|
|
@ -34,67 +38,79 @@ Future customLocalPushNotification(String title, String msg) async {
|
|||
);
|
||||
}
|
||||
|
||||
Future handlePushData(String pushDataJson) async {
|
||||
Future handlePushData(String pushDataB64) async {
|
||||
try {
|
||||
String jsonString = utf8.decode(base64.decode(pushDataJson));
|
||||
final pushData = PushNotification.fromJson(jsonDecode(jsonString));
|
||||
final pushData =
|
||||
EncryptedPushNotification.fromBuffer(base64.decode(pushDataB64));
|
||||
|
||||
PushKind? pushKind;
|
||||
PushUser? pushUser;
|
||||
int? fromUserId;
|
||||
PushNotification? pushNotification;
|
||||
PushUser? foundPushUser;
|
||||
|
||||
if (pushData.keyId == 0) {
|
||||
List<int> key = "InsecureOnlyUsedForAddingContact".codeUnits;
|
||||
pushKind = await tryDecryptMessage(key, pushData);
|
||||
pushNotification = await tryDecryptMessage(key, pushData);
|
||||
} else {
|
||||
var pushKeys = await getPushKeys("receivingPushKeys");
|
||||
for (final userId in pushKeys.keys) {
|
||||
for (final key in pushKeys[userId]!.keys) {
|
||||
final pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys);
|
||||
for (final pushUser in pushUsers) {
|
||||
for (final key in pushUser.pushKeys) {
|
||||
if (key.id == pushData.keyId) {
|
||||
pushKind = await tryDecryptMessage(key.key, pushData);
|
||||
if (pushKind != null) {
|
||||
pushUser = pushKeys[userId]!;
|
||||
fromUserId = userId;
|
||||
pushNotification = await tryDecryptMessage(key.key, pushData);
|
||||
if (pushNotification != null) {
|
||||
foundPushUser = pushUser;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// found correct key and user
|
||||
if (pushUser != null) break;
|
||||
if (foundPushUser != null) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pushKind != null) {
|
||||
if (pushKind == PushKind.testNotification) {
|
||||
if (pushNotification != null) {
|
||||
if (pushNotification.kind == PushKind.testNotification) {
|
||||
await customLocalPushNotification(
|
||||
"Test notification", "This is a test notification.");
|
||||
} else if (pushUser != null && fromUserId != null) {
|
||||
await showLocalPushNotification(pushUser, fromUserId, pushKind);
|
||||
"Test notification",
|
||||
"This is a test notification.",
|
||||
);
|
||||
} else if (foundPushUser != null) {
|
||||
if (pushNotification.hasMessageId()) {
|
||||
if (pushNotification.messageId <= foundPushUser.lastMessageId) {
|
||||
Log.info(
|
||||
"Got a push notification for a message which was already opened.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await showLocalPushNotification(foundPushUser, pushNotification);
|
||||
} else {
|
||||
await showLocalPushNotificationWithoutUserId(pushKind);
|
||||
await showLocalPushNotificationWithoutUserId(pushNotification);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
await customLocalPushNotification(
|
||||
"Du hast eine neue Nachricht.",
|
||||
"Öffne twonly um mehr zu erfahren.",
|
||||
);
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<PushKind?> tryDecryptMessage(
|
||||
List<int> key, PushNotification noti) async {
|
||||
Future<PushNotification?> tryDecryptMessage(
|
||||
List<int> key, EncryptedPushNotification push) async {
|
||||
try {
|
||||
final chacha20 = Chacha20.poly1305Aead();
|
||||
SecretKeyData secretKeyData = SecretKeyData(key);
|
||||
|
||||
SecretBox secretBox = SecretBox(
|
||||
noti.cipherText,
|
||||
nonce: noti.nonce,
|
||||
mac: Mac(noti.mac),
|
||||
push.ciphertext,
|
||||
nonce: push.nonce,
|
||||
mac: Mac(push.mac),
|
||||
);
|
||||
|
||||
final plaintext =
|
||||
await chacha20.decrypt(secretBox, secretKey: secretKeyData);
|
||||
final plaintextString = utf8.decode(plaintext);
|
||||
return PushKindExtension.fromString(plaintextString);
|
||||
return PushNotification.fromBuffer(plaintext);
|
||||
} catch (e) {
|
||||
// this error is allowed to happen...
|
||||
return null;
|
||||
|
|
@ -103,8 +119,7 @@ Future<PushKind?> tryDecryptMessage(
|
|||
|
||||
Future showLocalPushNotification(
|
||||
PushUser pushUser,
|
||||
int fromUserId,
|
||||
PushKind pushKind,
|
||||
PushNotification pushNotification,
|
||||
) async {
|
||||
String? title;
|
||||
String? body;
|
||||
|
|
@ -116,56 +131,64 @@ Future showLocalPushNotification(
|
|||
}
|
||||
|
||||
title = pushUser.displayName;
|
||||
body = getPushNotificationText(pushKind);
|
||||
body = getPushNotificationText(pushNotification);
|
||||
if (body == "") {
|
||||
Log.error("No push notification type defined!");
|
||||
}
|
||||
|
||||
FilePathAndroidBitmap? styleInformation;
|
||||
String? avatarPath = await getAvatarIcon(fromUserId);
|
||||
String? avatarPath = await getAvatarIcon(pushUser.userId.toInt());
|
||||
if (avatarPath != null) {
|
||||
styleInformation = FilePathAndroidBitmap(avatarPath);
|
||||
}
|
||||
|
||||
AndroidNotificationDetails androidNotificationDetails =
|
||||
AndroidNotificationDetails('0', 'Messages',
|
||||
AndroidNotificationDetails(
|
||||
'0',
|
||||
'Messages',
|
||||
channelDescription: 'Messages from other users.',
|
||||
importance: Importance.max,
|
||||
priority: Priority.max,
|
||||
ticker: 'You got a new message.',
|
||||
largeIcon: styleInformation);
|
||||
largeIcon: styleInformation,
|
||||
);
|
||||
|
||||
const DarwinNotificationDetails darwinNotificationDetails =
|
||||
DarwinNotificationDetails();
|
||||
NotificationDetails notificationDetails = NotificationDetails(
|
||||
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
||||
android: androidNotificationDetails,
|
||||
iOS: darwinNotificationDetails,
|
||||
);
|
||||
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
fromUserId,
|
||||
pushUser.userId.toInt(),
|
||||
title,
|
||||
body,
|
||||
notificationDetails,
|
||||
payload: pushKind.name,
|
||||
payload: pushNotification.kind.name,
|
||||
);
|
||||
}
|
||||
|
||||
Future showLocalPushNotificationWithoutUserId(
|
||||
PushKind pushKind,
|
||||
PushNotification pushNotification,
|
||||
) async {
|
||||
String? title;
|
||||
String? body;
|
||||
|
||||
body = getPushNotificationTextWithoutUserId(pushKind);
|
||||
body = getPushNotificationTextWithoutUserId(pushNotification.kind);
|
||||
if (body == "") {
|
||||
Log.error("No push notification type defined!");
|
||||
}
|
||||
|
||||
AndroidNotificationDetails androidNotificationDetails =
|
||||
AndroidNotificationDetails('0', 'Messages',
|
||||
AndroidNotificationDetails(
|
||||
'0',
|
||||
'Messages',
|
||||
channelDescription: 'Messages from other users.',
|
||||
importance: Importance.max,
|
||||
priority: Priority.max,
|
||||
ticker: 'You got a new message.');
|
||||
ticker: 'You got a new message.',
|
||||
);
|
||||
|
||||
const DarwinNotificationDetails darwinNotificationDetails =
|
||||
DarwinNotificationDetails();
|
||||
|
|
@ -177,7 +200,7 @@ Future showLocalPushNotificationWithoutUserId(
|
|||
title,
|
||||
body,
|
||||
notificationDetails,
|
||||
payload: pushKind.name,
|
||||
payload: pushNotification.kind.name,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +263,7 @@ String getPushNotificationTextWithoutUserId(PushKind pushKind) {
|
|||
return pushNotificationText[pushKind.name] ?? "";
|
||||
}
|
||||
|
||||
String getPushNotificationText(PushKind pushKind) {
|
||||
String getPushNotificationText(PushNotification pushNotification) {
|
||||
String systemLanguage = Platform.localeName;
|
||||
|
||||
Map<String, String> pushNotificationText;
|
||||
|
|
@ -256,9 +279,12 @@ String getPushNotificationText(PushKind pushKind) {
|
|||
PushKind.storedMediaFile.name: "hat dein Bild gespeichert.",
|
||||
PushKind.reaction.name: "hat auf dein Bild reagiert.",
|
||||
PushKind.reopenedMedia.name: "hat dein Bild erneut geöffnet.",
|
||||
PushKind.reactionToVideo.name: "hat auf dein Video reagiert.",
|
||||
PushKind.reactionToText.name: "hat auf deinen Text reagiert.",
|
||||
PushKind.reactionToImage.name: "hat auf dein Bild reagiert.",
|
||||
PushKind.reactionToVideo.name:
|
||||
"hat mit {{reaction}} auf dein Video reagiert.",
|
||||
PushKind.reactionToText.name:
|
||||
"hat mit {{reaction}} auf deine Nachricht reagiert.",
|
||||
PushKind.reactionToImage.name:
|
||||
"hat mit {{reaction}} auf dein Bild reagiert.",
|
||||
PushKind.response.name: "hat dir geantwortet.",
|
||||
};
|
||||
} else {
|
||||
|
|
@ -272,11 +298,19 @@ String getPushNotificationText(PushKind pushKind) {
|
|||
PushKind.storedMediaFile.name: "has stored your image.",
|
||||
PushKind.reaction.name: "has reacted to your image.",
|
||||
PushKind.reopenedMedia.name: "has reopened your image.",
|
||||
PushKind.reactionToVideo.name: "has reacted to your video.",
|
||||
PushKind.reactionToText.name: "has reacted to your text.",
|
||||
PushKind.reactionToImage.name: "has reacted to your image.",
|
||||
PushKind.reactionToVideo.name:
|
||||
"has reacted with {{reaction}} to your video.",
|
||||
PushKind.reactionToText.name:
|
||||
"has reacted with {{reaction}} to your message.",
|
||||
PushKind.reactionToImage.name:
|
||||
"has reacted with {{reaction}} to your image.",
|
||||
PushKind.response.name: "has responded.",
|
||||
};
|
||||
}
|
||||
return pushNotificationText[pushKind.name] ?? "";
|
||||
var contentText = pushNotificationText[pushNotification.kind.name] ?? "";
|
||||
if (pushNotification.hasReactionContent()) {
|
||||
contentText = contentText.replaceAll(
|
||||
"{{reaction}}", pushNotification.reactionContent);
|
||||
}
|
||||
return contentText;
|
||||
}
|
||||
263
lib/src/services/notifications/pushkeys.notifications.dart
Normal file
263
lib/src/services/notifications/pushkeys.notifications.dart
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/model/json/message.dart' as my;
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart';
|
||||
import 'package:twonly/src/services/api/messages.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
|
||||
/// This function must be called after the database is setup
|
||||
Future setupNotificationWithUsers({bool force = false}) async {
|
||||
var pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys);
|
||||
|
||||
var wasChanged = false;
|
||||
|
||||
final random = Random.secure();
|
||||
|
||||
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
||||
for (final contact in contacts) {
|
||||
PushUser? pushUser =
|
||||
pushUsers.firstWhereOrNull((x) => x.userId == contact.userId);
|
||||
|
||||
if (pushUser != null) {
|
||||
// make it harder to predict the change of the key
|
||||
final timeBefore =
|
||||
DateTime.now().subtract(Duration(days: 5 + random.nextInt(5)));
|
||||
final lastKey = pushUser.pushKeys.last;
|
||||
final createdAt = DateTime.fromMillisecondsSinceEpoch(
|
||||
lastKey.createdAtUnixTimestamp.toInt());
|
||||
|
||||
if (force || createdAt.isBefore(timeBefore)) {
|
||||
final pushKey = PushKey(
|
||||
id: lastKey.id + random.nextInt(5),
|
||||
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
||||
createdAtUnixTimestamp: Int64(DateTime.now().millisecondsSinceEpoch),
|
||||
);
|
||||
await sendNewPushKey(contact.userId, pushKey);
|
||||
// only store a maximum of two keys
|
||||
pushUser.pushKeys.clear();
|
||||
pushUser.pushKeys.add(lastKey);
|
||||
pushUser.pushKeys.add(pushKey);
|
||||
wasChanged = true;
|
||||
}
|
||||
} else {
|
||||
wasChanged = true;
|
||||
|
||||
/// Insert a new push user
|
||||
final pushKey = PushKey(
|
||||
id: Int64(1),
|
||||
key: List<int>.generate(32, (index) => random.nextInt(256)),
|
||||
createdAtUnixTimestamp: Int64(DateTime.now().millisecondsSinceEpoch),
|
||||
);
|
||||
await sendNewPushKey(contact.userId, pushKey);
|
||||
pushUsers.add(PushUser(
|
||||
displayName: getContactDisplayName(contact),
|
||||
blocked: contact.blocked,
|
||||
pushKeys: [pushKey],
|
||||
lastMessageId: null,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (wasChanged) {
|
||||
await setPushKeys(SecureStorageKeys.receivingPushKeys, pushUsers);
|
||||
}
|
||||
}
|
||||
|
||||
Future sendNewPushKey(int userId, PushKey pushKey) async {
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
userId,
|
||||
my.MessageJson(
|
||||
kind: MessageKind.pushKey,
|
||||
content: my.PushKeyContent(
|
||||
keyId: pushKey.id.toInt(),
|
||||
key: pushKey.key,
|
||||
),
|
||||
timestamp: DateTime.fromMillisecondsSinceEpoch(
|
||||
pushKey.createdAtUnixTimestamp.toInt(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future updatePushUser(Contact contact) async {
|
||||
var pushKeys = await getPushKeys(SecureStorageKeys.receivingPushKeys);
|
||||
|
||||
PushUser? pushUser =
|
||||
pushKeys.firstWhereOrNull((x) => x.userId == contact.userId);
|
||||
|
||||
if (pushUser == null) {
|
||||
pushKeys.add(PushUser(
|
||||
displayName: getContactDisplayName(contact),
|
||||
pushKeys: [],
|
||||
blocked: contact.blocked,
|
||||
lastMessageId: Int64(0),
|
||||
));
|
||||
} else {
|
||||
pushUser.displayName = getContactDisplayName(contact);
|
||||
pushUser.blocked = contact.blocked;
|
||||
}
|
||||
|
||||
await setPushKeys(SecureStorageKeys.receivingPushKeys, pushKeys);
|
||||
}
|
||||
|
||||
Future handleNewPushKey(int fromUserId, my.PushKeyContent pushKey) async {
|
||||
var pushKeys = await getPushKeys(SecureStorageKeys.sendingPushKeys);
|
||||
|
||||
PushUser? pushUser = pushKeys.firstWhereOrNull((x) => x.userId == fromUserId);
|
||||
|
||||
if (pushUser == null) {
|
||||
final contact = await twonlyDB.contactsDao
|
||||
.getContactByUserId(fromUserId)
|
||||
.getSingleOrNull();
|
||||
if (contact == null) return;
|
||||
pushKeys.add(PushUser(
|
||||
userId: Int64(fromUserId),
|
||||
displayName: getContactDisplayName(contact),
|
||||
pushKeys: [],
|
||||
blocked: contact.blocked,
|
||||
lastMessageId: Int64(0),
|
||||
));
|
||||
pushUser = pushKeys.firstWhereOrNull((x) => x.userId == fromUserId);
|
||||
}
|
||||
|
||||
if (pushUser == null) {
|
||||
Log.error("could not store new push key as no user was found");
|
||||
}
|
||||
|
||||
// only store the newest key...
|
||||
pushUser!.pushKeys.clear();
|
||||
pushUser.pushKeys.add(
|
||||
PushKey(
|
||||
id: Int64(pushKey.keyId),
|
||||
key: pushKey.key,
|
||||
createdAtUnixTimestamp: Int64(DateTime.now().millisecondsSinceEpoch),
|
||||
),
|
||||
);
|
||||
|
||||
await setPushKeys(SecureStorageKeys.sendingPushKeys, pushKeys);
|
||||
}
|
||||
|
||||
Future updateLastMessageId(int fromUserId, int messageId) async {
|
||||
List<PushUser> pushUsers =
|
||||
await getPushKeys(SecureStorageKeys.receivingPushKeys);
|
||||
|
||||
PushUser? pushUser =
|
||||
pushUsers.firstWhereOrNull((x) => x.userId == fromUserId);
|
||||
if (pushUser == null) {
|
||||
setupNotificationWithUsers();
|
||||
return;
|
||||
}
|
||||
|
||||
if (pushUser.lastMessageId < Int64(messageId)) {
|
||||
pushUser.lastMessageId = Int64(messageId);
|
||||
await setPushKeys(SecureStorageKeys.receivingPushKeys, pushUsers);
|
||||
}
|
||||
}
|
||||
|
||||
/// this will trigger a push notification
|
||||
/// push notification only containing the message kind and username
|
||||
Future<Uint8List?> getPushData(int toUserId, PushNotification content) async {
|
||||
final List<PushUser> pushKeys =
|
||||
await getPushKeys(SecureStorageKeys.sendingPushKeys);
|
||||
|
||||
List<int> key = "InsecureOnlyUsedForAddingContact".codeUnits;
|
||||
int keyId = 0;
|
||||
|
||||
PushUser? pushUser = pushKeys.firstWhereOrNull((x) => x.userId == toUserId);
|
||||
|
||||
if (pushUser == null) {
|
||||
// user does not have send any push keys
|
||||
// only allow accept request and contact request to be send in an insecure way :/
|
||||
// In future find a better way, e.g. use the signal protocol in a native way..
|
||||
if (content.kind != PushKind.acceptRequest &&
|
||||
content.kind != PushKind.contactRequest &&
|
||||
content.kind != PushKind.testNotification) {
|
||||
// this will be enforced after every app uses this system... :/
|
||||
// return null;
|
||||
Log.error("Using insecure key as the receiver does not send a push key!");
|
||||
await encryptAndSendMessageAsync(
|
||||
null,
|
||||
toUserId,
|
||||
my.MessageJson(
|
||||
kind: MessageKind.requestPushKey,
|
||||
content: my.MessageContent(),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
key = pushUser.pushKeys.last.key;
|
||||
keyId = pushUser.pushKeys.last.id.toInt();
|
||||
} catch (e) {
|
||||
Log.error("No push notification key found for user $toUserId");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final chacha20 = Chacha20.poly1305Aead();
|
||||
final nonce = chacha20.newNonce();
|
||||
final secretBox = await chacha20.encrypt(
|
||||
content.writeToBuffer(),
|
||||
secretKey: SecretKeyData(key),
|
||||
nonce: nonce,
|
||||
);
|
||||
final res = EncryptedPushNotification(
|
||||
keyId: Int64(keyId),
|
||||
nonce: nonce,
|
||||
ciphertext: secretBox.cipherText,
|
||||
mac: secretBox.mac.bytes,
|
||||
);
|
||||
return res.writeToBuffer();
|
||||
}
|
||||
|
||||
Future<List<PushUser>> getPushKeys(String storageKey) async {
|
||||
var storage = FlutterSecureStorage();
|
||||
String? pushKeysProto = await storage.read(
|
||||
key: storageKey,
|
||||
iOptions: IOSOptions(
|
||||
groupId: "CN332ZUGRP.eu.twonly.shared",
|
||||
synchronizable: false,
|
||||
accessibility: KeychainAccessibility.first_unlock,
|
||||
),
|
||||
);
|
||||
if (pushKeysProto == null) return [];
|
||||
Uint8List pushKeysRaw = base64Decode(pushKeysProto);
|
||||
return PushUsers.fromBuffer(pushKeysRaw).users;
|
||||
}
|
||||
|
||||
Future setPushKeys(String storageKey, List<PushUser> pushKeys) async {
|
||||
var storage = FlutterSecureStorage();
|
||||
|
||||
await storage.delete(
|
||||
key: storageKey,
|
||||
iOptions: IOSOptions(
|
||||
groupId: "CN332ZUGRP.eu.twonly.shared",
|
||||
synchronizable: false,
|
||||
accessibility: KeychainAccessibility.first_unlock,
|
||||
),
|
||||
);
|
||||
|
||||
String jsonString = base64Encode(PushUsers(users: pushKeys).writeToBuffer());
|
||||
await storage.write(
|
||||
key: storageKey,
|
||||
value: jsonString,
|
||||
iOptions: IOSOptions(
|
||||
groupId: "CN332ZUGRP.eu.twonly.shared",
|
||||
synchronizable: false,
|
||||
accessibility: KeychainAccessibility.first_unlock,
|
||||
),
|
||||
);
|
||||
}
|
||||
91
lib/src/services/notifications/setup.notifications.dart
Normal file
91
lib/src/services/notifications/setup.notifications.dart
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
|
||||
final StreamController<NotificationResponse> selectNotificationStream =
|
||||
StreamController<NotificationResponse>.broadcast();
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void notificationTapBackground(NotificationResponse notificationResponse) {
|
||||
// ignore: avoid_print
|
||||
print('notification(${notificationResponse.id}) action tapped: '
|
||||
'${notificationResponse.actionId} with'
|
||||
' payload: ${notificationResponse.payload}');
|
||||
if (notificationResponse.input?.isNotEmpty ?? false) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'notification action tapped with input: ${notificationResponse.input}');
|
||||
}
|
||||
}
|
||||
|
||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
|
||||
int id = 0;
|
||||
|
||||
Future<void> setupPushNotification() async {
|
||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||
AndroidInitializationSettings("ic_launcher_foreground");
|
||||
|
||||
final List<DarwinNotificationCategory> darwinNotificationCategories =
|
||||
<DarwinNotificationCategory>[];
|
||||
|
||||
/// Note: permissions aren't requested here just to demonstrate that can be
|
||||
/// done later
|
||||
final DarwinInitializationSettings initializationSettingsDarwin =
|
||||
DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
requestProvisionalPermission: false,
|
||||
notificationCategories: darwinNotificationCategories,
|
||||
);
|
||||
|
||||
final InitializationSettings initializationSettings = InitializationSettings(
|
||||
android: initializationSettingsAndroid,
|
||||
iOS: initializationSettingsDarwin,
|
||||
);
|
||||
|
||||
await flutterLocalNotificationsPlugin.initialize(
|
||||
initializationSettings,
|
||||
onDidReceiveNotificationResponse: selectNotificationStream.add,
|
||||
onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
||||
);
|
||||
}
|
||||
|
||||
Future createPushAvatars() async {
|
||||
if (!Platform.isAndroid) {
|
||||
return; // avatars currently only shown in Android...
|
||||
}
|
||||
final contacts = await twonlyDB.contactsDao.getAllNotBlockedContacts();
|
||||
|
||||
for (final contact in contacts) {
|
||||
if (contact.avatarSvg == null) return null;
|
||||
|
||||
final PictureInfo pictureInfo =
|
||||
await vg.loadPicture(SvgStringLoader(contact.avatarSvg!), null);
|
||||
|
||||
final ui.Image image = await pictureInfo.picture.toImage(300, 300);
|
||||
|
||||
final ByteData? byteData =
|
||||
await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
final Uint8List pngBytes = byteData!.buffer.asUint8List();
|
||||
|
||||
// Get the directory to save the image
|
||||
final directory = await getApplicationCacheDirectory();
|
||||
final avatarsDirectory = Directory('${directory.path}/avatars');
|
||||
|
||||
// Create the avatars directory if it does not exist
|
||||
if (!await avatarsDirectory.exists()) {
|
||||
await avatarsDirectory.create(recursive: true);
|
||||
}
|
||||
final filePath = '${avatarsDirectory.path}/${contact.userId}.png';
|
||||
await File(filePath).writeAsBytes(pngBytes);
|
||||
pictureInfo.picture.dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,14 +4,15 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
||||
import 'package:twonly/src/providers/connection.provider.dart';
|
||||
import 'package:twonly/src/services/api/utils.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/services/signal/session.signal.dart';
|
||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/views/components/headline.dart';
|
||||
|
|
@ -103,7 +104,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
timestamp: DateTime.now(),
|
||||
content: MessageContent(),
|
||||
),
|
||||
pushKind: PushKind.contactRequest,
|
||||
pushNotification: PushNotification(kind: PushKind.contactRequest),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -281,7 +282,7 @@ class _ContactsListViewState extends State<ContactsListView> {
|
|||
timestamp: DateTime.now(),
|
||||
content: MessageContent(),
|
||||
),
|
||||
pushKind: PushKind.acceptRequest,
|
||||
pushNotification: PushNotification(kind: PushKind.acceptRequest),
|
||||
);
|
||||
notifyContactsAboutProfileChange();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart';
|
||||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||
import 'package:twonly/src/views/chats/chat_messages_components/chat_message_entry.dart';
|
||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||
import 'package:twonly/src/views/components/initialsavatar.dart';
|
||||
|
|
@ -14,7 +16,7 @@ import 'package:twonly/src/database/twonly_database.dart';
|
|||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/services/api/messages.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
|
||||
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/contact/contact.view.dart';
|
||||
|
|
@ -174,6 +176,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
|
||||
Future _sendMessage() async {
|
||||
if (newMessageController.text == "") return;
|
||||
|
||||
await sendTextMessage(
|
||||
user.userId,
|
||||
TextMessageContent(
|
||||
|
|
@ -181,7 +184,16 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
|||
responseToMessageId: responseToMessage?.messageOtherId,
|
||||
responseToOtherMessageId: responseToMessage?.messageId,
|
||||
),
|
||||
(responseToMessage == null) ? PushKind.text : PushKind.response,
|
||||
PushNotification(
|
||||
kind: (responseToMessage == null)
|
||||
? PushKind.text
|
||||
: (isEmoji(newMessageController.text))
|
||||
? PushKind.reaction
|
||||
: PushKind.response,
|
||||
reactionContent: (isEmoji(newMessageController.text))
|
||||
? newMessageController.text
|
||||
: null,
|
||||
),
|
||||
);
|
||||
newMessageController.clear();
|
||||
currentInputText = "";
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import 'package:drift/drift.dart' show Value;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
||||
import 'package:twonly/src/views/chats/chat_messages_components/in_chat_media_viewer.dart';
|
||||
import 'package:twonly/src/database/twonly_database.dart';
|
||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/services/api/messages.dart';
|
||||
import 'package:twonly/src/services/api/media_download.dart' as received;
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
|
||||
import 'package:twonly/src/views/chats/media_viewer.view.dart';
|
||||
import 'package:twonly/src/model/memory_item.model.dart';
|
||||
import 'package:twonly/src/views/tutorial/tutorials.dart';
|
||||
|
|
@ -80,7 +81,9 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
|||
),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
pushKind: PushKind.reopenedMedia,
|
||||
pushNotification: PushNotification(
|
||||
kind: PushKind.reopenedMedia,
|
||||
),
|
||||
);
|
||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||
widget.message.messageId,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import 'package:lottie/lottie.dart';
|
|||
import 'package:no_screenshot/no_screenshot.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pb.dart';
|
||||
import 'package:twonly/src/services/api/utils.dart';
|
||||
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||
|
|
@ -18,7 +20,6 @@ import 'package:twonly/src/database/tables/messages_table.dart';
|
|||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/services/api/messages.dart';
|
||||
import 'package:twonly/src/services/api/media_download.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
|
||||
|
|
@ -314,7 +315,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
),
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
pushKind: PushKind.storedMediaFile,
|
||||
pushNotification: PushNotification(kind: PushKind.acceptRequest),
|
||||
);
|
||||
setState(() {
|
||||
imageSaved = true;
|
||||
|
|
@ -640,7 +641,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
responseToMessageId:
|
||||
allMediaFiles.first.messageOtherId,
|
||||
),
|
||||
PushKind.reaction,
|
||||
PushNotification(
|
||||
kind: PushKind.response,
|
||||
),
|
||||
);
|
||||
textMessageController.clear();
|
||||
}
|
||||
|
|
@ -822,9 +825,13 @@ class _EmojiReactionWidgetState extends State<EmojiReactionWidget> {
|
|||
text: widget.emoji,
|
||||
responseToMessageId: widget.responseToMessageId,
|
||||
),
|
||||
widget.isVideo
|
||||
PushNotification(
|
||||
kind: widget.isVideo
|
||||
? PushKind.reactionToVideo
|
||||
: PushKind.reactionToImage);
|
||||
: PushKind.reactionToImage,
|
||||
reactionContent: widget.emoji,
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
selectedShortReaction = 0; // Assuming index is 0 for this example
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import 'package:twonly/src/database/twonly_database.dart';
|
|||
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||
|
||||
enum MessageSendState {
|
||||
received,
|
||||
|
|
@ -89,11 +90,12 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
|
||||
MessageSendState state = messageSendStateFromMessage(message);
|
||||
late Color color;
|
||||
MessageContent? content;
|
||||
|
||||
if (message.contentJson == null) {
|
||||
color = getMessageColorFromType(TextMessageContent(text: ""), context);
|
||||
} else {
|
||||
MessageContent? content = MessageContent.fromJson(
|
||||
content = MessageContent.fromJson(
|
||||
message.kind,
|
||||
jsonDecode(message.contentJson!),
|
||||
);
|
||||
|
|
@ -106,6 +108,11 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
switch (state) {
|
||||
case MessageSendState.receivedOpened:
|
||||
icon = Icon(Icons.crop_square, size: 14, color: color);
|
||||
if (content is TextMessageContent) {
|
||||
if (isEmoji(content.text)) {
|
||||
icon = Text(content.text, style: TextStyle(fontSize: 12));
|
||||
}
|
||||
}
|
||||
text = context.lang.messageSendState_Received;
|
||||
break;
|
||||
case MessageSendState.sendOpened:
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:pie_menu/pie_menu.dart';
|
||||
import 'package:screenshot/screenshot.dart';
|
||||
import 'package:twonly/src/services/notifications/setup.notifications.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.dart';
|
||||
import 'package:twonly/src/views/components/user_context_menu.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/views/memories/memories.view.dart';
|
||||
import 'camera/camera_preview_controller_view.dart';
|
||||
import 'chats/chat_list.view.dart';
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/constants/secure_storage_keys.dart';
|
||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||
import 'package:twonly/src/services/fcm.service.dart';
|
||||
import 'package:twonly/src/services/notification.service.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
|
|
@ -50,7 +52,10 @@ class NotificationView extends StatelessWidget {
|
|||
if (user != null) {
|
||||
final pushData = await getPushData(
|
||||
user.userId,
|
||||
PushKind.testNotification,
|
||||
PushNotification(
|
||||
messageId: Int64(0),
|
||||
kind: PushKind.testNotification,
|
||||
),
|
||||
);
|
||||
await apiService.sendTextMessage(
|
||||
user.userId,
|
||||
|
|
|
|||
Loading…
Reference in a new issue