Compare commits

...

25 Commits

Author SHA1 Message Date
83ecc3142e Fix broken QR code scanner and fix landscape mode
Changelog-Fixed: Fix broken QR code scanner and fix landscape mode
2024-08-26 22:04:14 +03:00
chungwwei
7f00ef5d9d Add mute button to ProfileActionSheet
This PR adds mute button to ProfileActionSheet, allowing user to quick mute npubs/bots

Changelog-Added: Added mute button to ProfileActionSheet
Signed-off-by: chungwwei <chungwwei223@gmail.com>
2024-08-21 16:25:29 -07:00
ericholguin
d663155941 ux: Mute selected text
This PR adds the Mute action to the selected text menu. Pressing the mute
action will pop up a sheet which allows users to confirm their selection and
choose for how long they would like to mute the selected text for.

Changelog-Added: Added mute action to selected text menu

Signed-off-by: ericholguin <ericholguin@apache.org>
2024-08-19 17:46:24 -07:00
Eric Holguin
8a75537ea3 ux: Profile Edit Improvements (#2376)
This PR adds improvements to the profile edit view.  The banner image is
changed from the old ostrich image to the fresh new damoose. The image and
banner url text entries have been removed from the edit form and now live under
the image selector menu. Selecting the Image URL menu option presents a sheet
where a user can update the image URL. There are now safe guards in place for
users who update their profile, if they make any changes and try to navigate
back to home they will get an alert asking if they want to discard changes. The
Save button is also more prominent.

Changelog-Changed: Changed the default banner from ostriches to damoose
Changelog-Added: Added profile edit safe guards
Changelog-Changed: Changed image and banner url text fields to new sheet view

Signed-off-by: ericholguin <ericholguin@apache.org>
2024-08-12 11:54:32 -07:00
Daniel D’Aquino
49c8d63d0b Merge pull request #2365 from danieldaquino/#2341
Fix crash with blurhashes with reported dimension of 0x0
2024-08-08 10:18:52 -07:00
Daniel D’Aquino
6480023c96 Fix crash with blurhashes with reported dimension of 0x0
This commit fixes a consistent crash noticed when visiting a particular
profile.

The crash was occuring when trying to display the blurhash of a specific Event, where the metadata claimed the image dimensions were 0px x 0px.

The null dimensions caused a division by zero to occur when scaling the image down, yielding a NaN (Not a Number) size value, which crashed the app when trying to cast that CGFloat value down to an integer.

The crash was fixed by modifying the down-scaling computations to check for invalid dimensions, and return nil. The callers were then updated to fallback to a default display dimension.

Issue repro
-------

Device: iPhone 15 simulator
iOS: 17.5
Damus: dba1799df0
Steps:
1. Visit the profile npub1gujeqakgt7fyp6zjggxhyy7ft623qtcaay5lkc8n8gkry4cvnrzqd3f67z
2. Check accessing the profile does not crash Damus.
3. Visit the event that had invalid 0x0 dimensions on the metadata (note1qmqdualjezamcjun23l4d9xw7529m7fee6hklgtnhack2fwznxysuzuuyz)
4. Check that Damus does not crash.

Results: Steps 2 and 4 crash 100% of the time (3/3)

Testing
--------

PASS

Device: iPhone 15 simulator
iOS: 17.5
Damus: This commit
Steps: Same as repro
Results:
1. Crash no longer occurs
2. Blurhash looks ok

Closes: https://github.com/damus-io/damus/issues/2341
Changelog-Fixed: Fix crash when viewing notes with invalid image dimension metadata
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
2024-08-05 12:52:43 -07:00
William Casarin
774da239b9 Merge remote-tracking branches 'pr/2362', 'pr/2361', 'pr/2319' and 'pr/2355' 2024-08-05 10:57:02 -07:00
transifex-integration[bot]
90c80645ec Translate Localizable.stringsdict in ja
100% translated source file: 'Localizable.stringsdict'
on 'ja'.
2024-08-05 10:10:20 +00:00
transifex-integration[bot]
613ec23f7f Translate Localizable.strings in nl
100% translated source file: 'Localizable.strings'
on 'nl'.
2024-08-05 09:42:45 +00:00
transifex-integration[bot]
1d73ae1d32 Translate Localizable.strings in hu_HU
100% translated source file: 'Localizable.strings'
on 'hu_HU'.
2024-08-05 08:22:18 +00:00
63e364ce5b Export strings for translation 2024-08-04 23:59:48 -04:00
transifex-integration[bot]
ee5f53e4eb Translate Localizable.strings in hu_HU
100% translated source file: 'Localizable.strings'
on 'hu_HU'.
2024-08-04 23:56:25 -04:00
transifex-integration[bot]
9de21a730a Translate Localizable.strings in zh_TW
100% translated source file: 'Localizable.strings'
on 'zh_TW'.
2024-08-04 23:56:25 -04:00
transifex-integration[bot]
36c09c8657 Translate Localizable.strings in zh_HK
100% translated source file: 'Localizable.strings'
on 'zh_HK'.
2024-08-04 23:56:25 -04:00
transifex-integration[bot]
e8ac143192 Translate Localizable.strings in zh_CN
100% translated source file: 'Localizable.strings'
on 'zh_CN'.
2024-08-04 23:56:24 -04:00
transifex-integration[bot]
93f44939e3 Translate Localizable.strings in de
100% translated source file: 'Localizable.strings'
on 'de'.
2024-08-04 23:56:24 -04:00
transifex-integration[bot]
48078b9b6a Translate Localizable.strings in nl
100% translated source file: 'Localizable.strings'
on 'nl'.
2024-08-04 23:56:24 -04:00
d6d6858e0b Export strings for translations 2024-08-04 23:56:24 -04:00
transifex-integration[bot]
0187ff1dc0 Translate Localizable.stringsdict in sv_SE
100% translated source file: 'Localizable.stringsdict'
on 'sv_SE'.
2024-08-04 23:56:24 -04:00
transifex-integration[bot]
4f9fef8515 Translate Localizable.strings in sv_SE
100% translated source file: 'Localizable.strings'
on 'sv_SE'.
2024-08-04 23:56:24 -04:00
transifex-integration[bot]
1ebadd42f0 Translate Localizable.stringsdict in hu_HU
100% translated source file: 'Localizable.stringsdict'
on 'hu_HU'.
2024-08-04 23:56:24 -04:00
transifex-integration[bot]
4fb4f3a2de Translate Localizable.strings in hu_HU
100% translated source file: 'Localizable.strings'
on 'hu_HU'.
2024-08-04 23:56:24 -04:00
transifex-integration[bot]
f49169c03c Translate InfoPlist.strings in sw
100% translated source file: 'InfoPlist.strings'
on 'sw'.
2024-08-04 23:56:23 -04:00
Daniel D’Aquino
740c10c9b2 Implement push notification preferences and update API
This commit implements push notification preferences with the push
notifications server, as well as updates itself to the new push
notifications API.

Testing
-------

Device: iPhone 15 simulator
iOS: 17.5
Damus: this commit
notepush: 3ca3a8325707535fdbc98d681d5e4a47dc313c67
Steps:
1. Enable push notifications. Settings should get synced and success message should appear
2. Disable push notifications. Sync message should disappear as it no longer applies
3. Enable push notifications again, and tweak notifications. Settings should sync with no errors
4. Leave settings screen and come back. Settings should be declared as synced

Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Closes: https://github.com/damus-io/damus/issues/2360
2024-08-04 12:03:05 -07:00
ericholguin
653f9fbcbe relay: Add Tor Relay Image
This PR just adds the tor icon to relays ending with .onion

Changelog-Added: Tor relay icon

Closes: #2318
Signed-off-by: ericholguin <ericholguin@apache.org>
2024-07-30 21:22:55 -06:00
34 changed files with 900 additions and 214 deletions

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "damoose.jpeg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "tor.svg.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -14,6 +14,7 @@ struct SelectableText: View {
let attributedString: AttributedString
let textAlignment: NSTextAlignment
@State private var showHighlightPost = false
@State private var showMutePost = false
@State private var selectedText = ""
@State private var selectedTextHeight: CGFloat = .zero
@State private var selectedTextWidth: CGFloat = .zero
@@ -38,6 +39,7 @@ struct SelectableText: View {
textAlignment: self.textAlignment,
enableHighlighting: self.enableHighlighting(),
showHighlightPost: $showHighlightPost,
showMutePost: $showMutePost,
selectedText: $selectedText,
height: $selectedTextHeight
)
@@ -60,6 +62,11 @@ struct SelectableText: View {
.presentationDetents([.height(selectedTextHeight + 150), .medium, .large])
}
}
.sheet(isPresented: $showMutePost) {
AddMuteItemView(state: damus_state, new_text: $selectedText)
.presentationDragIndicator(.visible)
.presentationDetents([.height(300), .medium, .large])
}
.frame(height: selectedTextHeight)
}
@@ -70,10 +77,12 @@ struct SelectableText: View {
fileprivate class TextView: UITextView {
@Binding var showHighlightPost: Bool
@Binding var showMutePost: Bool
@Binding var selectedText: String
init(frame: CGRect, textContainer: NSTextContainer?, showHighlightPost: Binding<Bool>, selectedText: Binding<String>) {
init(frame: CGRect, textContainer: NSTextContainer?, showHighlightPost: Binding<Bool>, showMutePost: Binding<Bool>, selectedText: Binding<String>) {
self._showHighlightPost = showHighlightPost
self._showMutePost = showMutePost
self._selectedText = selectedText
super.init(frame: frame, textContainer: textContainer)
}
@@ -86,6 +95,11 @@ fileprivate class TextView: UITextView {
if action == #selector(highlightText(_:)) {
return true
}
if action == #selector(muteText(_:)) {
return true
}
return super.canPerformAction(action, withSender: sender)
}
@@ -94,6 +108,12 @@ fileprivate class TextView: UITextView {
selectedText = self.text(in: selectedRange) ?? ""
showHighlightPost.toggle()
}
@objc public func muteText(_ sender: Any?) {
guard let selectedRange = self.selectedTextRange else { return }
selectedText = self.text(in: selectedRange) ?? ""
showMutePost.toggle()
}
}
@@ -106,11 +126,12 @@ fileprivate class TextView: UITextView {
let textAlignment: NSTextAlignment
let enableHighlighting: Bool
@Binding var showHighlightPost: Bool
@Binding var showMutePost: Bool
@Binding var selectedText: String
@Binding var height: CGFloat
func makeUIView(context: UIViewRepresentableContext<Self>) -> TextView {
let view = TextView(frame: .zero, textContainer: nil, showHighlightPost: $showHighlightPost, selectedText: $selectedText)
let view = TextView(frame: .zero, textContainer: nil, showHighlightPost: $showHighlightPost, showMutePost: $showMutePost, selectedText: $selectedText)
view.isEditable = false
view.dataDetectorTypes = .all
view.isSelectable = true
@@ -123,7 +144,8 @@ fileprivate class TextView: UITextView {
let menuController = UIMenuController.shared
let highlightItem = UIMenuItem(title: "Highlight", action: #selector(view.highlightText(_:)))
menuController.menuItems = self.enableHighlighting ? [highlightItem] : []
let muteItem = UIMenuItem(title: "Mute", action: #selector(view.muteText(_:)))
menuController.menuItems = self.enableHighlighting ? [highlightItem, muteItem] : []
return view
}

View File

@@ -11,6 +11,10 @@ struct PushNotificationClient {
let keypair: Keypair
let settings: UserSettingsStore
private(set) var device_token: Data? = nil
var device_token_hex: String? {
guard let device_token else { return nil }
return device_token.map { String(format: "%02.2hhx", $0) }.joined()
}
mutating func set_device_token(new_device_token: Data) async throws {
self.device_token = new_device_token
@@ -20,26 +24,21 @@ struct PushNotificationClient {
}
func send_token() async throws {
guard let device_token else { return }
// Send the device token and pubkey to the server
let token = device_token.map { String(format: "%02.2hhx", $0) }.joined()
guard let token = device_token_hex else { return }
Log.info("Sending device token to server: %s", for: .push_notifications, token)
let pubkey = self.keypair.pubkey
// Send those as JSON to the server
let json: [String: Any] = ["deviceToken": token, "pubkey": pubkey.hex()]
// create post request
let url = self.settings.send_device_token_to_localhost ? Constants.DEVICE_TOKEN_RECEIVER_TEST_URL : Constants.DEVICE_TOKEN_RECEIVER_PRODUCTION_URL
let json_data = try JSONSerialization.data(withJSONObject: json)
let url = self.current_push_notification_environment().api_base_url()
.appendingPathComponent("user-info")
.appendingPathComponent(self.keypair.pubkey.hex())
.appendingPathComponent(token)
let (data, response) = try await make_nip98_authenticated_request(
method: .post,
method: .put,
url: url,
payload: json_data,
payload: nil,
payload_type: .json,
auth_keypair: self.keypair
)
@@ -58,26 +57,23 @@ struct PushNotificationClient {
}
func revoke_token() async throws {
guard let device_token else { return }
// Send the device token and pubkey to the server
let token = device_token.map { String(format: "%02.2hhx", $0) }.joined()
guard let token = device_token_hex else { return }
Log.info("Revoking device token from server: %s", for: .push_notifications, token)
let pubkey = self.keypair.pubkey
// Send those as JSON to the server
let json: [String: Any] = ["deviceToken": token, "pubkey": pubkey.hex()]
// create post request
let url = self.settings.send_device_token_to_localhost ? Constants.DEVICE_TOKEN_REVOKER_TEST_URL : Constants.DEVICE_TOKEN_REVOKER_PRODUCTION_URL
let json_data = try JSONSerialization.data(withJSONObject: json)
let url = self.current_push_notification_environment().api_base_url()
.appendingPathComponent("user-info")
.appendingPathComponent(pubkey.hex())
.appendingPathComponent(token)
let (data, response) = try await make_nip98_authenticated_request(
method: .post,
method: .delete,
url: url,
payload: json_data,
payload: nil,
payload_type: .json,
auth_keypair: self.keypair
)
@@ -94,6 +90,78 @@ struct PushNotificationClient {
return
}
func set_settings(_ new_settings: NotificationSettings? = nil) async throws {
// Send the device token and pubkey to the server
guard let token = device_token_hex else { return }
Log.info("Sending notification preferences to the server", for: .push_notifications)
let url = self.current_push_notification_environment().api_base_url()
.appendingPathComponent("user-info")
.appendingPathComponent(self.keypair.pubkey.hex())
.appendingPathComponent(token)
.appendingPathComponent("preferences")
let json_payload = try JSONEncoder().encode(new_settings ?? NotificationSettings.from(settings: settings))
let (data, response) = try await make_nip98_authenticated_request(
method: .put,
url: url,
payload: json_payload,
payload_type: .json,
auth_keypair: self.keypair
)
if let httpResponse = response as? HTTPURLResponse {
switch httpResponse.statusCode {
case 200:
Log.info("Sent notification settings to Damus push notification server successfully", for: .push_notifications)
default:
Log.error("Error in sending notification settings to Damus push notification server. HTTP status code: %d; Response: %s", for: .push_notifications, httpResponse.statusCode, String(data: data, encoding: .utf8) ?? "Unknown")
throw ClientError.http_response_error(status_code: httpResponse.statusCode, response: data)
}
}
return
}
func get_settings() async throws -> NotificationSettings {
// Send the device token and pubkey to the server
guard let token = device_token_hex else {
throw ClientError.no_device_token
}
let url = self.current_push_notification_environment().api_base_url()
.appendingPathComponent("user-info")
.appendingPathComponent(self.keypair.pubkey.hex())
.appendingPathComponent(token)
.appendingPathComponent("preferences")
let (data, response) = try await make_nip98_authenticated_request(
method: .get,
url: url,
payload: nil,
payload_type: .json,
auth_keypair: self.keypair
)
if let httpResponse = response as? HTTPURLResponse {
switch httpResponse.statusCode {
case 200:
guard let notification_settings = NotificationSettings.from(json_data: data) else { throw ClientError.json_decoding_error }
return notification_settings
default:
Log.error("Error in getting notification settings to Damus push notification server. HTTP status code: %d; Response: %s", for: .push_notifications, httpResponse.statusCode, String(data: data, encoding: .utf8) ?? "Unknown")
throw ClientError.http_response_error(status_code: httpResponse.statusCode, response: data)
}
}
throw ClientError.could_not_process_response
}
func current_push_notification_environment() -> Environment {
return self.settings.send_device_token_to_localhost ? .local_test(host: nil) : .production
}
}
// MARK: Helper structures
@@ -101,5 +169,111 @@ struct PushNotificationClient {
extension PushNotificationClient {
enum ClientError: Error {
case http_response_error(status_code: Int, response: Data)
case could_not_process_response
case no_device_token
case json_decoding_error
}
struct NotificationSettings: Codable, Equatable {
let zap_notifications_enabled: Bool
let mention_notifications_enabled: Bool
let repost_notifications_enabled: Bool
let reaction_notifications_enabled: Bool
let dm_notifications_enabled: Bool
let only_notifications_from_following_enabled: Bool
static func from(json_data: Data) -> Self? {
guard let decoded = try? JSONDecoder().decode(Self.self, from: json_data) else { return nil }
return decoded
}
static func from(settings: UserSettingsStore) -> Self {
return NotificationSettings(
zap_notifications_enabled: settings.zap_notification,
mention_notifications_enabled: settings.mention_notification,
repost_notifications_enabled: settings.repost_notification,
reaction_notifications_enabled: settings.like_notification,
dm_notifications_enabled: settings.dm_notification,
only_notifications_from_following_enabled: settings.notification_only_from_following
)
}
}
enum Environment: CaseIterable, Codable, Identifiable, StringCodable, Equatable, Hashable {
static var allCases: [Environment] = [.local_test(host: nil), .production]
case local_test(host: String?)
case production
func text_description() -> String {
switch self {
case .local_test:
return NSLocalizedString("Test (local)", comment: "Label indicating a local test environment for Push notification functionality (Developer feature)")
case .production:
return NSLocalizedString("Production", comment: "Label indicating the production environment for Push notification functionality")
}
}
func api_base_url() -> URL {
switch self {
case .local_test(let host):
URL(string: "http://\(host ?? "localhost:8000")") ?? Constants.PUSH_NOTIFICATION_SERVER_TEST_BASE_URL
case .production:
Constants.PURPLE_API_PRODUCTION_BASE_URL
}
}
func custom_host() -> String? {
switch self {
case .local_test(let host):
return host
default:
return nil
}
}
init?(from string: String) {
switch string {
case "local_test":
self = .local_test(host: nil)
case "production":
self = .production
default:
let components = string.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
if components.count == 2 && components[0] == "local_test" {
self = .local_test(host: String(components[1]))
} else {
return nil
}
}
}
func to_string() -> String {
switch self {
case .local_test(let host):
if let host {
return "local_test:\(host)"
}
return "local_test"
case .production:
return "production"
}
}
var id: String {
switch self {
case .local_test(let host):
if let host {
return "local_test:\(host)"
}
else {
return "local_test"
}
case .production:
return "production"
}
}
}
}

View File

@@ -10,13 +10,13 @@ import Foundation
class Constants {
//static let EXAMPLE_DEMOS: DamusState = .empty
static let DAMUS_APP_GROUP_IDENTIFIER: String = "group.com.damus"
static let DEVICE_TOKEN_RECEIVER_PRODUCTION_URL: URL = URL(string: "http://45.33.32.5:8000/user-info")!
static let DEVICE_TOKEN_RECEIVER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info")!
static let DEVICE_TOKEN_REVOKER_PRODUCTION_URL: URL = URL(string: "http://45.33.32.5:8000/user-info/remove")!
static let DEVICE_TOKEN_REVOKER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info/remove")!
static let MAIN_APP_BUNDLE_IDENTIFIER: String = "com.jb55.damus2"
static let NOTIFICATION_EXTENSION_BUNDLE_IDENTIFIER: String = "com.jb55.damus2.DamusNotificationService"
// MARK: Push notification server
static let PUSH_NOTIFICATION_SERVER_PRODUCTION_BASE_URL: URL = URL(string: "http://45.33.32.5:8000")!
static let PUSH_NOTIFICATION_SERVER_TEST_BASE_URL: URL = URL(string: "http://localhost:8000")!
// MARK: Purple
// API
static let PURPLE_API_LOCAL_TEST_BASE_URL: URL = URL(string: "http://localhost:8989")!

View File

@@ -60,7 +60,8 @@ struct ImageMetadata: Equatable {
func process_blurhash(blurhash: String, size: CGSize?) async -> UIImage? {
let res = Task.detached(priority: .low) {
let size = get_blurhash_size(img_size: size ?? CGSize(width: 100.0, height: 100.0))
let default_size = CGSize(width: 100.0, height: 100.0)
let size = get_blurhash_size(img_size: size ?? default_size) ?? default_size
guard let img = UIImage.init(blurHash: blurhash, size: size) else {
let noimg: UIImage? = nil
return noimg
@@ -135,7 +136,8 @@ extension UIImage {
}
}
func get_blurhash_size(img_size: CGSize) -> CGSize {
func get_blurhash_size(img_size: CGSize) -> CGSize? {
guard img_size.width > 0 && img_size.height > 0 else { return nil }
return CGSize(width: 100.0, height: (100.0/img_size.width) * img_size.height)
}
@@ -145,7 +147,7 @@ func calculate_blurhash(img: UIImage) async -> String? {
}
let res = Task.detached(priority: .low) {
let bhs = get_blurhash_size(img_size: img.size)
let bhs = get_blurhash_size(img_size: img.size) ?? CGSize(width: 100.0, height: 100.0)
let smaller = img.resized(to: bhs)
guard let blurhash = smaller.blurHash(numberOfComponents: (5,5)) else {

View File

@@ -13,7 +13,7 @@ struct EditBannerImageView: View {
var damus_state: DamusState
@ObservedObject var viewModel: ImageUploadingObserver
let callback: (URL?) -> Void
let defaultImage = UIImage(named: "profile-banner") ?? UIImage()
let defaultImage = UIImage(named: "damoose") ?? UIImage()
@State var banner_image: URL? = nil
@@ -38,7 +38,7 @@ struct EditBannerImageView: View {
struct InnerBannerImageView: View {
let disable_animation: Bool
let url: URL?
let defaultImage = UIImage(named: "profile-banner") ?? UIImage()
let defaultImage = UIImage(named: "damoose") ?? UIImage()
var body: some View {
ZStack {

View File

@@ -165,7 +165,7 @@ struct ChatBubble<T: View, U: ShapeStyle, V: View>: View {
stroke_style: .init(lineWidth: 4),
background_style: Color.accentColor
) {
Text("Hello there")
Text(verbatim: "Hello there")
.padding()
}
.foregroundColor(.white)
@@ -176,7 +176,7 @@ struct ChatBubble<T: View, U: ShapeStyle, V: View>: View {
stroke_style: .init(lineWidth: 4),
background_style: Color.accentColor
) {
Text("Hello there")
Text(verbatim: "Hello there")
.padding()
}
.foregroundColor(.white)

View File

@@ -182,25 +182,6 @@ extension CodeScannerView {
delegate?.didFail(reason: .badOutput)
return
}
}
override public func viewWillLayoutSubviews() {
previewLayer?.frame = view.layer.bounds
}
@objc func updateOrientation() {
guard let orientation = view.window?.windowScene?.interfaceOrientation else { return }
guard let connection = captureSession.connections.last, connection.isVideoOrientationSupported else { return }
connection.videoOrientation = AVCaptureVideoOrientation(rawValue: orientation.rawValue) ?? .portrait
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateOrientation()
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if previewLayer == nil {
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
@@ -220,6 +201,21 @@ extension CodeScannerView {
}
}
override public func viewWillLayoutSubviews() {
previewLayer?.frame = view.layer.bounds
}
@objc func updateOrientation() {
guard let orientation = view.window?.windowScene?.interfaceOrientation else { return }
guard let connection = captureSession.connections.last, connection.isVideoOrientationSupported else { return }
connection.videoOrientation = AVCaptureVideoOrientation(rawValue: orientation.rawValue) ?? .portrait
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateOrientation()
}
private func addviewfinder() {
guard showViewfinder, let imageView = viewFinder else { return }

View File

@@ -8,7 +8,7 @@ import SwiftUI
struct AddMuteItemView: View {
let state: DamusState
@State var new_text: String = ""
@Binding var new_text: String
@State var expiration: DamusDuration = .indefinite
@Environment(\.dismiss) var dismiss
@@ -108,6 +108,6 @@ struct AddMuteItemView: View {
struct AddMuteItemView_Previews: PreviewProvider {
static var previews: some View {
AddMuteItemView(state: test_damus_state)
AddMuteItemView(state: test_damus_state, new_text: .constant(""))
}
}

View File

@@ -15,6 +15,8 @@ struct MutelistView: View {
@State var hashtags: [MuteItem] = []
@State var threads: [MuteItem] = []
@State var words: [MuteItem] = []
@State var new_text: String = ""
func RemoveAction(item: MuteItem) -> some View {
Button {
@@ -120,13 +122,9 @@ struct MutelistView: View {
}
}
.sheet(isPresented: $show_add_muteitem, onDismiss: { self.show_add_muteitem = false }) {
if #available(iOS 16.0, *) {
AddMuteItemView(state: damus_state)
.presentationDetents([.height(300)])
.presentationDragIndicator(.visible)
} else {
AddMuteItemView(state: damus_state)
}
AddMuteItemView(state: damus_state, new_text: $new_text)
.presentationDetents([.height(300)])
.presentationDragIndicator(.visible)
}
}
}

View File

@@ -21,13 +21,15 @@ struct EditMetadataView: View {
@State var ln: String
@State var website: String
@Environment(\.dismiss) var dismiss
@State var confirm_ln_address: Bool = false
@State var confirm_save_alert: Bool = false
@StateObject var profileUploadObserver = ImageUploadingObserver()
@StateObject var bannerUploadObserver = ImageUploadingObserver()
@Environment(\.dismiss) var dismiss
@Environment(\.presentationMode) var presentationMode
init(damus_state: DamusState) {
self.damus_state = damus_state
let profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey)
@@ -77,7 +79,7 @@ struct EditMetadataView: View {
var TopSection: some View {
ZStack(alignment: .top) {
GeometryReader { geo in
EditBannerImageView(damus_state: damus_state, viewModel: bannerUploadObserver, callback: uploadedBanner(image_url:))
EditBannerImageView(damus_state: damus_state, viewModel: bannerUploadObserver, callback: uploadedBanner(image_url:), banner_image: URL(string: banner))
.aspectRatio(contentMode: .fill)
.frame(width: geo.size.width, height: BANNER_HEIGHT)
.clipped()
@@ -86,7 +88,7 @@ struct EditMetadataView: View {
let pfp_size: CGFloat = 90.0
HStack(alignment: .center) {
EditProfilePictureView(pubkey: damus_state.pubkey, damus_state: damus_state, size: pfp_size, uploadObserver: profileUploadObserver, callback: uploadedProfilePicture(image_url:))
EditProfilePictureView(profile_url: URL(string: picture), pubkey: damus_state.pubkey, damus_state: damus_state, size: pfp_size, uploadObserver: profileUploadObserver, callback: uploadedProfilePicture(image_url:))
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
Spacer()
@@ -97,6 +99,28 @@ struct EditMetadataView: View {
}
}
func navImage(img: String) -> some View {
Image(img)
.frame(width: 33, height: 33)
.background(Color.black.opacity(0.6))
.clipShape(Circle())
}
var navBackButton: some View {
HStack {
Button {
if didChange() {
confirm_save_alert.toggle()
} else {
presentationMode.wrappedValue.dismiss()
}
} label: {
navImage(img: "chevron-left")
}
Spacer()
}
}
var body: some View {
VStack(alignment: .leading) {
TopSection
@@ -116,18 +140,6 @@ struct EditMetadataView: View {
}
Section (NSLocalizedString("Profile Picture", comment: "Label for Profile Picture section of user profile form.")) {
TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $picture)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section (NSLocalizedString("Banner Image", comment: "Label for Banner Image section of user profile form.")) {
TextField(NSLocalizedString("https://example.com/pic.jpg", comment: "Placeholder example text for profile picture URL."), text: $banner)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
}
Section(NSLocalizedString("Website", comment: "Label for Website section of user profile form.")) {
TextField(NSLocalizedString("https://jb55.com", comment: "Placeholder example text for website URL for user profile."), text: $website)
.autocorrectionDisabled(true)
@@ -139,10 +151,10 @@ struct EditMetadataView: View {
ZStack(alignment: .topLeading) {
TextEditor(text: $about)
.textInputAutocapitalization(.sentences)
.frame(minHeight: 20, alignment: .leading)
.frame(minHeight: 45, alignment: .leading)
.multilineTextAlignment(.leading)
Text(about.isEmpty ? placeholder : about)
.padding(.leading, 4)
.padding(4)
.opacity(about.isEmpty ? 1 : 0)
.foregroundColor(Color(uiColor: .placeholderText))
}
@@ -175,25 +187,48 @@ struct EditMetadataView: View {
}
})
Button(NSLocalizedString("Save", comment: "Button for saving profile.")) {
if !ln.isEmpty && !is_ln_valid(ln: ln) {
confirm_ln_address = true
} else {
save()
dismiss()
}
}
Button(action: {
if !ln.isEmpty && !is_ln_valid(ln: ln) {
confirm_ln_address = true
} else {
save()
dismiss()
}
.disabled(profileUploadObserver.isLoading || bannerUploadObserver.isLoading)
.alert(NSLocalizedString("Invalid Tip Address", comment: "Title of alerting as invalid tip address."), isPresented: $confirm_ln_address) {
Button(NSLocalizedString("Ok", comment: "Button to dismiss the alert.")) {
}
} message: {
Text("The address should either begin with LNURL or should look like an email address.", comment: "Giving the description of the alert message.")
}, label: {
Text(NSLocalizedString("Save", comment: "Button for saving profile."))
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
})
.buttonStyle(GradientButtonStyle(padding: 15))
.padding(.horizontal, 10)
.padding(.bottom, 10)
.disabled(!didChange())
.opacity(!didChange() ? 0.5 : 1)
.disabled(profileUploadObserver.isLoading || bannerUploadObserver.isLoading)
.alert(NSLocalizedString("Invalid Tip Address", comment: "Title of alerting as invalid tip address."), isPresented: $confirm_ln_address) {
Button(NSLocalizedString("Ok", comment: "Button to dismiss the alert.")) {
}
} message: {
Text("The address should either begin with LNURL or should look like an email address.", comment: "Giving the description of the alert message.")
}
}
.ignoresSafeArea(edges: .top)
.background(Color(.systemGroupedBackground))
.navigationBarBackButtonHidden()
.toolbar {
ToolbarItem(placement: .principal) {
navBackButton
}
}
.alert(NSLocalizedString("Discard changes?", comment: "Alert user that changes have been made."), isPresented: $confirm_save_alert) {
Button(NSLocalizedString("No", comment: "Do not discard changes."), role: .cancel) {
}
Button(NSLocalizedString("Yes", comment: "Agree to discard changes made to profile.")) {
dismiss()
}
}
}
func uploadedProfilePicture(image_url: URL?) {
@@ -203,6 +238,45 @@ struct EditMetadataView: View {
func uploadedBanner(image_url: URL?) {
banner = image_url?.absoluteString ?? ""
}
func didChange() -> Bool {
let profile_txn = damus_state.profiles.lookup(id: damus_state.pubkey)
let data = profile_txn?.unsafeUnownedValue
if data?.name ?? "" != name {
return true
}
if data?.display_name ?? "" != display_name {
return true
}
if data?.about ?? "" != about {
return true
}
if data?.website ?? "" != website {
return true
}
if data?.picture ?? "" != picture {
return true
}
if data?.banner ?? "" != banner {
return true
}
if data?.nip05 ?? "" != nip05 {
return true
}
if data?.lud16 ?? data?.lud06 ?? "" != ln {
return true
}
return false
}
}
struct EditMetadataView_Previews: PreviewProvider {

View File

@@ -18,6 +18,7 @@ struct EditPictureControl: View {
var size: CGFloat? = 25
var setup: Bool? = false
@Binding var image_url: URL?
@State var image_url_temp: URL?
@ObservedObject var uploadObserver: ImageUploadingObserver
let callback: (URL?) -> Void
@@ -25,12 +26,21 @@ struct EditPictureControl: View {
@State private var show_camera = false
@State private var show_library = false
@State private var show_url_sheet = false
@State var image_upload_confirm: Bool = false
@State var preUploadedMedia: PreUploadedMedia? = nil
@Environment(\.dismiss) var dismiss
var body: some View {
Menu {
Button(action: {
self.show_url_sheet = true
}) {
Text("Image URL", comment: "Option to enter a url")
}
Button(action: {
self.show_library = true
}) {
@@ -51,7 +61,7 @@ struct EditPictureControl: View {
.background(DamusColors.white.opacity(0.7))
.clipShape(Circle())
.shadow(color: DamusColors.purple, radius: 15, x: 0, y: 0)
} else if let url = image_url {
} else if let url = image_url, setup ?? false {
KFAnimatedImage(url)
.imageContext(.pfp, disable_animation: false)
.onFailure(fallbackUrl: URL(string: robohash(pubkey)), cacheKey: url.absoluteString)
@@ -115,6 +125,70 @@ struct EditPictureControl: View {
Button(NSLocalizedString("Cancel", comment: "Button to cancel the upload."), role: .cancel) {}
}
}
.sheet(isPresented: $show_url_sheet) {
ZStack {
DamusColors.adaptableWhite.edgesIgnoringSafeArea(.all)
VStack {
Text("Image URL")
.bold()
Divider()
.padding(.horizontal)
HStack {
Image(systemName: "doc.on.clipboard")
.foregroundColor(.gray)
.onTapGesture {
if let pastedURL = UIPasteboard.general.string {
image_url_temp = URL(string: pastedURL)
}
}
TextField(image_url_temp?.absoluteString ?? "", text: Binding(
get: { image_url_temp?.absoluteString ?? "" },
set: { image_url_temp = URL(string: $0) }
))
}
.padding(12)
.background {
RoundedRectangle(cornerRadius: 12)
.stroke(.gray.opacity(0.5), lineWidth: 1)
.background {
RoundedRectangle(cornerRadius: 12)
.foregroundColor(.damusAdaptableWhite)
}
}
.padding(10)
Button(action: {
show_url_sheet.toggle()
}, label: {
Text("Cancel", comment: "Cancel button text for dismissing updating image url.")
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
.padding(10)
})
.buttonStyle(NeutralButtonStyle())
.padding(10)
Button(action: {
image_url = image_url_temp
callback(image_url)
show_url_sheet.toggle()
}, label: {
Text("Update", comment: "Update button text for updating image url.")
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
})
.buttonStyle(GradientButtonStyle(padding: 10))
.padding(.horizontal, 10)
.disabled(image_url_temp == image_url)
.opacity(image_url_temp == image_url ? 0.5 : 1)
}
}
.onAppear {
image_url_temp = image_url
}
.presentationDetents([.height(300)])
.presentationDragIndicator(.visible)
}
}
private func handle_upload(media: MediaUpload) {

View File

@@ -45,6 +45,21 @@ struct ProfileActionSheetView: View {
)
}
var muteButton: some View {
let target_pubkey = self.profile.pubkey
return VStack(alignment: .center, spacing: 10) {
MuteDurationMenu { duration in
notify(.mute(.user(target_pubkey, duration?.date_from_now)))
} label: {
Image("mute")
}
.buttonStyle(NeutralButtonShape.circle.style)
Text("Mute", comment: "Button label that allows the user to mute the user shown on-screen")
.foregroundStyle(.secondary)
.font(.caption)
}
}
var dmButton: some View {
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
return VStack(alignment: .center, spacing: 10) {
@@ -103,6 +118,9 @@ struct ProfileActionSheetView: View {
self.followButton
self.zapButton
self.dmButton
if damus_state.keypair.pubkey != profile.pubkey && damus_state.keypair.privkey != nil {
self.muteButton
}
}
.padding()

View File

@@ -126,11 +126,11 @@ struct QRCodeView: View {
if our_profile?.picture != nil {
ProfilePicView(pubkey: pubkey, size: 90.0, highlight: .custom(DamusColors.white, 3.0), profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
.padding(.top, 50)
.padding(.top, 20)
} else {
Image(systemName: "person.fill")
.font(.system(size: 60))
.padding(.top, 50)
.padding(.top, 20)
}
if let display_name = profile?.display_name {
@@ -150,17 +150,18 @@ struct QRCodeView: View {
.interpolation(.none)
.resizable()
.scaledToFit()
.frame(width: 300, height: 300)
.frame(minWidth: 100, maxWidth: 300, minHeight: 100, maxHeight: 300)
.cornerRadius(10)
.overlay(RoundedRectangle(cornerRadius: 10)
.stroke(DamusColors.white, lineWidth: 5.0))
.stroke(DamusColors.white, lineWidth: 5.0)
.scaledToFit())
.shadow(radius: 10)
Spacer()
Text("Follow me on Nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")
.font(.system(size: 24, weight: .heavy))
.padding(.top)
.padding(.top, 10)
.foregroundColor(.white)
Text("Scan the code", comment: "Text on QR code view to prompt viewer to scan the QR code on screen with their device camera.")
@@ -179,7 +180,7 @@ struct QRCodeView: View {
.frame(minWidth: 300, maxWidth: .infinity, maxHeight: 12, alignment: .center)
}
.buttonStyle(GradientButtonStyle())
.padding(50)
.padding(20)
}
}
@@ -201,11 +202,11 @@ struct QRCodeView: View {
}
}
.scaledToFit()
.frame(width: 300, height: 300)
.frame(maxWidth: 300, maxHeight: 300)
.cornerRadius(10)
.overlay(RoundedRectangle(cornerRadius: 10).stroke(DamusColors.white, lineWidth: 5.0))
.overlay(RoundedRectangle(cornerRadius: 10).stroke(DamusColors.white, lineWidth: 5.0).scaledToFit())
.overlay(RoundedRectangle(cornerRadius: 10).trim(from: 0.0, to: outerTrimEnd).stroke(DamusColors.black, lineWidth: 5.5)
.rotationEffect(.degrees(-90)))
.rotationEffect(.degrees(-90)).scaledToFit())
.shadow(radius: 10)
Spacer()

View File

@@ -50,6 +50,13 @@ struct RelayView: View {
.padding(.bottom, 2)
.lineLimit(1)
RelayType(is_paid: state.relay_model_cache.model(with_relay_id: relay)?.metadata.is_paid ?? false)
if relay.absoluteString.hasSuffix(".onion") {
Image("tor")
.resizable()
.interpolation(.none)
.frame(width: 20, height: 20)
}
}
Text(relay.absoluteString)
.font(.subheadline)

View File

@@ -7,10 +7,13 @@
import SwiftUI
let MINIMUM_PUSH_NOTIFICATION_SYNC_DELAY_IN_SECONDS = 0.25
struct NotificationSettingsView: View {
let damus_state: DamusState
@ObservedObject var settings: UserSettingsStore
@State var notification_mode_setting_error: String? = nil
@State var notification_preferences_sync_state: PreferencesSyncState = .undefined
@Environment(\.dismiss) var dismiss
@@ -32,6 +35,7 @@ struct NotificationSettingsView: View {
Task {
do {
try await damus_state.push_notification_client.send_token()
await self.sync_up_remote_notification_settings()
settings.notifications_mode = new_value
}
catch {
@@ -44,6 +48,7 @@ struct NotificationSettingsView: View {
do {
try await damus_state.push_notification_client.revoke_token()
settings.notifications_mode = new_value
notification_preferences_sync_state = .not_applicable
}
catch {
notification_mode_setting_error = String(format: NSLocalizedString("Error disabling push notifications with the server: %@", comment: "Error label shown when user tries to disable push notifications but something fails"), error.localizedDescription)
@@ -52,6 +57,61 @@ struct NotificationSettingsView: View {
}
}
// MARK: - Push notification preference sync management
func notification_preference_binding<T>(_ raw_binding: Binding<T>) -> Binding<T> {
return Binding(
get: {
return raw_binding.wrappedValue
},
set: { new_value in
let old_value = raw_binding.wrappedValue
raw_binding.wrappedValue = new_value
if self.settings.notifications_mode == .push {
Task {
await self.send_push_notification_preferences(on_failure: {
raw_binding.wrappedValue = old_value
})
}
}
}
)
}
func sync_up_remote_notification_settings() async {
do {
notification_preferences_sync_state = .syncing
let remote_settings = try await damus_state.push_notification_client.get_settings()
let local_settings = PushNotificationClient.NotificationSettings.from(settings: settings)
if remote_settings != local_settings {
await self.send_push_notification_preferences(local_settings)
}
else {
notification_preferences_sync_state = .success
}
}
catch {
notification_preferences_sync_state = .failure(error: String(format: NSLocalizedString("Failed to get push notification preferences from the server", comment: "Error label indicating about a failure in fetching notification preferences"), error.localizedDescription))
}
}
func send_push_notification_preferences(_ new_settings: PushNotificationClient.NotificationSettings? = nil, on_failure: (() -> Void)? = nil) async {
do {
notification_preferences_sync_state = .syncing
try await damus_state.push_notification_client.set_settings(new_settings)
// Make sync appear to take at least a few milliseconds or so to avoid issues with labor perception bias (https://growth.design/case-studies/labor-perception-bias)
DispatchQueue.main.asyncAfter(deadline: .now() + MINIMUM_PUSH_NOTIFICATION_SYNC_DELAY_IN_SECONDS) {
notification_preferences_sync_state = .success
}
}
catch {
notification_preferences_sync_state = .failure(error: String(format: NSLocalizedString("Error syncing up push notifications preferences with the server: %@", comment: "Error label shown when system tries to sync up notification preferences to the push notification server but something fails"), error.localizedDescription))
on_failure?()
}
}
// MARK: - View layout
var body: some View {
Form {
if settings.enable_experimental_push_notifications {
@@ -80,21 +140,40 @@ struct NotificationSettingsView: View {
}
}
Section(header: Text("Local Notifications", comment: "Section header for damus local notifications user configuration")) {
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: $settings.zap_notification)
Section(
header: Text("Notification Preferences", comment: "Section header for Notification Preferences"),
footer: VStack {
switch notification_preferences_sync_state {
case .undefined, .not_applicable:
EmptyView()
case .success:
HStack {
Image("check-circle.fill")
.foregroundStyle(.damusGreen)
Text("Successfully synced", comment: "Label indicating success in syncing notification preferences")
}
case .syncing:
HStack(spacing: 10) {
ProgressView()
Text("Syncing", comment: "Label indicating success in syncing notification preferences")
}
case .failure(let error):
Text(error)
.foregroundStyle(.damusDangerPrimary)
}
}
) {
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: self.notification_preference_binding($settings.zap_notification))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: $settings.mention_notification)
Toggle(NSLocalizedString("Mentions", comment: "Setting to enable Mention Local Notification"), isOn: self.notification_preference_binding($settings.mention_notification))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Reposts", comment: "Setting to enable Repost Local Notification"), isOn: $settings.repost_notification)
Toggle(NSLocalizedString("Reposts", comment: "Setting to enable Repost Local Notification"), isOn: self.notification_preference_binding($settings.repost_notification))
.toggleStyle(.switch)
Toggle(NSLocalizedString("Likes", comment: "Setting to enable Like Local Notification"), isOn: $settings.like_notification)
Toggle(NSLocalizedString("Likes", comment: "Setting to enable Like Local Notification"), isOn: self.notification_preference_binding($settings.like_notification))
.toggleStyle(.switch)
Toggle(NSLocalizedString("DMs", comment: "Setting to enable DM Local Notification"), isOn: $settings.dm_notification)
Toggle(NSLocalizedString("DMs", comment: "Setting to enable DM Local Notification"), isOn: self.notification_preference_binding($settings.dm_notification))
.toggleStyle(.switch)
}
Section(header: Text("Notification Preference", comment: "Section header for Notification Preferences")) {
Toggle(NSLocalizedString("Show only from users you follow", comment: "Setting to Show notifications only associated to users your follow"), isOn: $settings.notification_only_from_following)
Toggle(NSLocalizedString("Show only from users you follow", comment: "Setting to Show notifications only associated to users your follow"), isOn: self.notification_preference_binding($settings.notification_only_from_following))
.toggleStyle(.switch)
}
@@ -113,6 +192,28 @@ struct NotificationSettingsView: View {
.onReceive(handle_notify(.switched_timeline)) { _ in
dismiss()
}
.onAppear(perform: {
Task {
if self.settings.notifications_mode == .push {
await self.sync_up_remote_notification_settings()
}
}
})
}
}
extension NotificationSettingsView {
enum PreferencesSyncState {
/// State is unknown
case undefined
/// State is not applicable (e.g. Notifications are set to local)
case not_applicable
/// Preferences are successfully synced
case success
/// Preferences are being synced
case syncing
/// There was a failure during syncing
case failure(error: String)
}
}

Binary file not shown.

View File

@@ -2,7 +2,7 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd">
<file original="damus/en-US.lproj/InfoPlist.strings" source-language="en-US" target-language="en-US" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.3" build-num="15E204a"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
@@ -44,7 +44,7 @@
</file>
<file original="damus/en-US.lproj/Localizable.strings" source-language="en-US" target-language="en-US" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.3" build-num="15E204a"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="%@ %@" xml:space="preserve">
@@ -144,11 +144,6 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>API Key (required)</target>
<note>Prompt for required entry of API Key to use translation server.</note>
</trans-unit>
<trans-unit id="About" xml:space="preserve">
<source>About</source>
<target>About</target>
<note>Label to prompt for about text entry for user to describe about themself.</note>
</trans-unit>
<trans-unit id="About Me" xml:space="preserve">
<source>About Me</source>
<target>About Me</target>
@@ -159,6 +154,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Absolute Boss</target>
<note>Placeholder text for About Me description.</note>
</trans-unit>
<trans-unit id="Absolute legend." xml:space="preserve">
<source>Absolute legend.</source>
<target>Absolute legend.</target>
<note>Example Bio</note>
</trans-unit>
<trans-unit id="Accessibility" xml:space="preserve">
<source>Accessibility</source>
<target>Accessibility</target>
@@ -189,6 +189,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Add Bookmark</target>
<note>Button text to add bookmark to a note.</note>
</trans-unit>
<trans-unit id="Add Photo" xml:space="preserve">
<source>Add Photo</source>
<target>Add Photo</target>
<note>Label to indicate user can add a photo.</note>
</trans-unit>
<trans-unit id="Add all" xml:space="preserve">
<source>Add all</source>
<target>Add all</target>
@@ -255,6 +260,11 @@ Button text to add a relay</note>
<target>An additional percentage of each zap will be sent to support Damus development</target>
<note>Text indicating that they can contribute zaps to support Damus development.</note>
</trans-unit>
<trans-unit id="An unexpected error happened while trying to create the new contact list. Please contact support." xml:space="preserve">
<source>An unexpected error happened while trying to create the new contact list. Please contact support.</source>
<target>An unexpected error happened while trying to create the new contact list. Please contact support.</target>
<note>Error message for a failed contact list reset operation</note>
</trans-unit>
<trans-unit id="Animations" xml:space="preserve">
<source>Animations</source>
<target>Animations</target>
@@ -351,10 +361,10 @@ Tip: You can always change this later in Settings → Translations</target>
<target>Be the first to access upcoming premium features: Automatic translations, longer note storage, and more</target>
<note>Description of new features to be expected</note>
</trans-unit>
<trans-unit id="Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus." xml:space="preserve">
<source>Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus.</source>
<target>Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus.</target>
<note>Reminder to user that they should save their account information.</note>
<trans-unit id="Bio" xml:space="preserve">
<source>Bio</source>
<target>Bio</target>
<note>Label to prompt bio entry for user to describe themself.</note>
</trans-unit>
<trans-unit id="Bitcoin Lightning Tips" xml:space="preserve">
<source>Bitcoin Lightning Tips</source>
@@ -383,10 +393,10 @@ Tip: You can always change this later in Settings → Translations</target>
<target>Broadcast music playing on Apple Music</target>
<note>Toggle to enable or disable broadcasting what music is being played on Apple Music in their profile status.</note>
</trans-unit>
<trans-unit id="By signing up, you agree to our " xml:space="preserve">
<source>By signing up, you agree to our </source>
<target>By signing up, you agree to our </target>
<note>Ask the user if they already have an account on Nostr</note>
<trans-unit id="By continuing you agree to our " xml:space="preserve">
<source>By continuing you agree to our </source>
<target>By continuing you agree to our </target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="By subscribing to Damus Purple, you are accepting our [privacy policy](https://damus.io/privacy-policy.txt) and Apple's Standard [EULA](https://www.apple.com/legal/internet-services/itunes/dev/stdeula/)" xml:space="preserve">
<source>By subscribing to Damus Purple, you are accepting our [privacy policy](https://damus.io/privacy-policy.txt) and Apple's Standard [EULA](https://www.apple.com/legal/internet-services/itunes/dev/stdeula/)</source>
@@ -413,7 +423,8 @@ Tip: You can always change this later in Settings → Translations</target>
Button to cancel the upload.
Cancel deleting bookmarks.
Cancel deleting the user.
Cancel out of logging out the user.</note>
Cancel out of logging out the user.
Cancel resetting the contact list.</note>
</trans-unit>
<trans-unit id="Changing this setting will cause the cache to be cleared. This will free space, but images may take longer to load again. Are you sure you want to proceed?" xml:space="preserve">
<source>Changing this setting will cause the cache to be cleared. This will free space, but images may take longer to load again. Are you sure you want to proceed?</source>
@@ -486,6 +497,16 @@ Button to connect to the relay.</note>
<target>Connecting</target>
<note>Relay status label that indicates a relay is connecting.</note>
</trans-unit>
<trans-unit id="Contact list (Follows + Relay list)" xml:space="preserve">
<source>Contact list (Follows + Relay list)</source>
<target>Contact list (Follows + Relay list)</target>
<note>Section title for Contact list first aid tools</note>
</trans-unit>
<trans-unit id="Contact list has been reset" xml:space="preserve">
<source>Contact list has been reset</source>
<target>Contact list has been reset</target>
<note>Message indicating that the contact list was successfully reset.</note>
</trans-unit>
<trans-unit id="Content filters" xml:space="preserve">
<source>Content filters</source>
<target>Content filters</target>
@@ -495,7 +516,8 @@ Button to connect to the relay.</note>
<source>Continue</source>
<target>Continue</target>
<note>Continue with bookmarks.
Continue with deleting the user.</note>
Continue with deleting the user.
Continue with resetting the contact list.</note>
</trans-unit>
<trans-unit id="Copied" xml:space="preserve">
<source>Copied</source>
@@ -569,6 +591,11 @@ Button to connect to the relay.</note>
<target>Copy user public key</target>
<note>Context menu option for copying the ID of the user who created the note.</note>
</trans-unit>
<trans-unit id="Could not create your initial contact list event. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help." xml:space="preserve">
<source>Could not create your initial contact list event. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help.</source>
<target>Could not create your initial contact list event. This is a software bug, please contact Damus support via support@damus.io or through our Nostr account for help.</target>
<note>Error message to the user indicating that the initial contact list failed to be created.</note>
</trans-unit>
<trans-unit id="Could not find the user you're looking for" xml:space="preserve">
<source>Could not find the user you're looking for</source>
<target>Could not find the user you're looking for</target>
@@ -579,26 +606,21 @@ Button to connect to the relay.</note>
<target>Could not find user to mute...</target>
<note>Alert message to indicate that the muted user could not be found.</note>
</trans-unit>
<trans-unit id="Create Account" xml:space="preserve">
<source>Create Account</source>
<target>Create Account</target>
<note>Button to continue to the create account page.</note>
</trans-unit>
<trans-unit id="Create account" xml:space="preserve">
<source>Create account</source>
<target>Create account</target>
<note>Button to navigate to create account view.</note>
</trans-unit>
<trans-unit id="Create account now" xml:space="preserve">
<source>Create account now</source>
<target>Create account now</target>
<note>Button to create account.</note>
</trans-unit>
<trans-unit id="Create new mutelist" xml:space="preserve">
<source>Create new mutelist</source>
<target>Create new mutelist</target>
<note>Title of alert prompting the user to create a new mutelist.</note>
</trans-unit>
<trans-unit id="Creator(s) of Bitcoin. Absolute legend." xml:space="preserve">
<source>Creator(s) of Bitcoin. Absolute legend.</source>
<target>Creator(s) of Bitcoin. Absolute legend.</target>
<note>Example description about Bitcoin creator(s), Satoshi Nakamoto.</note>
</trans-unit>
<trans-unit id="Custom" xml:space="preserve">
<source>Custom</source>
<target>Custom</target>
@@ -609,6 +631,7 @@ Button to connect to the relay.</note>
<target>DMs</target>
<note>Navigation title for DMs view, where DM is the English abbreviation for Direct Message.
Navigation title for view of DMs, where DM is an English abbreviation for Direct Message.
Picker option for DM selector for seeing only DMs that have been responded to. DM is the English abbreviation for Direct Message.
Setting to enable DM Local Notification
Toolbar label for DMs view, where DM is the English abbreviation for Direct Message.</note>
</trans-unit>
@@ -695,11 +718,6 @@ Button to disconnect from a relay server.</note>
<target>Dismiss</target>
<note>Button to dismiss alert</note>
</trans-unit>
<trans-unit id="Display name" xml:space="preserve">
<source>Display name</source>
<target>Display name</target>
<note>Label to prompt display name entry.</note>
</trans-unit>
<trans-unit id="Done" xml:space="preserve">
<source>Done</source>
<target>Done</target>
@@ -766,6 +784,16 @@ Button to disconnect from a relay server.</note>
<note>Label to display that authentication to a server has failed.
Relay status label that indicates a relay had an error when connecting</note>
</trans-unit>
<trans-unit id="Error configuring push notifications with the server: %@" xml:space="preserve">
<source>Error configuring push notifications with the server: %@</source>
<target>Error configuring push notifications with the server: %@</target>
<note>Error label shown when user tries to enable push notifications but something fails</note>
</trans-unit>
<trans-unit id="Error disabling push notifications with the server: %@" xml:space="preserve">
<source>Error disabling push notifications with the server: %@</source>
<target>Error disabling push notifications with the server: %@</target>
<note>Error label shown when user tries to disable push notifications but something fails</note>
</trans-unit>
<trans-unit id="Error fetching lightning invoice" xml:space="preserve">
<source>Error fetching lightning invoice</source>
<target>Error fetching lightning invoice</target>
@@ -816,6 +844,12 @@ Relay status label that indicates a relay had an error when connecting</note>
<target>Failed to parse</target>
<note>NostrScript error message when it fails to parse a script.</note>
</trans-unit>
<trans-unit id="First Aid" xml:space="preserve">
<source>First Aid</source>
<target>First Aid</target>
<note>Navigation title for first aid settings and tools
Section header for first aid tools and settings</note>
</trans-unit>
<trans-unit id="Follow" xml:space="preserve">
<source>Follow</source>
<target>Follow</target>
@@ -907,6 +941,11 @@ My side interests include languages and I am striving to be a #polyglot - I am a
<target>Free</target>
<note>Dropdown option for selecting Free plan for DeepL translation service.</note>
</trans-unit>
<trans-unit id="General" xml:space="preserve">
<source>General</source>
<target>General</target>
<note>Section header for general damus notifications user configuration</note>
</trans-unit>
<trans-unit id="Get API Key" xml:space="preserve">
<source>Get API Key</source>
<target>Get API Key</target>
@@ -926,7 +965,8 @@ My side interests include languages and I am striving to be a #polyglot - I am a
<trans-unit id="Hashtags" xml:space="preserve">
<source>Hashtags</source>
<target>Hashtags</target>
<note>Section header title for a list of hashtags that are muted.</note>
<note>Label for filter for seeing only hashtag follows.
Section header title for a list of hashtags that are muted.</note>
</trans-unit>
<trans-unit id="Hello everybody!&#10;&#10;This is my first post on Damus, I am happy to meet you all 🤙. Whats up?&#10;&#10;#introductions" xml:space="preserve">
<source>Hello everybody!
@@ -971,6 +1011,16 @@ This is my first post on Damus, I am happy to meet you all 🤙. Whats up?
<target>Hide notes with #nsfw tags</target>
<note>Setting to hide notes with the #nsfw (not safe for work) tags</note>
</trans-unit>
<trans-unit id="Highlighted" xml:space="preserve">
<source>Highlighted</source>
<target>Highlighted</target>
<note>Label to indicate that the user is highlighting their own post.</note>
</trans-unit>
<trans-unit id="Highlighted %@" xml:space="preserve">
<source>Highlighted %@</source>
<target>Highlighted %@</target>
<note>Label to indicate that the user is highlighting 1 user.</note>
</trans-unit>
<trans-unit id="Home" xml:space="preserve">
<source>Home</source>
<target>Home</target>
@@ -1005,6 +1055,11 @@ Hope to meet folks who are on their own journeys to a peaceful and free life!</t
<target>Impersonation</target>
<note>Description of report type for impersonation.</note>
</trans-unit>
<trans-unit id="In progress…" xml:space="preserve">
<source>In progress…</source>
<target>In progress…</target>
<note>Loading message indicating that a contact list reset operation is in progress.</note>
</trans-unit>
<trans-unit id="Indefinite" xml:space="preserve">
<source>Indefinite</source>
<target>Indefinite</target>
@@ -1051,11 +1106,6 @@ Hope to meet folks who are on their own journeys to a peaceful and free life!</t
<target>LIVE</target>
<note>Text indicator that the video is a livestream.</note>
</trans-unit>
<trans-unit id="Learn more about Nostr" xml:space="preserve">
<source>Learn more about Nostr</source>
<target>Learn more about Nostr</target>
<note>Button that opens up a webpage where the user can learn more about Nostr.</note>
</trans-unit>
<trans-unit id="Learn more about the features" xml:space="preserve">
<source>Learn more about the features</source>
<target>Learn more about the features</target>
@@ -1066,16 +1116,6 @@ Hope to meet folks who are on their own journeys to a peaceful and free life!</t
<target>Left Handed</target>
<note>Moves the post button to the left side of the screen</note>
</trans-unit>
<trans-unit id="Let's get started!" xml:space="preserve">
<source>Let's get started!</source>
<target>Let's get started!</target>
<note>Button to continue to login page.</note>
</trans-unit>
<trans-unit id="Let's go!" xml:space="preserve">
<source>Let's go!</source>
<target>Let's go!</target>
<note>Button to complete account creation and start using the app.</note>
</trans-unit>
<trans-unit id="LibreTranslate (Open Source)" xml:space="preserve">
<source>LibreTranslate (Open Source)</source>
<target>LibreTranslate (Open Source)</target>
@@ -1106,6 +1146,11 @@ Hope to meet folks who are on their own journeys to a peaceful and free life!</t
<target>Load media</target>
<note>Button to show media in note.</note>
</trans-unit>
<trans-unit id="Local" xml:space="preserve">
<source>Local</source>
<target>Local</target>
<note>Option for notification mode setting: Local notification mode</note>
</trans-unit>
<trans-unit id="Local Notifications" xml:space="preserve">
<source>Local Notifications</source>
<target>Local Notifications</target>
@@ -1175,7 +1220,8 @@ Hope to meet folks who are on their own journeys to a peaceful and free life!</t
<trans-unit id="Mentions" xml:space="preserve">
<source>Mentions</source>
<target>Mentions</target>
<note>Setting to enable Mention Local Notification</note>
<note>Label for filter for seeing mention notifications (replies, etc).
Setting to enable Mention Local Notification</note>
</trans-unit>
<trans-unit id="Merch" xml:space="preserve">
<source>Merch</source>
@@ -1195,7 +1241,9 @@ Hope to meet folks who are on their own journeys to a peaceful and free life!</t
<trans-unit id="Mute" xml:space="preserve">
<source>Mute</source>
<target>Mute</target>
<note>Alert button to mute a user.</note>
<note>Alert button to mute a user.
Button to mute a profile
Title for confirmation dialog to mute a profile.</note>
</trans-unit>
<trans-unit id="Mute %@?" xml:space="preserve">
<source>Mute %@?</source>
@@ -1235,6 +1283,11 @@ Hope to meet folks who are on their own journeys to a peaceful and free life!</t
Text label indicating that there is no NIP-11 relay description information found. In English, N/A stands for not applicable.
Text label indicating that there is no NIP-11 relay software information found. In English, N/A stands for not applicable.</note>
</trans-unit>
<trans-unit id="Name" xml:space="preserve">
<source>Name</source>
<target>Name</target>
<note>Label to prompt name entry.</note>
</trans-unit>
<trans-unit id="Never" xml:space="preserve">
<source>Never</source>
<target>Never</target>
@@ -1260,11 +1313,21 @@ Text label indicating that there is no NIP-11 relay software information found.
<target>New to Nostr?</target>
<note>Ask the user if they are new to Nostr</note>
</trans-unit>
<trans-unit id="Next" xml:space="preserve">
<source>Next</source>
<target>Next</target>
<note>Button to continue with account creation.</note>
</trans-unit>
<trans-unit id="No" xml:space="preserve">
<source>No</source>
<target>No</target>
<note>User confirm No</note>
</trans-unit>
<trans-unit id="No contact list was found. You might experience issues using the app. If you suspect you have permanently lost your contact list (or if you never had one), you can fix this by resetting it" xml:space="preserve">
<source>No contact list was found. You might experience issues using the app. If you suspect you have permanently lost your contact list (or if you never had one), you can fix this by resetting it</source>
<target>No contact list was found. You might experience issues using the app. If you suspect you have permanently lost your contact list (or if you never had one), you can fix this by resetting it</target>
<note>Section footer for Contact list first aid tools</note>
</trans-unit>
<trans-unit id="No logs to display" xml:space="preserve">
<source>No logs to display</source>
<target>No logs to display</target>
@@ -1310,11 +1373,6 @@ Text label indicating that there is no NIP-11 relay software information found.
<target>Nostr Address</target>
<note>Label for the Nostr Address section of user profile form.</note>
</trans-unit>
<trans-unit id="Nostr is a protocol, designed for simplicity, that aims to create a censorship-resistant global social network" xml:space="preserve">
<source>Nostr is a protocol, designed for simplicity, that aims to create a censorship-resistant global social network</source>
<target>Nostr is a protocol, designed for simplicity, that aims to create a censorship-resistant global social network</target>
<note>Description about what is Nostr.</note>
</trans-unit>
<trans-unit id="NostrScript" xml:space="preserve">
<source>NostrScript</source>
<target>NostrScript</target>
@@ -1325,6 +1383,11 @@ Text label indicating that there is no NIP-11 relay software information found.
<target>NostrScript Error</target>
<note>Text indicating that there was an error with loading NostrScript. There is a more descriptive error message shown separately underneath.</note>
</trans-unit>
<trans-unit id="Not now" xml:space="preserve">
<source>Not now</source>
<target>Not now</target>
<note>Button to not save key, complete account creation, and start using the app.</note>
</trans-unit>
<trans-unit id="Note from a %@ you've muted" xml:space="preserve">
<source>Note from a %@ you've muted</source>
<target>Note from a %@ you've muted</target>
@@ -1333,20 +1396,18 @@ Text label indicating that there is no NIP-11 relay software information found.
<trans-unit id="Note you've muted" xml:space="preserve">
<source>Note you've muted</source>
<target>Note you've muted</target>
<note>Text to indicate that what is being shown is a note which has been muted.</note>
<note>Text to indicate that what is being shown is a note which has been muted.
Label indicating note has been muted</note>
</trans-unit>
<trans-unit id="Notes" xml:space="preserve">
<source>Notes</source>
<target>Notes</target>
<note>Label for filter for seeing only notes (instead of notes and replies).
Label for filter for seeing only your notes (instead of notes and replies).
A label indicating that the notes being displayed below it are from a timeline, not search results</note>
<note>Label for filter for seeing only notes (instead of notes and replies).</note>
</trans-unit>
<trans-unit id="Notes &amp; Replies" xml:space="preserve">
<source>Notes &amp; Replies</source>
<target>Notes &amp; Replies</target>
<note>Label for filter for seeing notes and replies (instead of only notes).
Label for filter for seeing your notes and replies (instead of only your notes).</note>
<note>Label for filter for seeing notes and replies (instead of only notes).</note>
</trans-unit>
<trans-unit id="Notes with the #nsfw tag usually contains adult content or other &quot;Not safe for work&quot; content" xml:space="preserve">
<source>Notes with the #nsfw tag usually contains adult content or other "Not safe for work" content</source>
@@ -1374,6 +1435,11 @@ Label for filter for seeing your notes and replies (instead of only your notes).
<note>Section header for Damus notifications
Toolbar label for Notifications view.</note>
</trans-unit>
<trans-unit id="Notifications mode" xml:space="preserve">
<source>Notifications mode</source>
<target>Notifications mode</target>
<note>Prompt selection of the notification mode (Feature to switch between local notifications (generated from user's own phone) or push notifications (generated by Damus server).</note>
</trans-unit>
<trans-unit id="Nudity" xml:space="preserve">
<source>Nudity</source>
<target>Nudity</target>
@@ -1430,6 +1496,11 @@ Button label to dismiss an error dialog</note>
<target>Optional</target>
<note>Prompt to enter optional additional information when reporting an account or content.</note>
</trans-unit>
<trans-unit id="Orange-pill" xml:space="preserve">
<source>Orange-pill</source>
<target>Orange-pill</target>
<note>Button label that allows the user to start a direct message conversation with the user shown on-screen, to orange-pill them (i.e. help them to setup zaps)</note>
</trans-unit>
<trans-unit id="Paid Relay" xml:space="preserve">
<source>Paid Relay</source>
<target>Paid Relay</target>
@@ -1478,7 +1549,8 @@ Button label to dismiss an error dialog</note>
<trans-unit id="Post" xml:space="preserve">
<source>Post</source>
<target>Post</target>
<note>Button to post a note.</note>
<note>Button to post a highlight.
Button to post a note.</note>
</trans-unit>
<trans-unit id="Private" xml:space="preserve">
<source>Private</source>
@@ -1540,11 +1612,6 @@ Button label to dismiss an error dialog</note>
<target>Public Account ID</target>
<note>Section title for the user's public account ID.</note>
</trans-unit>
<trans-unit id="Public Key" xml:space="preserve">
<source>Public Key</source>
<target>Public Key</target>
<note>Label to indicate the public key of the account.</note>
</trans-unit>
<trans-unit id="Public key" xml:space="preserve">
<source>Public key</source>
<target>Public key</target>
@@ -1570,6 +1637,11 @@ Button label to dismiss an error dialog</note>
<target>Purple</target>
<note>Subscription service name</note>
</trans-unit>
<trans-unit id="Push" xml:space="preserve">
<source>Push</source>
<target>Push</target>
<note>Option for notification mode setting: Push notification mode</note>
</trans-unit>
<trans-unit id="QR Code" xml:space="preserve">
<source>QR Code</source>
<target>QR Code</target>
@@ -1590,6 +1662,11 @@ Button label to dismiss an error dialog</note>
<target>Ran to suspension.</target>
<note>Indication that a NostrScript was run until it reached a suspended state.</note>
</trans-unit>
<trans-unit id="React with default reaction emoji" xml:space="preserve">
<source>React with default reaction emoji</source>
<target>React with default reaction emoji</target>
<note>Accessibility label for react button</note>
</trans-unit>
<trans-unit id="Reactions" xml:space="preserve">
<source>Reactions</source>
<target>Reactions</target>
@@ -1700,6 +1777,11 @@ Button label to dismiss an error dialog</note>
<target>Repost</target>
<note>Button to repost a note</note>
</trans-unit>
<trans-unit id="Repost or quote this note" xml:space="preserve">
<source>Repost or quote this note</source>
<target>Repost or quote this note</target>
<note>Accessibility label for repost/quote button</note>
</trans-unit>
<trans-unit id="Reposted" xml:space="preserve">
<source>Reposted</source>
<target>Reposted</target>
@@ -1720,7 +1802,12 @@ Button label to dismiss an error dialog</note>
<trans-unit id="Requests" xml:space="preserve">
<source>Requests</source>
<target>Requests</target>
<note>Picker option for DM selector for seeing only message requests (DMs that someone else sent the user which has not been responded to yet). DM is the English abbreviation for Direct Message.</note>
<note>Picker option for DM selector for seeing only message requests (DMs that someone else sent the user which has not been responded to yet</note>
</trans-unit>
<trans-unit id="Reset contact list" xml:space="preserve">
<source>Reset contact list</source>
<target>Reset contact list</target>
<note>Button to reset contact list.</note>
</trans-unit>
<trans-unit id="Retry" xml:space="preserve">
<source>Retry</source>
@@ -1777,6 +1864,11 @@ Button label to dismiss an error dialog</note>
<target>Save Key in Secure Keychain</target>
<note>Toggle to save private key to the Apple secure keychain.</note>
</trans-unit>
<trans-unit id="Save your login info?" xml:space="preserve">
<source>Save your login info?</source>
<target>Save your login info?</target>
<note>Ask user if they want to save their account information.</note>
</trans-unit>
<trans-unit id="Scan Code" xml:space="preserve">
<source>Scan Code</source>
<target>Scan Code</target>
@@ -1907,6 +1999,11 @@ Button label to dismiss an error dialog</note>
<target>Share Via...</target>
<note>Button to present iOS share sheet</note>
</trans-unit>
<trans-unit id="Share externally" xml:space="preserve">
<source>Share externally</source>
<target>Share externally</target>
<note>Accessibility label for external share button</note>
</trans-unit>
<trans-unit id="Show" xml:space="preserve">
<source>Show</source>
<target>Show</target>
@@ -1954,6 +2051,11 @@ Button label to dismiss an error dialog</note>
<target>Show wallet selector</target>
<note>Toggle to show or hide selection of wallet.</note>
</trans-unit>
<trans-unit id="Sign In" xml:space="preserve">
<source>Sign In</source>
<target>Sign In</target>
<note>Button to continue to login page.</note>
</trans-unit>
<trans-unit id="Sign Out" xml:space="preserve">
<source>Sign Out</source>
<target>Sign Out</target>
@@ -1974,11 +2076,6 @@ Button label to dismiss an error dialog</note>
<target>Skip</target>
<note>Button to dismiss the suggested users screen</note>
</trans-unit>
<trans-unit id="Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken" xml:space="preserve">
<source>Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken</source>
<target>Social media has developed into a key way information flows around the world. Unfortunately, our current social media systems are broken</target>
<note>Description about why Nostr is needed.</note>
</trans-unit>
<trans-unit id="Someone posted a note" xml:space="preserve">
<source>Someone posted a note</source>
<target>Someone posted a note</target>
@@ -2087,11 +2184,6 @@ Enjoy!</target>
<target>The address should either begin with LNURL or should look like an email address.</target>
<note>Giving the description of the alert message.</note>
</trans-unit>
<trans-unit id="The go-to iOS Nostr client" xml:space="preserve">
<source>The go-to iOS Nostr client</source>
<target>The go-to iOS Nostr client</target>
<note>Quick description of what Damus is</note>
</trans-unit>
<trans-unit id="The relay you are trying to add is already added.&#10;You're all set!" xml:space="preserve">
<source>The relay you are trying to add is already added.
You're all set!</source>
@@ -2099,6 +2191,11 @@ You're all set!</source>
You're all set!</target>
<note>An error message that appears when the user attempts to add a relay that has already been added.</note>
</trans-unit>
<trans-unit id="The social network you control" xml:space="preserve">
<source>The social network you control</source>
<target>The social network you control</target>
<note>Quick description of what Damus is</note>
</trans-unit>
<trans-unit id="There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@" xml:space="preserve">
<source>There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@</source>
<target>There has been an unexpected error with the in-app purchase. Please try again later or contact support@damus.io. Error: %@</target>
@@ -2128,16 +2225,16 @@ Nice to meet you all! #introductions #plebchain </source>
Nice to meet you all! #introductions #plebchain </target>
<note>First post example given to the user during onboarding, as a suggestion as to what they could post first</note>
</trans-unit>
<trans-unit id="This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!" xml:space="preserve">
<source>This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!</source>
<target>This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!</target>
<note>Label to describe that a private key is the user's secret account key and what they should do with it.</note>
</trans-unit>
<trans-unit id="This note contains too many items and cannot be rendered" xml:space="preserve">
<source>This note contains too many items and cannot be rendered</source>
<target>This note contains too many items and cannot be rendered</target>
<note>Error message indicating that a note is too big and cannot be rendered</note>
</trans-unit>
<trans-unit id="This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?" xml:space="preserve">
<source>This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?</source>
<target>This user cannot be zapped because they have not configured zaps on their account yet. Time to orange-pill?</target>
<note>Comment explaining why a user cannot be zapped.</note>
</trans-unit>
<trans-unit id="Thread" xml:space="preserve">
<source>Thread</source>
<target>Thread</target>
@@ -2275,6 +2372,11 @@ Nice to meet you all! #introductions #plebchain </target>
<target>User muted</target>
<note>Alert message to indicate the user has been muted</note>
</trans-unit>
<trans-unit id="User not zappable" xml:space="preserve">
<source>User not zappable</source>
<target>User not zappable</target>
<note>Headline indicating a user cannot be zapped</note>
</trans-unit>
<trans-unit id="Username" xml:space="preserve">
<source>Username</source>
<target>Username</target>
@@ -2342,6 +2444,15 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
ARE YOU SURE YOU WANT TO CONTINUE?</target>
<note>Alert for deleting the users account.</note>
</trans-unit>
<trans-unit id="WARNING:&#10;&#10;This will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY." xml:space="preserve">
<source>WARNING:
This will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.</source>
<target>WARNING:
This will reset your contact list, including the list of everyone you follow and the list of all relays you usually connect to. ONLY PROCEED IF YOU ARE SURE YOU HAVE LOST YOUR CONTACT LIST BEYOND RECOVERABILITY.</target>
<note>Alert for resetting the user's contact list.</note>
</trans-unit>
<trans-unit id="Wake up, %@" xml:space="preserve">
<source>Wake up, %@</source>
<target>Wake up, %@</target>
@@ -2365,6 +2476,16 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
<target>Wallet Relay</target>
<note>Label text indicating that below it is the information about the wallet relay.</note>
</trans-unit>
<trans-unit id="We did not detect any issues that we can automatically fix for you. If you are having issues, please contact Damus support: [support@damus.io](mailto:support@damus.io)" xml:space="preserve">
<source>We did not detect any issues that we can automatically fix for you. If you are having issues, please contact Damus support: [support@damus.io](mailto:support@damus.io)</source>
<target>We did not detect any issues that we can automatically fix for you. If you are having issues, please contact Damus support: [support@damus.io](mailto:support@damus.io)</target>
<note>Message indicating that no First Aid actions are available.</note>
</trans-unit>
<trans-unit id="We'll save your account key, so you won't need to enter it manually next time you log in." xml:space="preserve">
<source>We'll save your account key, so you won't need to enter it manually next time you log in.</source>
<target>We'll save your account key, so you won't need to enter it manually next time you log in.</target>
<note>Reminder to user that they should save their account information.</note>
</trans-unit>
<trans-unit id="Website" xml:space="preserve">
<source>Website</source>
<target>Website</target>
@@ -2405,21 +2526,11 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
<target>What do you want to report?</target>
<note>Header text to prompt user what issue they want to report.</note>
</trans-unit>
<trans-unit id="What is Nostr?" xml:space="preserve">
<source>What is Nostr?</source>
<target>What is Nostr?</target>
<note>Heading text for section describing what is Nostr.</note>
</trans-unit>
<trans-unit id="Who to Follow" xml:space="preserve">
<source>Who to Follow</source>
<target>Who to Follow</target>
<note>Title for a screen displaying suggestions of who to follow</note>
</trans-unit>
<trans-unit id="Why we need Nostr?" xml:space="preserve">
<source>Why we need Nostr?</source>
<target>Why we need Nostr?</target>
<note>Heading text for section describing why Nostr is needed.</note>
</trans-unit>
<trans-unit id="Words" xml:space="preserve">
<source>Words</source>
<target>Words</target>
@@ -2530,7 +2641,8 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
<trans-unit id="Zaps" xml:space="preserve">
<source>Zaps</source>
<target>Zaps</target>
<note>Navigation bar title for the Zaps view.
<note>Label for filter for zap notifications.
Navigation bar title for the Zaps view.
Navigation title for zap settings.
Section header for zap settings
Setting to enable Zap Local Notification
@@ -2647,6 +2759,11 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
<target>%@ and %@ reposted your profile</target>
<note>Notification that 2 users reposted the current user's profile</note>
</trans-unit>
<trans-unit id="required" xml:space="preserve">
<source>required</source>
<target>required</target>
<note>Label indicating that a form input is required.</note>
</trans-unit>
<trans-unit id="self" xml:space="preserve">
<source>self</source>
<target>self</target>
@@ -2701,7 +2818,7 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
</file>
<file original="damus/en-US.lproj/Localizable.stringsdict" source-language="en-US" target-language="en-US" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.3" build-num="15E204a"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="/followed_by_three_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
@@ -3083,7 +3200,7 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
</file>
<file original="DamusNotificationService/InfoPlist.xcstrings" source-language="en-US" target-language="en-US" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.3" build-num="15E204a"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="CFBundleDisplayName" xml:space="preserve">
@@ -3105,7 +3222,7 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
</file>
<file original="DamusNotificationService/Localizable.xcstrings" source-language="en-US" target-language="en-US" datatype="plaintext">
<header>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.3" build-num="15E204a"/>
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="15.4" build-num="15F31d"/>
</header>
<body>
<trans-unit id="" xml:space="preserve">
@@ -3158,6 +3275,11 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
<target state="new">LibreTranslate (Open Source)</target>
<note>Dropdown option for selecting LibreTranslate as the translation service.</note>
</trans-unit>
<trans-unit id="Local" xml:space="preserve">
<source>Local</source>
<target state="new">Local</target>
<note>Option for notification mode setting: Local notification mode</note>
</trans-unit>
<trans-unit id="Local default" xml:space="preserve">
<source>Local default</source>
<target state="new">Local default</target>
@@ -3168,6 +3290,11 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
<target state="new">Mentioned by %@</target>
<note>Mentioned by heading in local notification</note>
</trans-unit>
<trans-unit id="Muted event" xml:space="preserve">
<source>Muted event</source>
<target state="new">Muted event</target>
<note>Title for a push notification which has been muted</note>
</trans-unit>
<trans-unit id="New encrypted direct message" xml:space="preserve">
<source>New encrypted direct message</source>
<target state="new">New encrypted direct message</target>
@@ -3203,6 +3330,11 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
<target state="new">Production</target>
<note>Label indicating the production environment for Damus Purple</note>
</trans-unit>
<trans-unit id="Push" xml:space="preserve">
<source>Push</source>
<target state="new">Push</target>
<note>Option for notification mode setting: Push notification mode</note>
</trans-unit>
<trans-unit id="Reposted by %@" xml:space="preserve">
<source>Reposted by %@</source>
<target state="new">Reposted by %@</target>
@@ -3238,6 +3370,11 @@ YOU WILL NO LONGER BE ABLE TO LOG INTO DAMUS USING THIS ACCOUNT KEY.
<target state="new">Test (local)</target>
<note>Label indicating a local test environment for Damus Purple functionality (Developer feature)</note>
</trans-unit>
<trans-unit id="This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences" xml:space="preserve">
<source>This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences</source>
<target state="new">This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences</target>
<note>Description for a push notification which has been muted, and explanation that we cannot suppress it</note>
</trans-unit>
<trans-unit id="This note contains too many items and cannot be rendered" xml:space="preserve">
<source>This note contains too many items and cannot be rendered</source>
<target state="new">This note contains too many items and cannot be rendered</target>

View File

@@ -39,12 +39,18 @@
"LibreTranslate (Open Source)" : {
"comment" : "Dropdown option for selecting LibreTranslate as the translation service."
},
"Local" : {
"comment" : "Option for notification mode setting: Local notification mode"
},
"Local default" : {
"comment" : "Dropdown option label for system default for Lightning wallet."
},
"Mentioned by %@" : {
"comment" : "Mentioned by heading in local notification"
},
"Muted event" : {
"comment" : "Title for a push notification which has been muted"
},
"New encrypted direct message" : {
"comment" : "Notification that the user has received a new direct message"
},
@@ -78,6 +84,9 @@
"Production" : {
"comment" : "Label indicating the production environment for Damus Purple"
},
"Push" : {
"comment" : "Option for notification mode setting: Push notification mode"
},
"Reposted by %@" : {
"comment" : "Reposted by heading in local notification"
},
@@ -99,6 +108,9 @@
"Test (local)" : {
"comment" : "Label indicating a local test environment for Damus Purple functionality (Developer feature)"
},
"This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences" : {
"comment" : "Description for a push notification which has been muted, and explanation that we cannot suppress it"
},
"This note contains too many items and cannot be rendered" : {
"comment" : "Error message indicating that a note is too big and cannot be rendered"
},

View File

@@ -3,10 +3,10 @@
"project" : "damus.xcodeproj",
"targetLocale" : "en-US",
"toolInfo" : {
"toolBuildNumber" : "15E204a",
"toolBuildNumber" : "15F31d",
"toolID" : "com.apple.dt.xcode",
"toolName" : "Xcode",
"toolVersion" : "15.3"
"toolVersion" : "15.4"
},
"version" : "1.0"
}

View File

@@ -226,6 +226,22 @@
<string>Megosztások</string>
</dict>
</dict>
<key>quoted_reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@QUOTE_REPOSTS@</string>
<key>QUOTE_REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Idézet</string>
<key>other</key>
<string>Idézetek</string>
</dict>
</dict>
<key>sats</key>
<dict>
<key>NSStringLocalizedFormatKey</key>

View File

@@ -198,6 +198,20 @@
<string>リポスト</string>
</dict>
</dict>
<key>quoted_reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@QUOTE_REPOSTS@</string>
<key>QUOTE_REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>引用</string>
</dict>
</dict>
<key>sats</key>
<dict>
<key>NSStringLocalizedFormatKey</key>

Binary file not shown.

View File

@@ -226,6 +226,22 @@
<string>Delningar</string>
</dict>
</dict>
<key>quoted_reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@QUOTE_REPOSTS@</string>
<key>QUOTE_REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Citat</string>
<key>other</key>
<string>Citat</string>
</dict>
</dict>
<key>sats</key>
<dict>
<key>NSStringLocalizedFormatKey</key>

Binary file not shown.