Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2020-04-27 16:47:56 +04:00
commit 115c896a96
17 changed files with 2822 additions and 2743 deletions

View File

@ -3,7 +3,7 @@
include Utils.makefile
APP_VERSION="6.1"
APP_VERSION="6.1.1"
CORE_COUNT=$(shell sysctl -n hw.logicalcpu)
CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1)

View File

@ -1521,7 +1521,6 @@
"Channel.UpdatePhotoItem" = "Set Channel Photo";
"Channel.AboutItem" = "about";
"Channel.LinkItem" = "share link";
"Channel.Edit.AboutItem" = "Description";
"Channel.Edit.LinkItem" = "Link";
@ -5495,3 +5494,6 @@ Any member of this group will be able to see messages in the channel.";
"Message.GenericForwardedPsa" = "Public Service Announcement\nFrom: %@";
"Message.ForwardedPsa.covid" = "Covid-19 Notification\nFrom: %@";
"Channel.AboutItem" = "about";
"PeerInfo.GroupAboutItem" = "about";

View File

@ -1216,7 +1216,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if item.enableContextActions {
if case .psa = promoInfo {
peerRevealOptions = [
ItemListRevealOption(key: RevealOptionKey.hidePsa.rawValue, title: item.presentationData.strings.ChatList_HideAction, icon: hideIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor)
ItemListRevealOption(key: RevealOptionKey.hidePsa.rawValue, title: item.presentationData.strings.ChatList_HideAction, icon: deleteIcon, color: item.presentationData.theme.list.itemDisclosureActions.inactive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.neutral1.foregroundColor)
]
peerLeftRevealOptions = []
} else if promoInfo == nil {

View File

@ -156,7 +156,11 @@ public struct MessageIndex: Comparable, Hashable {
return lhs.id.namespace < rhs.id.namespace
}
return lhs.id.id < rhs.id.id
if lhs.id.id != rhs.id.id {
return lhs.id.id < rhs.id.id
}
return lhs.id.peerId.toInt64() < rhs.id.peerId.toInt64()
}
}

View File

@ -1879,13 +1879,16 @@ final class MessageHistoryTable: Table {
if let forwardInfo = message.forwardInfo {
var forwardInfoFlags: Int8 = 1
if forwardInfo.sourceId != nil {
forwardInfoFlags |= 2
forwardInfoFlags |= 1 << 1
}
if forwardInfo.sourceMessageId != nil {
forwardInfoFlags |= 4
forwardInfoFlags |= 1 << 2
}
if forwardInfo.authorSignature != nil {
forwardInfoFlags |= 8
forwardInfoFlags |= 1 << 3
}
if forwardInfo.psaType != nil {
forwardInfoFlags |= 1 << 4
}
sharedBuffer.write(&forwardInfoFlags, offset: 0, length: 1)
var forwardAuthorId: Int64 = forwardInfo.authorId?.toInt64() ?? 0
@ -1917,6 +1920,17 @@ final class MessageHistoryTable: Table {
sharedBuffer.write(&length, offset: 0, length: 4)
}
}
if let psaType = forwardInfo.psaType {
if let data = psaType.data(using: .utf8, allowLossyConversion: true) {
var length: Int32 = Int32(data.count)
sharedBuffer.write(&length, offset: 0, length: 4)
sharedBuffer.write(data)
} else {
var length: Int32 = 0
sharedBuffer.write(&length, offset: 0, length: 4)
}
}
} else {
var forwardInfoFlags: Int8 = 0
sharedBuffer.write(&forwardInfoFlags, offset: 0, length: 1)

View File

@ -296,6 +296,7 @@ public final class AccountStateManager {
var collectedPollCompletionSubscribers: [(Int32, ([MessageId]) -> Void)] = []
var collectedReplayAsynchronouslyBuiltFinalState: [(AccountFinalState, () -> Void)] = []
var processEvents: [(Int32, AccountFinalStateEvents)] = []
var customOperations: [(Int32, Signal<Void, NoError>)] = []
var replacedOperations: [AccountStateManagerOperation] = []
@ -313,6 +314,8 @@ public final class AccountStateManager {
collectedReplayAsynchronouslyBuiltFinalState.append((finalState, completion))
case let .processEvents(operationId, events):
processEvents.append((operationId, events))
case let .custom(operationId, customSignal):
customOperations.append((operationId, customSignal))
default:
break
}
@ -335,6 +338,10 @@ public final class AccountStateManager {
replacedOperations.append(AccountStateManagerOperation(content: .processEvents(operationId, events)))
}
for (operationId, customSignal) in customOperations {
replacedOperations.append(AccountStateManagerOperation(content: .custom(operationId, customSignal)))
}
self.operations.removeAll()
self.operations.append(contentsOf: replacedOperations)
}

View File

@ -60,40 +60,6 @@ private func dialogTopMessage(network: Network, postbox: Postbox, peerId: PeerId
}
}
func fetchPeerCloudReadState(network: Network, postbox: Postbox, peerId: PeerId, inputPeer: Api.InputPeer) -> Signal<PeerReadState?, NoError> {
return network.request(Api.functions.messages.getPeerDialogs(peers: [.inputDialogPeer(peer: inputPeer)]))
|> map { result -> PeerReadState? in
switch result {
case let .peerDialogs(dialogs, _, _, _, _):
if let dialog = dialogs.filter({ $0.peerId == peerId }).first {
let apiTopMessage: Int32
let apiReadInboxMaxId: Int32
let apiReadOutboxMaxId: Int32
let apiUnreadCount: Int32
let apiMarkedUnread: Bool
switch dialog {
case let .dialog(flags, _, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, _, _, _):
apiTopMessage = topMessage
apiReadInboxMaxId = readInboxMaxId
apiReadOutboxMaxId = readOutboxMaxId
apiUnreadCount = unreadCount
apiMarkedUnread = (flags & (1 << 3)) != 0
case .dialogFolder:
assertionFailure()
return nil
}
return .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount, markedUnread: apiMarkedUnread)
} else {
return nil
}
}
}
|> `catch` { _ -> Signal<PeerReadState?, NoError> in
return .single(nil)
}
}
private func dialogReadState(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(PeerReadState, PeerReadStateMarker), PeerReadStateValidationError> {
return dialogTopMessage(network: network, postbox: postbox, peerId: peerId)
|> mapToSignal { topMessage -> Signal<(PeerReadState, PeerReadStateMarker), PeerReadStateValidationError> in

View File

@ -6482,7 +6482,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let tooltipScreen = TooltipScreen(text: psaText, textEntities: psaEntities, icon: .info, location: .top, shouldDismissOnTouch: { point in
let tooltipScreen = TooltipScreen(text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
return .ignore
}, openActiveTextItem: { [weak self] item, action in
guard let strongSelf = self else {
@ -6492,7 +6492,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case let .url(url, concealed):
switch action {
case .tap:
strongSelf.openUrl(url, concealed: false)
strongSelf.openUrl(url, concealed: concealed)
case .longTap:
strongSelf.controllerInteraction?.longTap(.url(url), nil)
}
@ -6560,18 +6560,34 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let psaEntities: [MessageTextEntity] = generateTextEntities(psaText, enabledTypes: .url)
let messageId = item.message.id
var found = false
self.forEachController({ controller in
if let controller = controller as? TooltipScreen {
if controller.text == psaText {
found = true
controller.dismiss()
controller.resetDismissTimeout()
controller.willBecomeDismissed = { [weak self] tooltipScreen in
guard let strongSelf = self else {
return
}
if strongSelf.controllerInteraction?.currentPsaMessageWithTooltip == messageId {
strongSelf.controllerInteraction?.currentPsaMessageWithTooltip = nil
strongSelf.updatePollTooltipMessageState(animated: true)
}
}
return false
}
}
return true
})
if found {
self.controllerInteraction?.currentPsaMessageWithTooltip = messageId
self.updatePollTooltipMessageState(animated: !isAutomatic)
return
}
@ -6585,7 +6601,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
case let .url(url, concealed):
switch action {
case .tap:
strongSelf.openUrl(url, concealed: false)
strongSelf.openUrl(url, concealed: concealed)
case .longTap:
strongSelf.controllerInteraction?.longTap(.url(url), nil)
}
@ -6620,7 +6636,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
let messageId = item.message.id
self.controllerInteraction?.currentPsaMessageWithTooltip = messageId
self.updatePollTooltipMessageState(animated: !isAutomatic)

View File

@ -784,7 +784,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
var effectiveAuthor: Peer?
var ignoreForward = false
let displayAuthorInfo: Bool
var displayAuthorInfo: Bool
let avatarInset: CGFloat
var hasAvatar = false
@ -809,7 +809,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
displayAuthorInfo = !mergedTop.merged && incoming && effectiveAuthor != nil
} else {
effectiveAuthor = firstMessage.author
displayAuthorInfo = !mergedTop.merged && incoming && peerId.isGroupOrChannel && effectiveAuthor != nil
displayAuthorInfo = !mergedTop.merged && incoming && peerId.isGroupOrChannel && effectiveAuthor != nil
if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil {
displayAuthorInfo = false
}
}
if peerId != item.context.account.peerId {
@ -827,10 +830,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
} else if incoming {
hasAvatar = true
}
/*case .group:
allowFullWidth = true
hasAvatar = true
displayAuthorInfo = true*/
}
if let forwardInfo = item.content.firstMessage.forwardInfo, forwardInfo.source == nil, forwardInfo.author?.id.namespace == Namespaces.Peer.CloudUser {
@ -987,6 +986,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
}
if let forwardInfo = firstMessage.forwardInfo, forwardInfo.psaType != nil {
inlineBotNameString = nil
}
var contentPropertiesAndLayouts: [(CGSize?, ChatMessageBubbleContentProperties, ChatMessageBubblePreparePosition, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void)))] = []
let topNodeMergeStatus: ChatMessageBubbleMergeStatus = mergedTop.merged ? (incoming ? .Left : .Right) : .None(incoming ? .Incoming : .Outgoing)
@ -1877,19 +1880,19 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
if let forwardInfoNode = forwardInfoSizeApply.1(bubbleContentWidth) {
strongSelf.forwardInfoNode = forwardInfoNode
forwardInfoNode.openPsa = { [weak strongSelf] type, sourceNode in
guard let strongSelf = strongSelf, let item = strongSelf.item else {
return
}
item.controllerInteraction.displayPsa(type, sourceNode)
}
var animateFrame = true
if forwardInfoNode.supernode == nil {
strongSelf.contextSourceNode.contentNode.addSubnode(forwardInfoNode)
animateFrame = false
forwardInfoNode.openPsa = { [weak strongSelf] type, sourceNode in
guard let strongSelf = strongSelf, let item = strongSelf.item else {
return
}
item.controllerInteraction.displayPsa(type, sourceNode)
}
}
let previousForwardInfoNodeFrame = forwardInfoNode.frame
forwardInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + forwardInfoOriginY), size: forwardInfoSizeApply.0)
forwardInfoNode.frame = CGRect(origin: CGPoint(x: contentOrigin.x + layoutConstants.text.bubbleInsets.left, y: layoutConstants.bubble.contentInsets.top + forwardInfoOriginY), size: CGSize(width: bubbleContentWidth, height: forwardInfoSizeApply.0.height))
if case let .System(duration) = animation {
if animateFrame {
forwardInfoNode.layer.animateFrame(from: previousForwardInfoNodeFrame, to: forwardInfoNode.frame, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)

View File

@ -154,7 +154,32 @@ class ChatMessageForwardInfoNode: ASDisplayNode {
case .standalone:
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
titleColor = serviceColor.primaryText
completeSourceString = strings.Message_ForwardedMessageShort(peerString)
if let psaType = psaType {
var customFormat: String?
let key = "Message.ForwardedPsa.\(psaType)"
if let string = presentationData.strings.primaryComponent.dict[key] {
customFormat = string
} else if let string = presentationData.strings.secondaryComponent?.dict[key] {
customFormat = string
}
if let customFormat = customFormat {
if let range = customFormat.range(of: "%@") {
let leftPart = String(customFormat[customFormat.startIndex ..< range.lowerBound])
let rightPart = String(customFormat[range.upperBound...])
let formattedText = leftPart + peerString + rightPart
completeSourceString = (formattedText, [(0, NSRange(location: leftPart.count, length: peerString.count))])
} else {
completeSourceString = (customFormat, [])
}
} else {
completeSourceString = strings.Message_GenericForwardedPsa(peerString)
}
} else {
completeSourceString = strings.Message_ForwardedMessageShort(peerString)
}
}
var currentCredibilityIconImage: UIImage?

View File

@ -122,6 +122,10 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
if let strongSelf = self {
if let shareButtonNode = strongSelf.shareButtonNode, shareButtonNode.frame.contains(point) {
return .fail
} else if let forwardInfoNode = strongSelf.forwardInfoNode, forwardInfoNode.frame.contains(point) {
if forwardInfoNode.hasAction(at: strongSelf.view.convert(point, to: forwardInfoNode.view)) {
return .fail
}
}
}
return .waitForSingleTap
@ -572,6 +576,12 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
if strongSelf.forwardInfoNode == nil {
strongSelf.forwardInfoNode = forwardInfoNode
strongSelf.addSubnode(forwardInfoNode)
forwardInfoNode.openPsa = { [weak strongSelf] type, sourceNode in
guard let strongSelf = strongSelf, let item = strongSelf.item else {
return
}
item.controllerInteraction.displayPsa(type, sourceNode)
}
}
let forwardInfoFrame = CGRect(origin: CGPoint(x: (!incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + 12.0) : (params.width - params.rightInset - forwardInfoSize.width - layoutConstants.bubble.edgeInset - 12.0)), y: 8.0), size: forwardInfoSize)
forwardInfoNode.frame = forwardInfoFrame
@ -696,26 +706,28 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
if let forwardInfoNode = self.forwardInfoNode, forwardInfoNode.frame.contains(location) {
if let item = self.item, let forwardInfo = item.message.forwardInfo {
if let sourceMessageId = forwardInfo.sourceMessageId {
if let channel = forwardInfo.author as? TelegramChannel, channel.username == nil {
if case .member = channel.participationStatus {
} else {
return .optionalAction({
let performAction: () -> Void = {
if let sourceMessageId = forwardInfo.sourceMessageId {
if let channel = forwardInfo.author as? TelegramChannel, channel.username == nil {
if case let .broadcast(info) = channel.info, info.flags.contains(.hasDiscussionGroup) {
} else if case .member = channel.participationStatus {
} else {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_PrivateChannelTooltip, forwardInfoNode, nil)
})
return
}
}
}
return .optionalAction({
item.controllerInteraction.navigateToMessage(item.message.id, sourceMessageId)
})
} else if let id = forwardInfo.source?.id ?? forwardInfo.author?.id {
return .optionalAction({
item.controllerInteraction.openPeer(id, .chat(textInputState: nil, subject: nil), nil)
})
} else if let _ = forwardInfo.authorSignature {
return .optionalAction({
} else if let id = forwardInfo.source?.id ?? forwardInfo.author?.id {
item.controllerInteraction.openPeer(id, .info, nil)
} else if let _ = forwardInfo.authorSignature {
item.controllerInteraction.displayMessageTooltip(item.message.id, item.presentationData.strings.Conversation_ForwardAuthorHiddenTooltip, forwardInfoNode, nil)
})
}
}
if forwardInfoNode.hasAction(at: self.view.convert(location, to: forwardInfoNode.view)) {
return .action({})
} else {
return .optionalAction(performAction)
}
}
}

View File

@ -609,11 +609,11 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}
if let cachedData = data.cachedData as? CachedUserData {
if user.isScam {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Channel_AboutItem, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledBioEntities : []), action: nil, requestLayout: {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledBioEntities : []), action: nil, requestLayout: {
interaction.requestLayout()
}))
} else if let about = cachedData.about, !about.isEmpty {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Channel_AboutItem, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: []), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: user.botInfo == nil ? presentationData.strings.Profile_About : presentationData.strings.Profile_BotInfo, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: []), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
interaction.requestLayout()
}))
}
@ -733,11 +733,11 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
} else if let group = data.peer as? TelegramGroup {
if let cachedData = data.cachedData as? CachedGroupData {
if group.isScam {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_AboutItem, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, requestLayout: {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_GroupAboutItem, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, requestLayout: {
interaction.requestLayout()
}))
} else if let about = cachedData.about, !about.isEmpty {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Channel_AboutItem, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.PeerInfo_GroupAboutItem, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction, requestLayout: {
interaction.requestLayout()
}))
}

