mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 13:08:42 +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/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/"
|
SRC_DIR="../twonly-server/twonly/src/"
|
||||||
|
|
|
||||||
|
|
@ -5,419 +5,417 @@
|
||||||
// Created by Tobi on 03.04.25.
|
// Created by Tobi on 03.04.25.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UserNotifications
|
// import UserNotifications
|
||||||
import CryptoKit
|
// import CryptoKit
|
||||||
import Foundation
|
// import Foundation
|
||||||
|
|
||||||
class NotificationService: UNNotificationServiceExtension {
|
// class NotificationService: UNNotificationServiceExtension {
|
||||||
|
|
||||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
// var contentHandler: ((UNNotificationContent) -> Void)?
|
||||||
var bestAttemptContent: UNMutableNotificationContent?
|
// var bestAttemptContent: UNMutableNotificationContent?
|
||||||
|
|
||||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
// override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||||
self.contentHandler = contentHandler
|
// self.contentHandler = contentHandler
|
||||||
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
// bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||||
|
|
||||||
if let bestAttemptContent = bestAttemptContent {
|
// if let bestAttemptContent = bestAttemptContent {
|
||||||
|
|
||||||
guard let _ = bestAttemptContent.userInfo as? [String: Any],
|
// guard let _ = bestAttemptContent.userInfo as? [String: Any],
|
||||||
let push_data = bestAttemptContent.userInfo["push_data"] as? String else {
|
// let push_data = bestAttemptContent.userInfo["push_data"] as? String else {
|
||||||
return contentHandler(bestAttemptContent);
|
// return contentHandler(bestAttemptContent);
|
||||||
}
|
// }
|
||||||
|
|
||||||
let data = getPushNotificationData(pushDataJson: push_data)
|
// let data = getPushNotificationData(pushDataJson: push_data)
|
||||||
|
|
||||||
if data != nil {
|
// if data != nil {
|
||||||
if data!.title == "blocked" {
|
// if data!.title == "blocked" {
|
||||||
NSLog("Block message because user is blocked!")
|
// NSLog("Block message because user is blocked!")
|
||||||
// https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.usernotifications.filtering
|
// // https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.usernotifications.filtering
|
||||||
return contentHandler(UNNotificationContent())
|
// return contentHandler(UNNotificationContent())
|
||||||
}
|
// }
|
||||||
bestAttemptContent.title = data!.title;
|
// bestAttemptContent.title = data!.title;
|
||||||
bestAttemptContent.body = data!.body;
|
// bestAttemptContent.body = data!.body;
|
||||||
bestAttemptContent.threadIdentifier = String(format: "%d", data!.notificationId)
|
// bestAttemptContent.threadIdentifier = String(format: "%d", data!.notificationId)
|
||||||
} else {
|
// } else {
|
||||||
bestAttemptContent.title = "\(bestAttemptContent.title)"
|
// bestAttemptContent.title = "\(bestAttemptContent.title)"
|
||||||
}
|
// }
|
||||||
|
|
||||||
contentHandler(bestAttemptContent)
|
// contentHandler(bestAttemptContent)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
override func serviceExtensionTimeWillExpire() {
|
// override func serviceExtensionTimeWillExpire() {
|
||||||
// Called just before the extension will be terminated by the system.
|
// // 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.
|
// // 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 {
|
// if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
|
||||||
contentHandler(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 {
|
// import CryptoKit
|
||||||
case text
|
// import Foundation
|
||||||
case twonly
|
// import Security
|
||||||
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
|
// func getPushNotificationData(pushDataJson: String) -> (title: String, body: String, notificationId: Int)? {
|
||||||
import Foundation
|
// // Decode the pushDataJson
|
||||||
import Security
|
// guard let pushData = decodePushData(pushDataJson) else {
|
||||||
|
// NSLog("Failed to decode push data")
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
func getPushNotificationData(pushDataJson: String) -> (title: String, body: String, notificationId: Int)? {
|
// var pushKind: PushKind?
|
||||||
// Decode the pushDataJson
|
// var displayName: String?
|
||||||
guard let pushData = decodePushData(pushDataJson) else {
|
// var fromUserId: Int?
|
||||||
NSLog("Failed to decode push data")
|
// var blocked: Bool?
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var pushKind: PushKind?
|
// // Check the keyId
|
||||||
var displayName: String?
|
// if pushData.keyId == 0 {
|
||||||
var fromUserId: Int?
|
// let key = "InsecureOnlyUsedForAddingContact".data(using: .utf8)!.map { Int($0) }
|
||||||
var blocked: Bool?
|
// 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 blocked == true {
|
||||||
if pushData.keyId == 0 {
|
// return ("blocked", "blocked", 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 {
|
// // Handle the push notification based on the pushKind
|
||||||
return ("blocked", "blocked", 0)
|
// if let pushKind = pushKind {
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the push notification based on the pushKind
|
// if pushKind == .testNotification {
|
||||||
if let pushKind = pushKind {
|
// 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 {
|
// } else {
|
||||||
return ("Test Notification", "This is a test notification.", 0)
|
// NSLog("Failed to decrypt message or pushKind is nil")
|
||||||
} else if displayName != nil && fromUserId != nil {
|
// }
|
||||||
return (displayName!, getPushNotificationText(pushKind: pushKind), fromUserId!)
|
// return nil
|
||||||
} else {
|
// }
|
||||||
return ("", getPushNotificationTextWithoutUserId(pushKind: pushKind), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
// func tryDecryptMessage(key: [Int], pushData: PushNotification) -> PushKind? {
|
||||||
NSLog("Failed to decrypt message or pushKind is nil")
|
// // Convert the key from [Int] to Data
|
||||||
}
|
// let keyData = Data(key.map { UInt8($0) }) // Convert Int to UInt8
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tryDecryptMessage(key: [Int], pushData: PushNotification) -> PushKind? {
|
// guard let nonceData = Data(base64Encoded: pushData.nonce),
|
||||||
// Convert the key from [Int] to Data
|
// let cipherTextData = Data(base64Encoded: pushData.cipherText),
|
||||||
let keyData = Data(key.map { UInt8($0) }) // Convert Int to UInt8
|
// let macData = Data(base64Encoded: pushData.mac) else {
|
||||||
|
// NSLog("Failed to decode base64 strings")
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
guard let nonceData = Data(base64Encoded: pushData.nonce),
|
// do {
|
||||||
let cipherTextData = Data(base64Encoded: pushData.cipherText),
|
// // Create a nonce for ChaChaPoly
|
||||||
let macData = Data(base64Encoded: pushData.mac) else {
|
// let nonce = try ChaChaPoly.Nonce(data: nonceData)
|
||||||
NSLog("Failed to decode base64 strings")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
// // Create a sealed box for ChaChaPoly
|
||||||
// Create a nonce for ChaChaPoly
|
// let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: cipherTextData, tag: macData)
|
||||||
let nonce = try ChaChaPoly.Nonce(data: nonceData)
|
|
||||||
|
|
||||||
// Create a sealed box for ChaChaPoly
|
// // Decrypt the data using the key
|
||||||
let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: cipherTextData, tag: macData)
|
// let decryptedData = try ChaChaPoly.open(sealedBox, using: SymmetricKey(data: keyData))
|
||||||
|
|
||||||
// Decrypt the data using the key
|
// // Convert decrypted data to a string
|
||||||
let decryptedData = try ChaChaPoly.open(sealedBox, using: SymmetricKey(data: keyData))
|
// if let decryptedMessage = String(data: decryptedData, encoding: .utf8) {
|
||||||
|
// NSLog("Decrypted message: \(decryptedMessage)")
|
||||||
|
|
||||||
// Convert decrypted data to a string
|
// // Here you can determine the PushKind based on the decrypted message
|
||||||
if let decryptedMessage = String(data: decryptedData, encoding: .utf8) {
|
// return determinePushKind(from: decryptedMessage)
|
||||||
NSLog("Decrypted message: \(decryptedMessage)")
|
// }
|
||||||
|
// } catch {
|
||||||
|
// NSLog("Decryption failed: \(error)")
|
||||||
|
// }
|
||||||
|
|
||||||
// Here you can determine the PushKind based on the decrypted message
|
// return nil
|
||||||
return determinePushKind(from: decryptedMessage)
|
// }
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
NSLog("Decryption failed: \(error)")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 decodePushData(_ json: String) -> PushNotification? {
|
||||||
func determinePushKind(from message: String) -> PushKind? {
|
// // First, decode the base64 string
|
||||||
// Implement your logic to determine the PushKind based on the message content
|
// guard let base64Data = Data(base64Encoded: json) else {
|
||||||
// For example, you might check for specific keywords or formats in the message
|
// NSLog("Failed to decode base64 string")
|
||||||
// This is just a placeholder implementation
|
// return nil
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// // 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? {
|
// // Convert the JSON string to Data
|
||||||
// First, decode the base64 string
|
// guard let jsonData = jsonString.data(using: .utf8) else {
|
||||||
guard let base64Data = Data(base64Encoded: json) else {
|
// NSLog("Failed to convert JSON string to Data")
|
||||||
NSLog("Failed to decode base64 string")
|
// return nil
|
||||||
return nil
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the base64 decoded data to a JSON string
|
// do {
|
||||||
guard let jsonString = String(data: base64Data, encoding: .utf8) else {
|
// // Use JSONDecoder to decode the JSON data into a PushNotification instance
|
||||||
NSLog("Failed to convert base64 data to JSON string")
|
// let decoder = JSONDecoder()
|
||||||
return nil
|
// 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
|
// struct PushNotification: Codable {
|
||||||
guard let jsonData = jsonString.data(using: .utf8) else {
|
// let keyId: Int
|
||||||
NSLog("Failed to convert JSON string to Data")
|
// let nonce: String
|
||||||
return nil
|
// let cipherText: String
|
||||||
}
|
// let mac: String
|
||||||
|
|
||||||
do {
|
// // You can add custom coding keys if the JSON keys differ from the property names
|
||||||
// Use JSONDecoder to decode the JSON data into a PushNotification instance
|
// enum CodingKeys: String, CodingKey {
|
||||||
let decoder = JSONDecoder()
|
// case keyId
|
||||||
let pushNotification = try decoder.decode(PushNotification.self, from: jsonData)
|
// case nonce
|
||||||
return pushNotification
|
// case cipherText
|
||||||
} catch {
|
// case mac
|
||||||
NSLog("Error decoding JSON: \(error)")
|
// }
|
||||||
return nil
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PushNotification: Codable {
|
// struct PushKeyMeta: Codable {
|
||||||
let keyId: Int
|
// let id: Int
|
||||||
let nonce: String
|
// let key: [Int]
|
||||||
let cipherText: String
|
// let createdAt: Date
|
||||||
let mac: String
|
|
||||||
|
|
||||||
// You can add custom coding keys if the JSON keys differ from the property names
|
// enum CodingKeys: String, CodingKey {
|
||||||
enum CodingKeys: String, CodingKey {
|
// case id
|
||||||
case keyId
|
// case key
|
||||||
case nonce
|
// case createdAt
|
||||||
case cipherText
|
// }
|
||||||
case mac
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PushKeyMeta: Codable {
|
// struct PushUser: Codable {
|
||||||
let id: Int
|
// let displayName: String
|
||||||
let key: [Int]
|
// let keys: [PushKeyMeta]
|
||||||
let createdAt: Date
|
// let blocked: Bool?
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
// enum CodingKeys: String, CodingKey {
|
||||||
case id
|
// case displayName
|
||||||
case key
|
// case keys
|
||||||
case createdAt
|
// case blocked
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
struct PushUser: Codable {
|
// func getPushKey() -> [Int: PushUser]? {
|
||||||
let displayName: String
|
// // Retrieve the data from secure storage (Keychain)
|
||||||
let keys: [PushKeyMeta]
|
// guard let data = readFromKeychain(key: "receivingPushKeys") else {
|
||||||
let blocked: Bool?
|
// NSLog("No data found for key: receivingPushKeys")
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
// do {
|
||||||
case displayName
|
// // Decode the JSON data into a dictionary
|
||||||
case keys
|
// let jsonData = data.data(using: .utf8)!
|
||||||
case blocked
|
// let jsonMap = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any]
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPushKey() -> [Int: PushUser]? {
|
// var pushKeys: [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
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
// // Iterate through the JSON map and decode each PushUser
|
||||||
// Decode the JSON data into a dictionary
|
// for (key, value) in jsonMap ?? [:] {
|
||||||
let jsonData = data.data(using: .utf8)!
|
// if let userData = try? JSONSerialization.data(withJSONObject: value, options: []),
|
||||||
let jsonMap = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any]
|
// 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
|
// // Helper function to read from Keychain
|
||||||
for (key, value) in jsonMap ?? [:] {
|
// func readFromKeychain(key: String) -> String? {
|
||||||
if let userData = try? JSONSerialization.data(withJSONObject: value, options: []),
|
// let query: [String: Any] = [
|
||||||
let pushUser = try? JSONDecoder().decode(PushUser.self, from: userData) {
|
// kSecClass as String: kSecClassGenericPassword,
|
||||||
pushKeys[Int(key)!] = pushUser
|
// 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
|
// var dataTypeRef: AnyObject? = nil
|
||||||
} catch {
|
// let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
||||||
NSLog("Error decoding JSON: \(error)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to read from Keychain
|
// if status == errSecSuccess {
|
||||||
func readFromKeychain(key: String) -> String? {
|
// if let data = dataTypeRef as? Data {
|
||||||
let query: [String: Any] = [
|
// return String(data: data, encoding: .utf8)
|
||||||
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
|
|
||||||
]
|
|
||||||
|
|
||||||
var dataTypeRef: AnyObject? = nil
|
// return nil
|
||||||
let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
|
// }
|
||||||
|
|
||||||
if status == errSecSuccess {
|
// func getPushNotificationText(pushKind: PushKind) -> String {
|
||||||
if let data = dataTypeRef as? Data {
|
// let systemLanguage = Locale.current.languageCode ?? "en" // Get the current system language
|
||||||
return String(data: data, encoding: .utf8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
// var pushNotificationText: [PushKind: String] = [:]
|
||||||
}
|
|
||||||
|
|
||||||
func getPushNotificationText(pushKind: PushKind) -> String {
|
// // Define the messages based on the system language
|
||||||
let systemLanguage = Locale.current.languageCode ?? "en" // Get the current 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
|
// func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String {
|
||||||
if systemLanguage.contains("de") { // German
|
// let systemLanguage = Locale.current.languageCode ?? "en" // Get the current system language
|
||||||
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."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the corresponding message or an empty string if not found
|
// var pushNotificationText: [PushKind: String] = [:]
|
||||||
return pushNotificationText[pushKind] ?? ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPushNotificationTextWithoutUserId(pushKind: PushKind) -> String {
|
// // Define the messages based on the system language
|
||||||
let systemLanguage = Locale.current.languageCode ?? "en" // Get the current 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] = [:]
|
// // 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: "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] ?? ""
|
|
||||||
}
|
|
||||||
|
|
|
||||||
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 'FirebaseCoreInternal', :modular_headers => true
|
||||||
pod 'GoogleUtilities', :modular_headers => true
|
pod 'GoogleUtilities', :modular_headers => true
|
||||||
pod 'FirebaseCore', :modular_headers => true
|
pod 'FirebaseCore', :modular_headers => true
|
||||||
|
pod 'SwiftProtobuf'
|
||||||
# pod 'sqlite3', :modular_headers => true
|
# pod 'sqlite3', :modular_headers => true
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,7 @@ PODS:
|
||||||
- sqlite3/math
|
- sqlite3/math
|
||||||
- sqlite3/perf-threadsafe
|
- sqlite3/perf-threadsafe
|
||||||
- sqlite3/rtree
|
- sqlite3/rtree
|
||||||
|
- SwiftProtobuf (1.30.0)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- video_compress (0.3.0):
|
- video_compress (0.3.0):
|
||||||
|
|
@ -253,6 +254,7 @@ DEPENDENCIES:
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||||
|
- SwiftProtobuf
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_compress (from `.symlinks/plugins/video_compress/ios`)
|
- video_compress (from `.symlinks/plugins/video_compress/ios`)
|
||||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||||
|
|
@ -276,6 +278,7 @@ SPEC REPOS:
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
- SDWebImageWebPCoder
|
- SDWebImageWebPCoder
|
||||||
- sqlite3
|
- sqlite3
|
||||||
|
- SwiftProtobuf
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
background_downloader:
|
background_downloader:
|
||||||
|
|
@ -372,10 +375,11 @@ SPEC CHECKSUMS:
|
||||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
|
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
|
||||||
sqlite3_flutter_libs: 74334e3ef2dbdb7d37e50859bb45da43935779c4
|
sqlite3_flutter_libs: 74334e3ef2dbdb7d37e50859bb45da43935779c4
|
||||||
|
SwiftProtobuf: 3697407f0d5b23bedeba9c2eaaf3ec6fdff69349
|
||||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||||
video_compress: f2133a07762889d67f0711ac831faa26f956980e
|
video_compress: f2133a07762889d67f0711ac831faa26f956980e
|
||||||
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
||||||
|
|
||||||
PODFILE CHECKSUM: 3e94c12f4f6904137d1449e3b100fda499ccd32d
|
PODFILE CHECKSUM: a01f0821a361ca6708e29b1299e8becf492a8a71
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
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/connection.provider.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.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/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/onboarding/onboarding.view.dart';
|
import 'package:twonly/src/views/onboarding/onboarding.view.dart';
|
||||||
import 'package:twonly/src/views/home.view.dart';
|
import 'package:twonly/src/views/home.view.dart';
|
||||||
|
|
@ -36,7 +35,6 @@ class _AppState extends State<App> with WidgetsBindingObserver {
|
||||||
globalCallbackConnectionState = (update) {
|
globalCallbackConnectionState = (update) {
|
||||||
context.read<CustomChangeProvider>().updateConnectionState(update);
|
context.read<CustomChangeProvider>().updateConnectionState(update);
|
||||||
setUserPlan();
|
setUserPlan();
|
||||||
setupNotificationWithUsers();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
initAsync();
|
initAsync();
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
import 'package:twonly/src/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/providers/settings.provider.dart';
|
import 'package:twonly/src/providers/settings.provider.dart';
|
||||||
import 'package:twonly/src/services/fcm.service.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/services/twonly_safe/create_backup.twonly_safe.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,7 @@ class SecureStorageKeys {
|
||||||
static const String googleFcm = "google_fcm";
|
static const String googleFcm = "google_fcm";
|
||||||
static const String userData = "userData";
|
static const String userData = "userData";
|
||||||
static const String twonlySafeLastBackupHash = "twonly_safe_last_backup_hash";
|
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:drift/drift.dart';
|
||||||
import 'package:twonly/src/database/tables/contacts_table.dart';
|
import 'package:twonly/src/database/tables/contacts_table.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.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';
|
part 'contacts_dao.g.dart';
|
||||||
|
|
||||||
|
|
@ -113,7 +113,11 @@ class ContactsDao extends DatabaseAccessor<TwonlyDatabase>
|
||||||
|
|
||||||
Future newMessageExchange(int userId) {
|
Future newMessageExchange(int userId) {
|
||||||
return updateContact(
|
return updateContact(
|
||||||
userId, ContactsCompanion(lastMessageExchange: Value(DateTime.now())));
|
userId,
|
||||||
|
ContactsCompanion(
|
||||||
|
lastMessageExchange: Value(DateTime.now()),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<List<Contact>> watchNotAcceptedContacts() {
|
Stream<List<Contact>> watchNotAcceptedContacts() {
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
||||||
static const ErrorCode InvalidSignedPreKey = ErrorCode._(1027, _omitEnumNames ? '' : 'InvalidSignedPreKey');
|
static const ErrorCode InvalidSignedPreKey = ErrorCode._(1027, _omitEnumNames ? '' : 'InvalidSignedPreKey');
|
||||||
static const ErrorCode UserIdNotFound = ErrorCode._(1028, _omitEnumNames ? '' : 'UserIdNotFound');
|
static const ErrorCode UserIdNotFound = ErrorCode._(1028, _omitEnumNames ? '' : 'UserIdNotFound');
|
||||||
static const ErrorCode UserIdAlreadyTaken = ErrorCode._(1029, _omitEnumNames ? '' : 'UserIdAlreadyTaken');
|
static const ErrorCode UserIdAlreadyTaken = ErrorCode._(1029, _omitEnumNames ? '' : 'UserIdAlreadyTaken');
|
||||||
|
static const ErrorCode AppVersionOutdated = ErrorCode._(1030, _omitEnumNames ? '' : 'AppVersionOutdated');
|
||||||
|
|
||||||
static const $core.List<ErrorCode> values = <ErrorCode> [
|
static const $core.List<ErrorCode> values = <ErrorCode> [
|
||||||
Unknown,
|
Unknown,
|
||||||
|
|
@ -80,6 +81,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
||||||
InvalidSignedPreKey,
|
InvalidSignedPreKey,
|
||||||
UserIdNotFound,
|
UserIdNotFound,
|
||||||
UserIdAlreadyTaken,
|
UserIdAlreadyTaken,
|
||||||
|
AppVersionOutdated,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ const ErrorCode$json = {
|
||||||
{'1': 'InvalidSignedPreKey', '2': 1027},
|
{'1': 'InvalidSignedPreKey', '2': 1027},
|
||||||
{'1': 'UserIdNotFound', '2': 1028},
|
{'1': 'UserIdNotFound', '2': 1028},
|
||||||
{'1': 'UserIdAlreadyTaken', '2': 1029},
|
{'1': 'UserIdAlreadyTaken', '2': 1029},
|
||||||
|
{'1': 'AppVersionOutdated', '2': 1030},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -68,5 +69,5 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode(
|
||||||
'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q'
|
'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q'
|
||||||
'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIIIEhgKE0ludmFsaWRTaW'
|
'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIIIEhgKE0ludmFsaWRTaW'
|
||||||
'duZWRQcmVLZXkQgwgSEwoOVXNlcklkTm90Rm91bmQQhAgSFwoSVXNlcklkQWxyZWFkeVRha2Vu'
|
'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_download.dart';
|
||||||
import 'package:twonly/src/services/api/media_upload.dart';
|
import 'package:twonly/src/services/api/media_upload.dart';
|
||||||
import 'package:twonly/src/services/api/server_messages.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/identity.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/prekeys.signal.dart';
|
import 'package:twonly/src/services/signal/prekeys.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/utils.signal.dart';
|
import 'package:twonly/src/services/signal/utils.signal.dart';
|
||||||
|
|
@ -92,6 +93,7 @@ class ApiService {
|
||||||
notifyContactsAboutProfileChange();
|
notifyContactsAboutProfileChange();
|
||||||
twonlyDB.markUpdated();
|
twonlyDB.markUpdated();
|
||||||
syncFlameCounters();
|
syncFlameCounters();
|
||||||
|
setupNotificationWithUsers();
|
||||||
signalHandleNewServerConnection();
|
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/json/message.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/http/http_requests.pb.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/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/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/signal/encryption.signal.dart';
|
||||||
import 'package:twonly/src/services/twonly_safe/create_backup.twonly_safe.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/log.dart';
|
||||||
|
|
@ -539,17 +540,24 @@ Future handleMediaUpload(MediaUpload media) async {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (encryptedBytes == null) continue;
|
if (encryptedBytes == null) continue;
|
||||||
|
|
||||||
|
var messageOnSuccess = TextMessage()
|
||||||
|
..body = encryptedBytes
|
||||||
|
..userId = Int64(message.contactId);
|
||||||
|
|
||||||
final pushKind = (media.metadata!.isRealTwonly)
|
final pushKind = (media.metadata!.isRealTwonly)
|
||||||
? PushKind.twonly
|
? PushKind.twonly
|
||||||
: (media.metadata!.isVideo)
|
: (media.metadata!.isVideo)
|
||||||
? PushKind.video
|
? PushKind.video
|
||||||
: PushKind.image;
|
: PushKind.image;
|
||||||
|
|
||||||
var messageOnSuccess = TextMessage()
|
final pushData = await getPushData(
|
||||||
..body = encryptedBytes
|
message.contactId,
|
||||||
..userId = Int64(message.contactId);
|
PushNotification(
|
||||||
|
messageId: Int64(message.messageId),
|
||||||
var pushData = await getPushData(message.contactId, pushKind);
|
kind: pushKind,
|
||||||
|
),
|
||||||
|
);
|
||||||
if (pushData != null) {
|
if (pushData != null) {
|
||||||
messageOnSuccess.pushData = pushData.toList();
|
messageOnSuccess.pushData = pushData.toList();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,17 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/json/userdata.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/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/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/signal/encryption.signal.dart';
|
||||||
import 'package:twonly/src/services/notification.service.dart';
|
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/storage.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
|
// encrypts and stores the message and then sends it in the background
|
||||||
Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg,
|
Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg,
|
||||||
{PushKind? pushKind, bool willNotGetACKByUser = false}) async {
|
{PushNotification? pushNotification,
|
||||||
|
bool willNotGetACKByUser = false}) async {
|
||||||
if (gIsDemoUser) {
|
if (gIsDemoUser) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List? pushData;
|
Uint8List? pushData;
|
||||||
if (pushKind != null) {
|
if (pushNotification != null) {
|
||||||
pushData = await getPushData(userId, pushKind);
|
pushData = await getPushData(userId, pushNotification);
|
||||||
}
|
}
|
||||||
|
|
||||||
int? retransId = await twonlyDB.messageRetransmissionDao.insertRetransmission(
|
int? retransId = await twonlyDB.messageRetransmissionDao.insertRetransmission(
|
||||||
|
|
@ -158,7 +161,7 @@ Future encryptAndSendMessageAsync(int? messageId, int userId, MessageJson msg,
|
||||||
Future sendTextMessage(
|
Future sendTextMessage(
|
||||||
int target,
|
int target,
|
||||||
TextMessageContent content,
|
TextMessageContent content,
|
||||||
PushKind? pushKind,
|
PushNotification? pushNotification,
|
||||||
) async {
|
) async {
|
||||||
DateTime messageSendAt = DateTime.now();
|
DateTime messageSendAt = DateTime.now();
|
||||||
|
|
||||||
|
|
@ -178,6 +181,10 @@ Future sendTextMessage(
|
||||||
|
|
||||||
if (messageId == null) return;
|
if (messageId == null) return;
|
||||||
|
|
||||||
|
if (pushNotification != null && !pushNotification.hasReactionContent()) {
|
||||||
|
pushNotification.messageId = Int64(messageId);
|
||||||
|
}
|
||||||
|
|
||||||
MessageJson msg = MessageJson(
|
MessageJson msg = MessageJson(
|
||||||
kind: MessageKind.textMessage,
|
kind: MessageKind.textMessage,
|
||||||
messageId: messageId,
|
messageId: messageId,
|
||||||
|
|
@ -185,14 +192,22 @@ Future sendTextMessage(
|
||||||
timestamp: messageSendAt,
|
timestamp: messageSendAt,
|
||||||
);
|
);
|
||||||
|
|
||||||
await encryptAndSendMessageAsync(messageId, target, msg, pushKind: pushKind);
|
await encryptAndSendMessageAsync(
|
||||||
|
messageId,
|
||||||
|
target,
|
||||||
|
msg,
|
||||||
|
pushNotification: pushNotification,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future notifyContactAboutOpeningMessage(
|
Future notifyContactAboutOpeningMessage(
|
||||||
int fromUserId,
|
int fromUserId,
|
||||||
List<int> messageOtherIds,
|
List<int> messageOtherIds,
|
||||||
) async {
|
) async {
|
||||||
|
int biggestMessageId = messageOtherIds.first;
|
||||||
|
|
||||||
for (final messageOtherId in messageOtherIds) {
|
for (final messageOtherId in messageOtherIds) {
|
||||||
|
if (messageOtherId > biggestMessageId) biggestMessageId = messageOtherId;
|
||||||
await encryptAndSendMessageAsync(
|
await encryptAndSendMessageAsync(
|
||||||
null,
|
null,
|
||||||
fromUserId,
|
fromUserId,
|
||||||
|
|
@ -204,6 +219,7 @@ Future notifyContactAboutOpeningMessage(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
await updateLastMessageId(fromUserId, biggestMessageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future notifyContactsAboutProfileChange() async {
|
Future notifyContactsAboutProfileChange() async {
|
||||||
|
|
@ -216,8 +232,12 @@ Future notifyContactsAboutProfileChange() async {
|
||||||
|
|
||||||
for (Contact contact in contacts) {
|
for (Contact contact in contacts) {
|
||||||
if (contact.myAvatarCounter < user.avatarCounter) {
|
if (contact.myAvatarCounter < user.avatarCounter) {
|
||||||
twonlyDB.contactsDao.updateContact(contact.userId,
|
twonlyDB.contactsDao.updateContact(
|
||||||
ContactsCompanion(myAvatarCounter: Value(user.avatarCounter)));
|
contact.userId,
|
||||||
|
ContactsCompanion(
|
||||||
|
myAvatarCounter: Value(user.avatarCounter),
|
||||||
|
),
|
||||||
|
);
|
||||||
await encryptAndSendMessageAsync(
|
await encryptAndSendMessageAsync(
|
||||||
null,
|
null,
|
||||||
contact.userId,
|
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/json/message.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart'
|
import 'package:twonly/src/model/protobuf/api/websocket/client_to_server.pb.dart'
|
||||||
as client;
|
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/error.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart'
|
||||||
as server;
|
as server;
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/api/utils.dart';
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.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/encryption.signal.dart';
|
||||||
import 'package:twonly/src/services/signal/identity.signal.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/prekeys.signal.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
|
|
||||||
final lockHandleServerMessage = Mutex();
|
final lockHandleServerMessage = Mutex();
|
||||||
|
|
||||||
|
|
@ -272,6 +274,14 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
||||||
if (content is TextMessageContent) {
|
if (content is TextMessageContent) {
|
||||||
responseToMessageId = content.responseToMessageId;
|
responseToMessageId = content.responseToMessageId;
|
||||||
responseToOtherMessageId = content.responseToOtherMessageId;
|
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) {
|
if (content is ReopenedMediaFileContent) {
|
||||||
responseToMessageId = content.messageId;
|
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:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage_keys.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 'package:twonly/src/utils/log.dart';
|
||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
import '../../firebase_options.dart';
|
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:cryptography_plus/cryptography_plus.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:path_provider/path_provider.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';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||||
|
|
@ -24,7 +26,9 @@ Future customLocalPushNotification(String title, String msg) async {
|
||||||
const DarwinNotificationDetails darwinNotificationDetails =
|
const DarwinNotificationDetails darwinNotificationDetails =
|
||||||
DarwinNotificationDetails();
|
DarwinNotificationDetails();
|
||||||
const NotificationDetails notificationDetails = NotificationDetails(
|
const NotificationDetails notificationDetails = NotificationDetails(
|
||||||
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
android: androidNotificationDetails,
|
||||||
|
iOS: darwinNotificationDetails,
|
||||||
|
);
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
999999 + Random.secure().nextInt(9999),
|
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 {
|
try {
|
||||||
String jsonString = utf8.decode(base64.decode(pushDataJson));
|
final pushData =
|
||||||
final pushData = PushNotification.fromJson(jsonDecode(jsonString));
|
EncryptedPushNotification.fromBuffer(base64.decode(pushDataB64));
|
||||||
|
|
||||||
PushKind? pushKind;
|
PushNotification? pushNotification;
|
||||||
PushUser? pushUser;
|
PushUser? foundPushUser;
|
||||||
int? fromUserId;
|
|
||||||
|
|
||||||
if (pushData.keyId == 0) {
|
if (pushData.keyId == 0) {
|
||||||
List<int> key = "InsecureOnlyUsedForAddingContact".codeUnits;
|
List<int> key = "InsecureOnlyUsedForAddingContact".codeUnits;
|
||||||
pushKind = await tryDecryptMessage(key, pushData);
|
pushNotification = await tryDecryptMessage(key, pushData);
|
||||||
} else {
|
} else {
|
||||||
var pushKeys = await getPushKeys("receivingPushKeys");
|
final pushUsers = await getPushKeys(SecureStorageKeys.receivingPushKeys);
|
||||||
for (final userId in pushKeys.keys) {
|
for (final pushUser in pushUsers) {
|
||||||
for (final key in pushKeys[userId]!.keys) {
|
for (final key in pushUser.pushKeys) {
|
||||||
if (key.id == pushData.keyId) {
|
if (key.id == pushData.keyId) {
|
||||||
pushKind = await tryDecryptMessage(key.key, pushData);
|
pushNotification = await tryDecryptMessage(key.key, pushData);
|
||||||
if (pushKind != null) {
|
if (pushNotification != null) {
|
||||||
pushUser = pushKeys[userId]!;
|
foundPushUser = pushUser;
|
||||||
fromUserId = userId;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// found correct key and user
|
// found correct key and user
|
||||||
if (pushUser != null) break;
|
if (foundPushUser != null) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pushKind != null) {
|
if (pushNotification != null) {
|
||||||
if (pushKind == PushKind.testNotification) {
|
if (pushNotification.kind == PushKind.testNotification) {
|
||||||
await customLocalPushNotification(
|
await customLocalPushNotification(
|
||||||
"Test notification", "This is a test notification.");
|
"Test notification",
|
||||||
} else if (pushUser != null && fromUserId != null) {
|
"This is a test notification.",
|
||||||
await showLocalPushNotification(pushUser, fromUserId, pushKind);
|
);
|
||||||
|
} 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 {
|
} else {
|
||||||
await showLocalPushNotificationWithoutUserId(pushKind);
|
await showLocalPushNotificationWithoutUserId(pushNotification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
await customLocalPushNotification(
|
||||||
|
"Du hast eine neue Nachricht.",
|
||||||
|
"Öffne twonly um mehr zu erfahren.",
|
||||||
|
);
|
||||||
Log.error(e);
|
Log.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PushKind?> tryDecryptMessage(
|
Future<PushNotification?> tryDecryptMessage(
|
||||||
List<int> key, PushNotification noti) async {
|
List<int> key, EncryptedPushNotification push) async {
|
||||||
try {
|
try {
|
||||||
final chacha20 = Chacha20.poly1305Aead();
|
final chacha20 = Chacha20.poly1305Aead();
|
||||||
SecretKeyData secretKeyData = SecretKeyData(key);
|
SecretKeyData secretKeyData = SecretKeyData(key);
|
||||||
|
|
||||||
SecretBox secretBox = SecretBox(
|
SecretBox secretBox = SecretBox(
|
||||||
noti.cipherText,
|
push.ciphertext,
|
||||||
nonce: noti.nonce,
|
nonce: push.nonce,
|
||||||
mac: Mac(noti.mac),
|
mac: Mac(push.mac),
|
||||||
);
|
);
|
||||||
|
|
||||||
final plaintext =
|
final plaintext =
|
||||||
await chacha20.decrypt(secretBox, secretKey: secretKeyData);
|
await chacha20.decrypt(secretBox, secretKey: secretKeyData);
|
||||||
final plaintextString = utf8.decode(plaintext);
|
return PushNotification.fromBuffer(plaintext);
|
||||||
return PushKindExtension.fromString(plaintextString);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// this error is allowed to happen...
|
// this error is allowed to happen...
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -103,8 +119,7 @@ Future<PushKind?> tryDecryptMessage(
|
||||||
|
|
||||||
Future showLocalPushNotification(
|
Future showLocalPushNotification(
|
||||||
PushUser pushUser,
|
PushUser pushUser,
|
||||||
int fromUserId,
|
PushNotification pushNotification,
|
||||||
PushKind pushKind,
|
|
||||||
) async {
|
) async {
|
||||||
String? title;
|
String? title;
|
||||||
String? body;
|
String? body;
|
||||||
|
|
@ -116,56 +131,64 @@ Future showLocalPushNotification(
|
||||||
}
|
}
|
||||||
|
|
||||||
title = pushUser.displayName;
|
title = pushUser.displayName;
|
||||||
body = getPushNotificationText(pushKind);
|
body = getPushNotificationText(pushNotification);
|
||||||
if (body == "") {
|
if (body == "") {
|
||||||
Log.error("No push notification type defined!");
|
Log.error("No push notification type defined!");
|
||||||
}
|
}
|
||||||
|
|
||||||
FilePathAndroidBitmap? styleInformation;
|
FilePathAndroidBitmap? styleInformation;
|
||||||
String? avatarPath = await getAvatarIcon(fromUserId);
|
String? avatarPath = await getAvatarIcon(pushUser.userId.toInt());
|
||||||
if (avatarPath != null) {
|
if (avatarPath != null) {
|
||||||
styleInformation = FilePathAndroidBitmap(avatarPath);
|
styleInformation = FilePathAndroidBitmap(avatarPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidNotificationDetails androidNotificationDetails =
|
AndroidNotificationDetails androidNotificationDetails =
|
||||||
AndroidNotificationDetails('0', 'Messages',
|
AndroidNotificationDetails(
|
||||||
|
'0',
|
||||||
|
'Messages',
|
||||||
channelDescription: 'Messages from other users.',
|
channelDescription: 'Messages from other users.',
|
||||||
importance: Importance.max,
|
importance: Importance.max,
|
||||||
priority: Priority.max,
|
priority: Priority.max,
|
||||||
ticker: 'You got a new message.',
|
ticker: 'You got a new message.',
|
||||||
largeIcon: styleInformation);
|
largeIcon: styleInformation,
|
||||||
|
);
|
||||||
|
|
||||||
const DarwinNotificationDetails darwinNotificationDetails =
|
const DarwinNotificationDetails darwinNotificationDetails =
|
||||||
DarwinNotificationDetails();
|
DarwinNotificationDetails();
|
||||||
NotificationDetails notificationDetails = NotificationDetails(
|
NotificationDetails notificationDetails = NotificationDetails(
|
||||||
android: androidNotificationDetails, iOS: darwinNotificationDetails);
|
android: androidNotificationDetails,
|
||||||
|
iOS: darwinNotificationDetails,
|
||||||
|
);
|
||||||
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
fromUserId,
|
pushUser.userId.toInt(),
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
notificationDetails,
|
notificationDetails,
|
||||||
payload: pushKind.name,
|
payload: pushNotification.kind.name,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future showLocalPushNotificationWithoutUserId(
|
Future showLocalPushNotificationWithoutUserId(
|
||||||
PushKind pushKind,
|
PushNotification pushNotification,
|
||||||
) async {
|
) async {
|
||||||
String? title;
|
String? title;
|
||||||
String? body;
|
String? body;
|
||||||
|
|
||||||
body = getPushNotificationTextWithoutUserId(pushKind);
|
body = getPushNotificationTextWithoutUserId(pushNotification.kind);
|
||||||
if (body == "") {
|
if (body == "") {
|
||||||
Log.error("No push notification type defined!");
|
Log.error("No push notification type defined!");
|
||||||
}
|
}
|
||||||
|
|
||||||
AndroidNotificationDetails androidNotificationDetails =
|
AndroidNotificationDetails androidNotificationDetails =
|
||||||
AndroidNotificationDetails('0', 'Messages',
|
AndroidNotificationDetails(
|
||||||
|
'0',
|
||||||
|
'Messages',
|
||||||
channelDescription: 'Messages from other users.',
|
channelDescription: 'Messages from other users.',
|
||||||
importance: Importance.max,
|
importance: Importance.max,
|
||||||
priority: Priority.max,
|
priority: Priority.max,
|
||||||
ticker: 'You got a new message.');
|
ticker: 'You got a new message.',
|
||||||
|
);
|
||||||
|
|
||||||
const DarwinNotificationDetails darwinNotificationDetails =
|
const DarwinNotificationDetails darwinNotificationDetails =
|
||||||
DarwinNotificationDetails();
|
DarwinNotificationDetails();
|
||||||
|
|
@ -177,7 +200,7 @@ Future showLocalPushNotificationWithoutUserId(
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
notificationDetails,
|
notificationDetails,
|
||||||
payload: pushKind.name,
|
payload: pushNotification.kind.name,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,7 +263,7 @@ String getPushNotificationTextWithoutUserId(PushKind pushKind) {
|
||||||
return pushNotificationText[pushKind.name] ?? "";
|
return pushNotificationText[pushKind.name] ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPushNotificationText(PushKind pushKind) {
|
String getPushNotificationText(PushNotification pushNotification) {
|
||||||
String systemLanguage = Platform.localeName;
|
String systemLanguage = Platform.localeName;
|
||||||
|
|
||||||
Map<String, String> pushNotificationText;
|
Map<String, String> pushNotificationText;
|
||||||
|
|
@ -256,9 +279,12 @@ String getPushNotificationText(PushKind pushKind) {
|
||||||
PushKind.storedMediaFile.name: "hat dein Bild gespeichert.",
|
PushKind.storedMediaFile.name: "hat dein Bild gespeichert.",
|
||||||
PushKind.reaction.name: "hat auf dein Bild reagiert.",
|
PushKind.reaction.name: "hat auf dein Bild reagiert.",
|
||||||
PushKind.reopenedMedia.name: "hat dein Bild erneut geöffnet.",
|
PushKind.reopenedMedia.name: "hat dein Bild erneut geöffnet.",
|
||||||
PushKind.reactionToVideo.name: "hat auf dein Video reagiert.",
|
PushKind.reactionToVideo.name:
|
||||||
PushKind.reactionToText.name: "hat auf deinen Text reagiert.",
|
"hat mit {{reaction}} auf dein Video reagiert.",
|
||||||
PushKind.reactionToImage.name: "hat auf dein Bild 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.",
|
PushKind.response.name: "hat dir geantwortet.",
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -272,11 +298,19 @@ String getPushNotificationText(PushKind pushKind) {
|
||||||
PushKind.storedMediaFile.name: "has stored your image.",
|
PushKind.storedMediaFile.name: "has stored your image.",
|
||||||
PushKind.reaction.name: "has reacted to your image.",
|
PushKind.reaction.name: "has reacted to your image.",
|
||||||
PushKind.reopenedMedia.name: "has reopened your image.",
|
PushKind.reopenedMedia.name: "has reopened your image.",
|
||||||
PushKind.reactionToVideo.name: "has reacted to your video.",
|
PushKind.reactionToVideo.name:
|
||||||
PushKind.reactionToText.name: "has reacted to your text.",
|
"has reacted with {{reaction}} to your video.",
|
||||||
PushKind.reactionToImage.name: "has reacted to your image.",
|
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.",
|
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:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:provider/provider.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/providers/connection.provider.dart';
|
||||||
import 'package:twonly/src/services/api/utils.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/services/signal/session.signal.dart';
|
||||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.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/src/utils/misc.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/views/components/headline.dart';
|
import 'package:twonly/src/views/components/headline.dart';
|
||||||
|
|
@ -103,7 +104,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
content: MessageContent(),
|
content: MessageContent(),
|
||||||
),
|
),
|
||||||
pushKind: PushKind.contactRequest,
|
pushNotification: PushNotification(kind: PushKind.contactRequest),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -281,7 +282,7 @@ class _ContactsListViewState extends State<ContactsListView> {
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
content: MessageContent(),
|
content: MessageContent(),
|
||||||
),
|
),
|
||||||
pushKind: PushKind.acceptRequest,
|
pushNotification: PushNotification(kind: PushKind.acceptRequest),
|
||||||
);
|
);
|
||||||
notifyContactsAboutProfileChange();
|
notifyContactsAboutProfileChange();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/globals.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/chats/chat_messages_components/chat_message_entry.dart';
|
||||||
import 'package:twonly/src/views/components/animate_icon.dart';
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
import 'package:twonly/src/views/components/initialsavatar.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/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/services/api/messages.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/views/camera/camera_send_to_view.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/contact/contact.view.dart';
|
import 'package:twonly/src/views/contact/contact.view.dart';
|
||||||
|
|
@ -174,6 +176,7 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
|
|
||||||
Future _sendMessage() async {
|
Future _sendMessage() async {
|
||||||
if (newMessageController.text == "") return;
|
if (newMessageController.text == "") return;
|
||||||
|
|
||||||
await sendTextMessage(
|
await sendTextMessage(
|
||||||
user.userId,
|
user.userId,
|
||||||
TextMessageContent(
|
TextMessageContent(
|
||||||
|
|
@ -181,7 +184,16 @@ class _ChatMessagesViewState extends State<ChatMessagesView> {
|
||||||
responseToMessageId: responseToMessage?.messageOtherId,
|
responseToMessageId: responseToMessage?.messageOtherId,
|
||||||
responseToOtherMessageId: responseToMessage?.messageId,
|
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();
|
newMessageController.clear();
|
||||||
currentInputText = "";
|
currentInputText = "";
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import 'package:drift/drift.dart' show Value;
|
import 'package:drift/drift.dart' show Value;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/globals.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/views/chats/chat_messages_components/in_chat_media_viewer.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/services/api/messages.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/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/views/chats/media_viewer.view.dart';
|
||||||
import 'package:twonly/src/model/memory_item.model.dart';
|
import 'package:twonly/src/model/memory_item.model.dart';
|
||||||
import 'package:twonly/src/views/tutorial/tutorials.dart';
|
import 'package:twonly/src/views/tutorial/tutorials.dart';
|
||||||
|
|
@ -80,7 +81,9 @@ class _ChatMediaEntryState extends State<ChatMediaEntry> {
|
||||||
),
|
),
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
),
|
),
|
||||||
pushKind: PushKind.reopenedMedia,
|
pushNotification: PushNotification(
|
||||||
|
kind: PushKind.reopenedMedia,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
await twonlyDB.messagesDao.updateMessageByMessageId(
|
await twonlyDB.messagesDao.updateMessageByMessageId(
|
||||||
widget.message.messageId,
|
widget.message.messageId,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ import 'package:lottie/lottie.dart';
|
||||||
import 'package:no_screenshot/no_screenshot.dart';
|
import 'package:no_screenshot/no_screenshot.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.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/api/utils.dart';
|
||||||
|
import 'package:twonly/src/services/notifications/background.notifications.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
import 'package:twonly/src/views/camera/share_image_editor_view.dart';
|
||||||
import 'package:twonly/src/views/components/animate_icon.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/model/json/message.dart';
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/api/media_download.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/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
|
import 'package:twonly/src/views/camera/camera_send_to_view.dart';
|
||||||
|
|
@ -314,7 +315,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
),
|
),
|
||||||
timestamp: DateTime.now(),
|
timestamp: DateTime.now(),
|
||||||
),
|
),
|
||||||
pushKind: PushKind.storedMediaFile,
|
pushNotification: PushNotification(kind: PushKind.acceptRequest),
|
||||||
);
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
imageSaved = true;
|
imageSaved = true;
|
||||||
|
|
@ -640,7 +641,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
responseToMessageId:
|
responseToMessageId:
|
||||||
allMediaFiles.first.messageOtherId,
|
allMediaFiles.first.messageOtherId,
|
||||||
),
|
),
|
||||||
PushKind.reaction,
|
PushNotification(
|
||||||
|
kind: PushKind.response,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
textMessageController.clear();
|
textMessageController.clear();
|
||||||
}
|
}
|
||||||
|
|
@ -822,9 +825,13 @@ class _EmojiReactionWidgetState extends State<EmojiReactionWidget> {
|
||||||
text: widget.emoji,
|
text: widget.emoji,
|
||||||
responseToMessageId: widget.responseToMessageId,
|
responseToMessageId: widget.responseToMessageId,
|
||||||
),
|
),
|
||||||
widget.isVideo
|
PushNotification(
|
||||||
|
kind: widget.isVideo
|
||||||
? PushKind.reactionToVideo
|
? PushKind.reactionToVideo
|
||||||
: PushKind.reactionToImage);
|
: PushKind.reactionToImage,
|
||||||
|
reactionContent: widget.emoji,
|
||||||
|
),
|
||||||
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedShortReaction = 0; // Assuming index is 0 for this example
|
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/database/tables/messages_table.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/views/components/animate_icon.dart';
|
||||||
|
|
||||||
enum MessageSendState {
|
enum MessageSendState {
|
||||||
received,
|
received,
|
||||||
|
|
@ -89,11 +90,12 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
||||||
|
|
||||||
MessageSendState state = messageSendStateFromMessage(message);
|
MessageSendState state = messageSendStateFromMessage(message);
|
||||||
late Color color;
|
late Color color;
|
||||||
|
MessageContent? content;
|
||||||
|
|
||||||
if (message.contentJson == null) {
|
if (message.contentJson == null) {
|
||||||
color = getMessageColorFromType(TextMessageContent(text: ""), context);
|
color = getMessageColorFromType(TextMessageContent(text: ""), context);
|
||||||
} else {
|
} else {
|
||||||
MessageContent? content = MessageContent.fromJson(
|
content = MessageContent.fromJson(
|
||||||
message.kind,
|
message.kind,
|
||||||
jsonDecode(message.contentJson!),
|
jsonDecode(message.contentJson!),
|
||||||
);
|
);
|
||||||
|
|
@ -106,6 +108,11 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case MessageSendState.receivedOpened:
|
case MessageSendState.receivedOpened:
|
||||||
icon = Icon(Icons.crop_square, size: 14, color: color);
|
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;
|
text = context.lang.messageSendState_Received;
|
||||||
break;
|
break;
|
||||||
case MessageSendState.sendOpened:
|
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:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:pie_menu/pie_menu.dart';
|
import 'package:pie_menu/pie_menu.dart';
|
||||||
import 'package:screenshot/screenshot.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/utils/misc.dart';
|
||||||
import 'package:twonly/src/views/camera/camera_preview_components/camera_preview.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/views/components/user_context_menu.dart';
|
||||||
import 'package:twonly/src/services/notification.service.dart';
|
|
||||||
import 'package:twonly/src/views/memories/memories.view.dart';
|
import 'package:twonly/src/views/memories/memories.view.dart';
|
||||||
import 'camera/camera_preview_controller_view.dart';
|
import 'camera/camera_preview_controller_view.dart';
|
||||||
import 'chats/chat_list.view.dart';
|
import 'chats/chat_list.view.dart';
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/constants/secure_storage_keys.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/views/components/alert_dialog.dart';
|
||||||
import 'package:twonly/src/services/fcm.service.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/misc.dart';
|
||||||
import 'package:twonly/src/utils/storage.dart';
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
|
|
@ -50,7 +52,10 @@ class NotificationView extends StatelessWidget {
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
final pushData = await getPushData(
|
final pushData = await getPushData(
|
||||||
user.userId,
|
user.userId,
|
||||||
PushKind.testNotification,
|
PushNotification(
|
||||||
|
messageId: Int64(0),
|
||||||
|
kind: PushKind.testNotification,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
await apiService.sendTextMessage(
|
await apiService.sendTextMessage(
|
||||||
user.userId,
|
user.userId,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue