diff --git a/DcCore/DcCore/DC/events.swift b/DcCore/DcCore/DC/events.swift index f3dc260d5..70e2da513 100644 --- a/DcCore/DcCore/DC/events.swift +++ b/DcCore/DcCore/DC/events.swift @@ -13,6 +13,7 @@ public let eventEphemeralTimerModified = Notification.Name(rawValue: "eventEphe public let eventMsgsNoticed = Notification.Name(rawValue: "eventMsgsNoticed") public let eventConnectivityChanged = Notification.Name(rawValue: "eventConnectivityChanged") public let eventWebxdcStatusUpdate = Notification.Name(rawValue: "eventWebxdcStatusUpdate") +public let eventLocationChanged = Notification.Name(rawValue: "eventLocationChanged") public class DcEventHandler { let dcAccounts: DcAccounts @@ -174,6 +175,22 @@ public class DcEventHandler { ]) } + case DC_EVENT_LOCATION_CHANGED: + if accountId != dcAccounts.getSelected().id { + return + } + logger.info("📡[\(accountId)] location changed for contact \(data1)") + DispatchQueue.main.async { + let nc = NotificationCenter.default + nc.post( + name: eventLocationChanged, + object: nil, + userInfo: [ + "contact_id": Int(data1), + ] + ) + } + default: break } diff --git a/DcCore/DcCore/Extensions/UIColor+Extensions.swift b/DcCore/DcCore/Extensions/UIColor+Extensions.swift index 2cae4ad1b..2de0a517d 100644 --- a/DcCore/DcCore/Extensions/UIColor+Extensions.swift +++ b/DcCore/DcCore/Extensions/UIColor+Extensions.swift @@ -64,4 +64,16 @@ public extension UIColor { return UIColor(red: red / 255, green: green / 255, blue: blue / 255, alpha: 1) } + var hexValue: String { + var color = self + if color.cgColor.numberOfComponents < 4 { + let c = color.cgColor.components! + color = UIColor(red: c[0], green: c[0], blue: c[0], alpha: c[1]) + } + if color.cgColor.colorSpace!.model != .rgb { + return "#FFFFFF" + } + let c = color.cgColor.components! + return String(format: "#%02X%02X%02X", Int(c[0]*255.0), Int(c[1]*255.0), Int(c[2]*255.0)) + } } diff --git a/deltachat-ios.xcodeproj/project.pbxproj b/deltachat-ios.xcodeproj/project.pbxproj index c7f9141e3..d5c4667e1 100644 --- a/deltachat-ios.xcodeproj/project.pbxproj +++ b/deltachat-ios.xcodeproj/project.pbxproj @@ -195,6 +195,7 @@ AEFBE22F23FEF23D0045327A /* ProviderInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFBE22E23FEF23D0045327A /* ProviderInfoCell.swift */; }; B20462E42440A4A600367A57 /* AutodelOverviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20462E32440A4A600367A57 /* AutodelOverviewViewController.swift */; }; B20462E62440C99600367A57 /* AutodelOptionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20462E52440C99600367A57 /* AutodelOptionsViewController.swift */; }; + B206C2AB2B7B8088003ACBE6 /* MapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B206C2AA2B7B8088003ACBE6 /* MapViewController.swift */; }; B21005DB23383664004C70C5 /* EmailOptionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B21005DA23383664004C70C5 /* EmailOptionsViewController.swift */; }; B2172F3C29C125F2002C289E /* AdvancedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2172F3B29C125F2002C289E /* AdvancedViewController.swift */; }; B259D64329B771D5008FB706 /* BackupTransferViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B259D64229B771D5008FB706 /* BackupTransferViewController.swift */; }; @@ -499,6 +500,7 @@ B20462E22440805C00367A57 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = ""; }; B20462E32440A4A600367A57 /* AutodelOverviewViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutodelOverviewViewController.swift; sourceTree = ""; }; B20462E52440C99600367A57 /* AutodelOptionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutodelOptionsViewController.swift; sourceTree = ""; }; + B206C2AA2B7B8088003ACBE6 /* MapViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapViewController.swift; sourceTree = ""; }; B209B2042B10139700FBBECF /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/InfoPlist.strings; sourceTree = ""; }; B209B2052B10139700FBBECF /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; B209B2062B10139700FBBECF /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = vi; path = vi.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -961,6 +963,7 @@ AEE6EC3E2282C59C00EDC689 /* GroupMembersViewController.swift */, 302E592326A5CF4800DD4F58 /* ConnectivityViewController.swift */, AE19887423EB264000B4CD5F /* HelpViewController.swift */, + B206C2AA2B7B8088003ACBE6 /* MapViewController.swift */, 304F769425DD237B0094B5E2 /* WebViewViewController.swift */, 304F769825DD23D70094B5E2 /* FullMessageViewController.swift */, 305501732798CDE1008FD5CA /* WebxdcViewController.swift */, @@ -1577,6 +1580,7 @@ B2C42570265C325C00B95377 /* MultilineLabelCell.swift in Sources */, AEF53BD5248904BF00D309C1 /* GalleryTimeLabel.swift in Sources */, AEE6EC482283045D00EDC689 /* SelfProfileViewController.swift in Sources */, + B206C2AB2B7B8088003ACBE6 /* MapViewController.swift in Sources */, 30653081254358B10093E196 /* QuoteView.swift in Sources */, 3067AAC62667F3FE00525036 /* ImageFormat.swift in Sources */, 30EF7308252F6A3300E2C54A /* PaddingTextView.swift in Sources */, diff --git a/deltachat-ios/Assets/maps.xdc b/deltachat-ios/Assets/maps.xdc new file mode 100644 index 000000000..2fb5ca191 Binary files /dev/null and b/deltachat-ios/Assets/maps.xdc differ diff --git a/deltachat-ios/Controller/AllMediaViewController.swift b/deltachat-ios/Controller/AllMediaViewController.swift index bb841643c..bb4bc3ea0 100644 --- a/deltachat-ios/Controller/AllMediaViewController.swift +++ b/deltachat-ios/Controller/AllMediaViewController.swift @@ -47,6 +47,14 @@ class AllMediaViewController: UIPageViewController { return control }() + private lazy var mapButton: UIBarButtonItem = { + if #available(iOS 13.0, *) { + return UIBarButtonItem(image: UIImage(systemName: "map"), style: .plain, target: self, action: #selector(showMap)) + } else { + return UIBarButtonItem(title: String.localized("tab_map"), style: .plain, target: self, action: #selector(showMap)) + } + }() + init(dcContext: DcContext, chatId: Int = 0) { self.dcContext = dcContext self.chatId = chatId @@ -78,6 +86,11 @@ class AllMediaViewController: UIPageViewController { } } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationItem.rightBarButtonItem = chatId == 0 && UserDefaults.standard.bool(forKey: "location_streaming") ? mapButton : nil + } + // MARK: - actions @objc private func segmentControlChanged(_ sender: UISegmentedControl) { if sender.selectedSegmentIndex < pages.count { @@ -88,6 +101,10 @@ class AllMediaViewController: UIPageViewController { } } + @objc private func showMap() { + navigationController?.pushViewController(MapViewController(dcContext: dcContext, chatId: chatId), animated: true) + } + // MARK: - factory private func makeViewController(_ page: Page) -> UIViewController { if page.type1 == DC_MSG_IMAGE { diff --git a/deltachat-ios/Controller/ContactDetailViewController.swift b/deltachat-ios/Controller/ContactDetailViewController.swift index 1c404f535..356a52b67 100644 --- a/deltachat-ios/Controller/ContactDetailViewController.swift +++ b/deltachat-ios/Controller/ContactDetailViewController.swift @@ -95,6 +95,20 @@ class ContactDetailViewController: UITableViewController { return cell }() + private lazy var locationsCell: UITableViewCell = { + let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) + cell.textLabel?.text = String.localized("locations") + if #available(iOS 13.0, *) { + cell.imageView?.image = UIImage(systemName: "map") // added in ios13 + } + cell.accessoryType = .disclosureIndicator + if viewModel.chatId == 0 { + cell.isUserInteractionEnabled = false + cell.textLabel?.isEnabled = false + } + return cell + }() + private lazy var statusCell: MultilineLabelCell = { let cell = MultilineLabelCell() cell.multilineDelegate = self @@ -179,6 +193,8 @@ class ContactDetailViewController: UITableViewController { return verifiedByCell case .allMedia: return allMediaCell + case .locations: + return locationsCell case .ephemeralMessages: return ephemeralMessagesCell case .startChat: @@ -368,6 +384,8 @@ class ContactDetailViewController: UITableViewController { } case .allMedia: showAllMedia() + case .locations: + showLocations() case .ephemeralMessages: showEphemeralMessagesController() case .startChat: @@ -523,6 +541,10 @@ class ContactDetailViewController: UITableViewController { navigationController?.pushViewController(AllMediaViewController(dcContext: viewModel.context, chatId: viewModel.chatId), animated: true) } + private func showLocations() { + navigationController?.pushViewController(MapViewController(dcContext: viewModel.context, chatId: viewModel.chatId), animated: true) + } + private func showSearch() { if let chatViewController = navigationController?.viewControllers.last(where: { $0 is ChatViewController diff --git a/deltachat-ios/Controller/GroupChatDetailViewController.swift b/deltachat-ios/Controller/GroupChatDetailViewController.swift index ea84315ca..e0f6e3390 100644 --- a/deltachat-ios/Controller/GroupChatDetailViewController.swift +++ b/deltachat-ios/Controller/GroupChatDetailViewController.swift @@ -13,6 +13,7 @@ class GroupChatDetailViewController: UIViewController { enum ChatOption { case allMedia + case locations case ephemeralMessages } @@ -142,6 +143,16 @@ class GroupChatDetailViewController: UIViewController { return cell }() + private lazy var locationsCell: UITableViewCell = { + let cell = UITableViewCell(style: .value1, reuseIdentifier: nil) + cell.textLabel?.text = String.localized("locations") + if #available(iOS 13.0, *) { + cell.imageView?.image = UIImage(systemName: "map") // added in ios13 + } + cell.accessoryType = .disclosureIndicator + return cell + }() + init(chatId: Int, dcContext: DcContext) { self.dcContext = dcContext self.chatId = chatId @@ -279,23 +290,25 @@ class GroupChatDetailViewController: UIViewController { private func updateOptions() { self.editBarButtonItem.isEnabled = chat.isMailinglist || chat.canSend + self.chatOptions = [.allMedia] + if UserDefaults.standard.bool(forKey: "location_streaming") { + self.chatOptions.append(.locations) + } + if chat.isMailinglist { - self.chatOptions = [.allMedia] self.memberManagementRows = 0 self.chatActions = [.archiveChat, .copyToClipboard, .clearChat, .deleteChat] self.groupHeader.showMuteButton(show: true) } else if chat.isBroadcast { - self.chatOptions = [.allMedia] self.memberManagementRows = 1 self.chatActions = [.archiveChat, .cloneChat, .clearChat, .deleteChat] self.groupHeader.showMuteButton(show: false) } else if chat.canSend { - self.chatOptions = [.allMedia, .ephemeralMessages] + self.chatOptions.append(.ephemeralMessages) self.memberManagementRows = 2 self.chatActions = [.archiveChat, .cloneChat, .leaveGroup, .clearChat, .deleteChat] self.groupHeader.showMuteButton(show: true) } else { - self.chatOptions = [.allMedia] self.memberManagementRows = 0 self.chatActions = [.archiveChat, .clearChat, .deleteChat] self.groupHeader.showMuteButton(show: true) @@ -416,6 +429,10 @@ class GroupChatDetailViewController: UIViewController { navigationController?.pushViewController(AllMediaViewController(dcContext: dcContext, chatId: chatId), animated: true) } + private func showLocations() { + navigationController?.pushViewController(MapViewController(dcContext: dcContext, chatId: chatId), animated: true) + } + private func showSearch() { if let chatViewController = navigationController?.viewControllers.last(where: { $0 is ChatViewController @@ -478,6 +495,8 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou switch chatOptions[row] { case .allMedia: return allMediaCell + case .locations: + return locationsCell case .ephemeralMessages: return ephemeralMessagesCell } @@ -538,6 +557,8 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou switch chatOptions[row] { case .allMedia: showAllMedia() + case .locations: + showLocations() case .ephemeralMessages: showEphemeralMessagesController() } diff --git a/deltachat-ios/Controller/MapViewController.swift b/deltachat-ios/Controller/MapViewController.swift new file mode 100644 index 000000000..d05ea61e4 --- /dev/null +++ b/deltachat-ios/Controller/MapViewController.swift @@ -0,0 +1,87 @@ +import UIKit +import WebKit +import DcCore + +class MapViewController: WebxdcViewController { + private let chatId: Int + private var locationChangedObserver: NSObjectProtocol? + private var lastLocationId: Int = 0 + + init(dcContext: DcContext, chatId: Int) { + self.chatId = chatId + let msgIdConfigKey = "maps_webxdc_msg_id16." + var msgId = UserDefaults.standard.integer(forKey: msgIdConfigKey + String(dcContext.id)) + if !dcContext.msgExists(id: msgId) { + if let path = Bundle.main.url(forResource: "maps", withExtension: "xdc", subdirectory: "Assets") { + let chatId = dcContext.createChatByContactId(contactId: Int(DC_CONTACT_ID_SELF)) + let msg = dcContext.newMessage(viewType: DC_MSG_WEBXDC) + msg.setFile(filepath: path.path) + msgId = dcContext.sendMessage(chatId: chatId, message: msg) // TODO: this should be hidden by core somehow + UserDefaults.standard.setValue(msgId, forKey: msgIdConfigKey + String(dcContext.id)) + } + } + super.init(dcContext: dcContext, messageId: msgId) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + navigationItem.rightBarButtonItem = nil + } + + override func willMove(toParent parent: UIViewController?) { + super.willMove(toParent: parent) + if parent == nil { + removeObservers() + } else { + addObservers() + } + } + + override func refreshWebxdcInfo() { + super.refreshWebxdcInfo() + title = String.localized(chatId == 0 ? "menu_show_global_map" : "locations") + } + + // MARK: - setup + + private func addObservers() { + locationChangedObserver = NotificationCenter.default.addObserver(forName: eventLocationChanged, object: nil, queue: nil) { [weak self]_ in + self?.updateWebxdc() + } + } + + private func removeObservers() { + if let locationChangedObserver = self.locationChangedObserver { + NotificationCenter.default.removeObserver(locationChangedObserver) + } + } + + + // MARK: - handle updates + + override func sendWebxdcStatusUpdate(payload: String, description: String) -> Bool { + guard let data: Data = payload.data(using: .utf8), + let dict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: AnyObject], + let payload = dict["payload"] as? [String: AnyObject] else { + return false + } + + let msg = dcContext.newMessage(viewType: DC_MSG_TEXT) + msg.text = payload["label"] as? String ?? "ErrLabel" + msg.setLocation(lat: payload["lat"] as? Double ?? 0.0, lng: payload["lng"] as? Double ?? 0.0) + return dcContext.sendMessage(chatId: chatId == 0 ? dcContext.createChatByContactId(contactId: Int(DC_CONTACT_ID_SELF)) : chatId, message: msg) != 0 + } + + override func getWebxdcStatusUpdates(lastKnownSerial: Int) -> String { + let end = Int64(Date().timeIntervalSince1970) + let begin = end - 24*60*60 + let (json, maxLocationId) = dcContext.getLocations(chatId: chatId, timestampBegin: begin, timestampEnd: 0, lastLocationId: lastLocationId) + lastLocationId = max(maxLocationId, lastLocationId) + UIPasteboard.general.string = json // TODO: remove this line, useful for debugging to get JSON out + return json + } +} diff --git a/deltachat-ios/Controller/Settings/AdvancedViewController.swift b/deltachat-ios/Controller/Settings/AdvancedViewController.swift index d136a99ee..bb32d88bc 100644 --- a/deltachat-ios/Controller/Settings/AdvancedViewController.swift +++ b/deltachat-ios/Controller/Settings/AdvancedViewController.swift @@ -329,6 +329,7 @@ internal final class AdvancedViewController: UITableViewController, ProgressAler if !locationStreaming { let alert = UIAlertController(title: "Thanks for trying out the experimental feature 🧪 \"Location streaming\"", message: "You will find a corresponding option in the attach menu (the paper clip) of each chat now.\n\n" + + "Moreover, \"Profiles\" and \"All Media\" will offer a map.\n\n" + "If you want to quit the experimental feature, you can disable it at \"Settings / Advanced\".", preferredStyle: .alert) alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil)) diff --git a/deltachat-ios/Controller/WebxdcViewController.swift b/deltachat-ios/Controller/WebxdcViewController.swift index 542578032..224acd6e0 100644 --- a/deltachat-ios/Controller/WebxdcViewController.swift +++ b/deltachat-ios/Controller/WebxdcViewController.swift @@ -264,7 +264,7 @@ class WebxdcViewController: WebViewViewController { } } - private func refreshWebxdcInfo() { + func refreshWebxdcInfo() { let msg = dcContext.getMessage(id: messageId) let dict = msg.getWebxdcInfoDict() @@ -372,7 +372,7 @@ class WebxdcViewController: WebViewViewController { } } - private func updateWebxdc() { + func updateWebxdc() { webView.evaluateJavaScript("window.__webxdcUpdate()", completionHandler: nil) } @@ -414,6 +414,14 @@ class WebxdcViewController: WebViewViewController { private func shareWebxdc(_ action: UIAlertAction) { Utils.share(message: dcContext.getMessage(id: messageId), parentViewController: self, sourceItem: moreButton) } + + func sendWebxdcStatusUpdate(payload: String, description: String) -> Bool { + return dcContext.sendWebxdcStatusUpdate(msgId: messageId, payload: payload, description: description) + } + + func getWebxdcStatusUpdates(lastKnownSerial: Int) -> String { + return dcContext.getWebxdcStatusUpdates(msgId: messageId, lastKnownSerial: lastKnownSerial) + } } extension WebxdcViewController: WKScriptMessageHandler { @@ -436,7 +444,7 @@ extension WebxdcViewController: WKScriptMessageHandler { logger.error("Failed to parse status update parameters \(message.body)") return } - _ = dcContext.sendWebxdcStatusUpdate(msgId: messageId, payload: payloadString, description: description) + _ = sendWebxdcStatusUpdate(payload: payloadString, description: description) case .sendToChat: if let dict = message.body as? [String: AnyObject] { @@ -477,8 +485,7 @@ extension WebxdcViewController: WKURLSchemeHandler { let statusCode: Int if url.path == "/webxdc-update.json" || url.path == "webxdc-update.json" { let lastKnownSerial = Int(url.query ?? "0") ?? 0 - data = Data( - dcContext.getWebxdcStatusUpdates(msgId: messageId, lastKnownSerial: lastKnownSerial).utf8) + data = Data(getWebxdcStatusUpdates(lastKnownSerial: lastKnownSerial).utf8) mimeType = "application/json; charset=utf-8" statusCode = 200 } else { diff --git a/deltachat-ios/DC/DcContext.swift b/deltachat-ios/DC/DcContext.swift index c17dd4588..621926fb4 100644 --- a/deltachat-ios/DC/DcContext.swift +++ b/deltachat-ios/DC/DcContext.swift @@ -54,13 +54,25 @@ public class DcContext { return DcMsg(pointer: messagePointer) } + public func msgExists(id: Int) -> Bool { + if id <= DC_MSG_ID_LAST_SPECIAL { + return false + } else { + let messagePointer = dc_get_msg(contextPointer, UInt32(id)) + let exists = messagePointer != nil && dc_msg_get_chat_id(messagePointer) != DC_CHAT_ID_TRASH + dc_msg_unref(messagePointer) + return exists + } + } + public func getMessage(id: Int) -> DcMsg { let messagePointer = dc_get_msg(contextPointer, UInt32(id)) return DcMsg(pointer: messagePointer) } - public func sendMessage(chatId: Int, message: DcMsg) { - dc_send_msg(contextPointer, UInt32(chatId), message.messagePointer) + @discardableResult + public func sendMessage(chatId: Int, message: DcMsg) -> Int { + return Int(dc_send_msg(contextPointer, UInt32(chatId), message.messagePointer)) } public func downloadFullMessage(id: Int) { @@ -532,6 +544,72 @@ public class DcContext { dc_set_location(contextPointer, latitude, longitude, accuracy) } + public func getLocations(chatId: Int, timestampBegin: Int64, timestampEnd: Int64, lastLocationId: Int) -> (String, Int) { + var names = [Int: String]() + var colors = [Int: String]() + var maxLocationId = 0 + + let jsonEncoder = JSONEncoder() + var json: String + json = "[" + let array = dc_get_locations(contextPointer, UInt32(chatId), 0, timestampBegin, timestampEnd) + let arrayCnt = dc_array_get_cnt(array) + for i in (0 ..< arrayCnt).reversed() { // most recent position is reported last + let locationId = Int(dc_array_get_id(array, i)) + if locationId > lastLocationId { + maxLocationId = max(maxLocationId, locationId) + if json != "[" { + json += "," // JSON is picky about commas after the last element + } + + let contactId = Int(dc_array_get_contact_id(array, i)) + if names[contactId] == nil { + let contact = getContact(id: contactId) + names[contactId] = contact.displayName + colors[contactId] = contact.color.hexValue + } + + let isIndependet = dc_array_is_independent(array, i) != 0 + var label: String = "" + let name: String = names[contactId] ?? "ErrName" + if isIndependet { + if let cString = dc_array_get_marker(array, i) { + label = String(cString: cString) // get_marker() returns one-char labels only + dc_str_unref(cString) + } else { + let msgId = Int(dc_array_get_msg_id(array, i)) + if msgId != 0 { + label = String((getMessage(id: msgId).text ?? "").prefix(256)) + } + } + } + + let jsonName = (try? String(data: jsonEncoder.encode(name), encoding: .utf8) ?? "\"\"") ?? "\"\"" + let jsonLabel = (try? String(data: jsonEncoder.encode(label), encoding: .utf8) ?? "\"\"") ?? "\"\"" + json += """ + { + "payload": { + "action": "pos", + "contactId": \(contactId), + "lat": \(dc_array_get_latitude(array, i)), + "lng": \(dc_array_get_longitude(array, i)), + "independent": \(isIndependet), + "timestamp": \(dc_array_get_timestamp(array, i)), + "label": \(jsonLabel), + "name": \(jsonName), + "color": "\(colors[contactId] ?? "#ff0000")" + }, + "serial": \(locationId), + "max_serial": \(locationId) + } + """ + } + } + dc_array_unref(array) + json += "]" + return (json, maxLocationId) + } + public func searchMessages(chatId: Int = 0, searchText: String) -> [Int] { let start = CFAbsoluteTimeGetCurrent() guard let arrayPointer = dc_search_msgs(contextPointer, UInt32(chatId), searchText) else { diff --git a/deltachat-ios/DC/DcMsg.swift b/deltachat-ios/DC/DcMsg.swift index 190dea60d..da534d087 100644 --- a/deltachat-ios/DC/DcMsg.swift +++ b/deltachat-ios/DC/DcMsg.swift @@ -226,6 +226,10 @@ public class DcMsg { dc_msg_set_dimension(messagePointer, Int32(width), Int32(height)) } + public func setLocation(lat: Double, lng: Double) { + dc_msg_set_location(messagePointer, lat, lng) + } + public var filesize: Int { return Int(dc_msg_get_filebytes(messagePointer)) } diff --git a/deltachat-ios/ViewModel/ContactDetailViewModel.swift b/deltachat-ios/ViewModel/ContactDetailViewModel.swift index d0e8e3ca9..dcfc4f7e4 100644 --- a/deltachat-ios/ViewModel/ContactDetailViewModel.swift +++ b/deltachat-ios/ViewModel/ContactDetailViewModel.swift @@ -15,6 +15,7 @@ class ContactDetailViewModel { enum ChatOption { case verifiedBy case allMedia + case locations case ephemeralMessages case startChat } @@ -80,6 +81,10 @@ class ContactDetailViewModel { chatOptions.append(.verifiedBy) } chatOptions.append(.allMedia) + if UserDefaults.standard.bool(forKey: "location_streaming") { + chatOptions.append(.locations) + } + if chatId != 0 { if !isDeviceTalk { chatOptions.append(.ephemeralMessages)