View File

@ -391,6 +391,8 @@ public final class TooltipScreen: ViewController {
public var willBecomeDismissed: ((TooltipScreen) -> Void)?
public var becameDismissed: ((TooltipScreen) -> Void)?
private var dismissTimer: Foundation.Timer?
public init(text: String, textEntities: [MessageTextEntity] = [], icon: TooltipScreen.Icon?, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: @escaping (TooltipActiveTextItem, TooltipActiveTextAction) -> Void = { _, _ in }) {
self.text = text
self.textEntities = textEntities
@ -409,22 +411,47 @@ public final class TooltipScreen: ViewController {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.dismissTimer?.invalidate()
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.controllerNode.animateIn()
self.resetDismissTimeout(duration: self.displayDuration)
}
public func resetDismissTimeout(duration: TooltipScreen.DisplayDuration? = nil) {
self.dismissTimer?.invalidate()
let timeout: Double
switch self.displayDuration {
switch duration ?? self.displayDuration {
case .default:
timeout = 5.0
case let .custom(value):
timeout = value
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + timeout, execute: { [weak self] in
self?.dismiss()
})
final class TimerTarget: NSObject {
private let f: () -> Void
init(_ f: @escaping () -> Void) {
self.f = f
}
@objc func timerEvent() {
self.f()
}
}
let dismissTimer = Foundation.Timer(timeInterval: timeout, target: TimerTarget { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.dismiss()
}, selector: #selector(TimerTarget.timerEvent), userInfo: nil, repeats: false)
self.dismissTimer = dismissTimer
RunLoop.main.add(dismissTimer, forMode: .common)
}
override public func loadDisplayNode() {

View File

@ -449,12 +449,12 @@ public final class WalletStrings: Equatable {
public var Wallet_SecureStorageReset_Title: String { return self._s[219]! }
public var Wallet_Receive_CommentHeader: String { return self._s[220]! }
public var Wallet_Info_ReceiveGrams: String { return self._s[221]! }
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
}
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)