Various improvements

This commit is contained in:
Ilya Laktyushin 2025-11-13 01:29:20 +04:00
parent c6acd92aed
commit 29b24d9d57
8 changed files with 222 additions and 120 deletions

View File

@ -15299,3 +15299,58 @@ Error: %8$@";
"Camera.LiveStream.Change" = "change"; "Camera.LiveStream.Change" = "change";
"Camera.LiveStream.StartLiveStream" = "Start Live Stream"; "Camera.LiveStream.StartLiveStream" = "Start Live Stream";
"Camera.LiveStream.End" = "End";
"Camera.LiveStream.End.Title" = "End Live Stream";
"Camera.LiveStream.End.Text" = "Are you sure you want to end this live stream?";
"Camera.LiveStream.End.End" = "End";
"Camera.LiveStream.End.Leave" = "Leave";
"Message.RepeatAt" = "%1$@ at %2$@";
"Message.RepeatPeriod.Daily" = "daily";
"Message.RepeatPeriod.Weekly" = "weekly";
"Message.RepeatPeriod.Biweekly" = "biweekly";
"Message.RepeatPeriod.Monthly" = "monthly";
"Message.RepeatPeriod.3Months" = "every 3 months";
"Message.RepeatPeriod.6Months" = "every 6 months";
"Message.RepeatPeriod.Yearly" = "yearly";
"Message.Approximate" = "appx. %@";
"LiveStreamSettings.Title" = "Live Settings";
"LiveStreamSettings.TitleEdit" = "Live Stream";
"LiveStreamSettings.StartLiveAs" = "START LIVE AS";
"LiveStreamSettings.WhoCanView" = "WHO CAN VIEW THIS LIVE";
"LiveStreamSettings.WhoCanViewInfo" = "[Select people]() who won't see your live.";
"LiveStreamSettings.ConnectStream" = "Connect Stream";
"LiveStreamSettings.ConnectStreamInfo" = "Stream with a different app.";
"LiveStreamSettings.AllowComments" = "Allow Comments";
"LiveStreamSettings.AllowScreenshots" = "Allow Screenshots";
"LiveStreamSettings.PricePerComment" = "PRICE PER COMMENT";
"LiveStreamSettings.PricePerCommentInfo" = "The price a viewer must pay to send a comment.";
"LiveStreamSettings.PricePerComment.Free" = "Free";
"LiveStreamSettings.PricePerComment.Stars_1" = "%@ Star";
"LiveStreamSettings.PricePerComment.Stars_any" = "%@ Stars";
"LiveStreamSettings.SaveSettings" = "Save Settings";
"AddContact.Title" = "New Contact";
"AddContact.PhoneNumber.IsContact"= "This phone number is already in your contacts. [View >]()";
"AddContact.PhoneNumber.Registered"= "This phone number is on Telegram.";
"AddContact.PhoneNumber.NotRegistered"= "This phone number is not on Telegram. [Invite >]()";
"AddContact.SyncToPhone" = "Sync Contact to Phone";
"AddContact.NotePlaceholder" = "Add notes only visible to you";
"AddContact.AddQR" = "Add via QR Code";
"ScheduleMessage.Time" = "Time";
"ScheduleMessage.Repeat" = "Repeat";
"ScheduleMessage.RepeatPeriod.Never" = "Never";
"ScheduleMessage.RepeatPeriod.Daily" = "Daily";
"ScheduleMessage.RepeatPeriod.Weekly" = "Weekly";
"ScheduleMessage.RepeatPeriod.Biweekly" = "Biweekly";
"ScheduleMessage.RepeatPeriod.Monthly" = "Monthly";
"ScheduleMessage.RepeatPeriod.3Months" = "Every 3 Months";
"ScheduleMessage.RepeatPeriod.6Months" = "Every 6 Months";
"ScheduleMessage.RepeatPeriod.Yearly" = "Yearly";
"ScheduleMessage.PremiumRequired.Title" = "Premium Required";
"ScheduleMessage.PremiumRequired.Text" = "Subscribe to **Telegram Premium** to schedule repeating messages.";
"ScheduleMessage.PremiumRequired.Add" = "Add";

View File

@ -2575,6 +2575,11 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupLoginEmail.id).startStandalone() let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: ServerProvidedSuggestion.setupLoginEmail.id).startStandalone()
} }
}) })
if let layout = strongSelf.validLayout, layout.metrics.isTablet {
controller.navigationPresentation = .standaloneFlatModal
} else {
controller.navigationPresentation = .flatModal
}
navigationController.pushViewController(controller) navigationController.pushViewController(controller)
} }
}) })

View File

@ -75,6 +75,12 @@ struct CameraState: Equatable {
} }
} }
enum StreamingMode {
case none
case camera
case rtmp
}
let mode: CameraMode let mode: CameraMode
let position: Camera.Position let position: Camera.Position
let flashMode: Camera.FlashMode let flashMode: Camera.FlashMode
@ -87,7 +93,7 @@ struct CameraState: Equatable {
let isCollageEnabled: Bool let isCollageEnabled: Bool
let collageGrid: Camera.CollageGrid let collageGrid: Camera.CollageGrid
let collageProgress: Float let collageProgress: Float
let isStreaming: Bool let isStreaming: StreamingMode
let isWaitingForStream: Bool let isWaitingForStream: Bool
func updatedMode(_ mode: CameraMode) -> CameraState { func updatedMode(_ mode: CameraMode) -> CameraState {
@ -134,7 +140,7 @@ struct CameraState: Equatable {
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream) return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: collageProgress, isStreaming: self.isStreaming, isWaitingForStream: self.isWaitingForStream)
} }
func updatedIsStreaming(_ isStreaming: Bool) -> CameraState { func updatedIsStreaming(_ isStreaming: StreamingMode) -> CameraState {
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: isStreaming, isWaitingForStream: self.isWaitingForStream) return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, flashTintSize: self.flashTintSize, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled, isCollageEnabled: self.isCollageEnabled, collageGrid: self.collageGrid, collageProgress: self.collageProgress, isStreaming: isStreaming, isWaitingForStream: self.isWaitingForStream)
} }
@ -1133,7 +1139,7 @@ private final class CameraScreenComponent: CombinedComponent {
} }
self.liveStreamStory = story self.liveStreamStory = story
controller?.updateCameraState({ $0.updatedIsStreaming(true).updatedIsWaitingForStream(false) }, transition: .spring(duration: 0.4)) controller?.updateCameraState({ $0.updatedIsStreaming(rtmp ? .rtmp : .camera).updatedIsWaitingForStream(false) }, transition: .spring(duration: 0.4))
self.updated(transition: .immediate) self.updated(transition: .immediate)
}) })
} }
@ -1155,9 +1161,9 @@ private final class CameraScreenComponent: CombinedComponent {
return return
} }
for item in state.items.reversed() { for item in state.items.reversed() {
if case let .item(item) = item, case .liveStream = item.media { if case let .item(item) = item, case let .liveStream(liveStream) = item.media {
self.liveStreamStory = item self.liveStreamStory = item
controller?.updateCameraState({ $0.updatedIsStreaming(true) }, transition: .spring(duration: 0.4)) controller?.updateCameraState({ $0.updatedIsStreaming(liveStream.kind == .rtmp ? .rtmp : .camera) }, transition: .spring(duration: 0.4))
self.updated(transition: .immediate) self.updated(transition: .immediate)
return return
} }
@ -1180,17 +1186,17 @@ private final class CameraScreenComponent: CombinedComponent {
let alertController = textAlertController( let alertController = textAlertController(
context: self.context, context: self.context,
forceTheme: defaultDarkColorPresentationTheme, forceTheme: defaultDarkColorPresentationTheme,
title: "End Live Stream", title: presentationData.strings.Camera_LiveStream_End_Title,
text: "Are you sure you want to end this live stream?", text: presentationData.strings.Camera_LiveStream_End_Text,
actions: [ actions: [
TextAlertAction(type: .destructiveAction, title: "End", action: { [weak self, weak controller] in TextAlertAction(type: .destructiveAction, title: presentationData.strings.Camera_LiveStream_End_End, action: { [weak self, weak controller] in
guard let self, let controller else { guard let self, let controller else {
return return
} }
let _ = self.liveStreamCall?.leave(terminateIfPossible: true).startStandalone() let _ = self.liveStreamCall?.leave(terminateIfPossible: true).startStandalone()
controller.requestDismiss(animated: true) controller.requestDismiss(animated: true)
}), }),
TextAlertAction(type: .genericAction, title: "Leave", action: { [weak controller] in TextAlertAction(type: .genericAction, title: presentationData.strings.Camera_LiveStream_End_Leave, action: { [weak controller] in
guard let controller else { guard let controller else {
return return
} }
@ -1206,11 +1212,11 @@ private final class CameraScreenComponent: CombinedComponent {
let alertController = textAlertController( let alertController = textAlertController(
context: self.context, context: self.context,
forceTheme: defaultDarkColorPresentationTheme, forceTheme: defaultDarkColorPresentationTheme,
title: "End Live Stream", title: presentationData.strings.Camera_LiveStream_End_Title,
text: "Are you sure you want to end this live stream?", text: presentationData.strings.Camera_LiveStream_End_Text,
actions: [ actions: [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .destructiveAction, title: "End", action: { [weak self, weak controller] in TextAlertAction(type: .destructiveAction, title: presentationData.strings.Camera_LiveStream_End_End, action: { [weak self, weak controller] in
guard let self, let controller else { guard let self, let controller else {
return return
} }
@ -1435,7 +1441,7 @@ private final class CameraScreenComponent: CombinedComponent {
case .video: case .video:
shutterState = .video shutterState = .video
case .live: case .live:
shutterState = .live(active: component.cameraState.isStreaming, progress: component.cameraState.isWaitingForStream) shutterState = .live(active: component.cameraState.isStreaming != .none, progress: component.cameraState.isWaitingForStream)
} }
} }
} }
@ -1560,7 +1566,7 @@ private final class CameraScreenComponent: CombinedComponent {
) )
} }
if component.cameraState.mode == .live, component.cameraState.isStreaming { if component.cameraState.mode == .live, component.cameraState.isStreaming != .none {
let liveStream = liveStream.update( let liveStream = liveStream.update(
component: CameraLiveStreamComponent( component: CameraLiveStreamComponent(
context: component.context, context: component.context,
@ -1607,7 +1613,7 @@ private final class CameraScreenComponent: CombinedComponent {
let topControlVerticalInset: CGFloat = 12.0 let topControlVerticalInset: CGFloat = 12.0
let topButtonSpacing: CGFloat = 15.0 let topButtonSpacing: CGFloat = 15.0
if component.cameraState.mode == .live && !component.cameraState.isStreaming { if component.cameraState.mode == .live && component.cameraState.isStreaming == .none {
let streamAsButton = streamAsButton.update( let streamAsButton = streamAsButton.update(
component: PlainButtonComponent( component: PlainButtonComponent(
content: AnyComponent( content: AnyComponent(
@ -1647,7 +1653,7 @@ private final class CameraScreenComponent: CombinedComponent {
} }
if case .none = component.cameraState.recording, !state.isTransitioning { if case .none = component.cameraState.recording, !state.isTransitioning {
if !state.displayingCollageSelection && !component.cameraState.isStreaming { if !state.displayingCollageSelection && component.cameraState.isStreaming == .none {
let cancelButton = cancelButton.update( let cancelButton = cancelButton.update(
component: CameraButton( component: CameraButton(
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
@ -1728,22 +1734,23 @@ private final class CameraScreenComponent: CombinedComponent {
} }
if hasAllRequiredAccess { if hasAllRequiredAccess {
let isStreaming = component.cameraState.mode == .live && component.cameraState.isStreaming let isStreaming = component.cameraState.mode == .live && component.cameraState.isStreaming != .none
var endStreamButtonWidth: CGFloat = 56.0
if isStreaming { if isStreaming {
let endStreamButton = endStreamButton.update( let endStreamButton = endStreamButton.update(
component: GlassBarButtonComponent( component: GlassBarButtonComponent(
size: CGSize(width: 56.0, height: 40.0), size: nil,
backgroundColor: UIColor.black.withAlphaComponent(0.5), backgroundColor: UIColor.black.withAlphaComponent(0.5),
isDark: true, isDark: true,
state: .glass, state: .glass,
component: AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: "End", font: Font.semibold(17.0), color: .white))), component: AnyComponentWithIdentity(id: "label", component: AnyComponent(Text(text: environment.strings.Camera_LiveStream_End, font: Font.semibold(17.0), color: .white))),
action: { [weak state] _ in action: { [weak state] _ in
if let state { if let state {
state.endLiveStream() state.endLiveStream()
} }
} }
), ),
availableSize: CGSize(width: 40.0, height: 40.0), availableSize: CGSize(width: 120.0, height: 40.0),
transition: .immediate transition: .immediate
) )
context.add(endStreamButton context.add(endStreamButton
@ -1751,12 +1758,13 @@ private final class CameraScreenComponent: CombinedComponent {
.appear(.default(scale: true)) .appear(.default(scale: true))
.disappear(.default(scale: true)) .disappear(.default(scale: true))
) )
endStreamButtonWidth = endStreamButton.size.width
} }
let rightMostButtonWidth: CGFloat let rightMostButtonWidth: CGFloat
if component.cameraState.mode == .live { if component.cameraState.mode == .live {
if component.cameraState.isStreaming { if component.cameraState.isStreaming != .none {
rightMostButtonWidth = -25.0 rightMostButtonWidth = -25.0 + (endStreamButtonWidth - 56.0) * 2.0
} else { } else {
rightMostButtonWidth = -55.0 rightMostButtonWidth = -55.0
} }
@ -1824,7 +1832,7 @@ private final class CameraScreenComponent: CombinedComponent {
if !isSticker && !isAvatar && !isTablet { if !isSticker && !isAvatar && !isTablet {
var nextButtonX = availableSize.width - topControlSideInset - rightMostButtonWidth / 2.0 - 100.0 var nextButtonX = availableSize.width - topControlSideInset - rightMostButtonWidth / 2.0 - 100.0
if Camera.isDualCameraSupported(forRoundVideo: false) && !component.cameraState.isCollageEnabled { if Camera.isDualCameraSupported(forRoundVideo: false) && !component.cameraState.isCollageEnabled && component.cameraState.isStreaming == .none {
let dualButton = dualButton.update( let dualButton = dualButton.update(
component: CameraButton( component: CameraButton(
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
@ -1856,7 +1864,35 @@ private final class CameraScreenComponent: CombinedComponent {
} }
if component.cameraState.mode == .live { if component.cameraState.mode == .live {
if component.cameraState.isStreaming == .camera {
let flipButton = flipButton.update(
component: CameraButton(
content: AnyComponentWithIdentity(
id: "flip",
component: AnyComponent(
FlipButtonContentComponent(
action: animateFlipAction,
maskFrame: .zero,
tintColor: controlsTintColor
)
)
),
minSize: CGSize(width: 44.0, height: 44.0),
action: { [weak state] in
if let state {
state.togglePosition(animateFlipAction)
}
}
),
availableSize: availableSize,
transition: context.transition
)
context.add(flipButton
.position(CGPoint(x: nextButtonX, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlVerticalInset) + flipButton.size.height / 2.0 - 4.0))
.appear(.default(scale: true))
.disappear(.default(scale: true))
)
}
} else { } else {
let collageButton = collageButton.update( let collageButton = collageButton.update(
component: CameraButton( component: CameraButton(
@ -2026,7 +2062,7 @@ private final class CameraScreenComponent: CombinedComponent {
} }
} }
if !isSticker, case .none = component.cameraState.recording, !component.cameraState.isStreaming && !state.isTransitioning && hasAllRequiredAccess && component.cameraState.collageProgress < 1.0 - .ulpOfOne { if !isSticker, case .none = component.cameraState.recording, component.cameraState.isStreaming == .none && !state.isTransitioning && hasAllRequiredAccess && component.cameraState.collageProgress < 1.0 - .ulpOfOne {
let availableModeControlSize: CGSize let availableModeControlSize: CGSize
if isTablet { if isTablet {
availableModeControlSize = CGSize(width: panelWidth, height: 120.0) availableModeControlSize = CGSize(width: panelWidth, height: 120.0)
@ -2474,7 +2510,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
isCollageEnabled: false, isCollageEnabled: false,
collageGrid: collageGrids[6], collageGrid: collageGrids[6],
collageProgress: 0.0, collageProgress: 0.0,
isStreaming: false, isStreaming: .none,
isWaitingForStream: false isWaitingForStream: false
) )
@ -2900,7 +2936,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
case .began: case .began:
break break
case .changed: case .changed:
if case .none = self.cameraState.recording, !self.cameraState.isStreaming && !self.cameraState.isWaitingForStream { if case .none = self.cameraState.recording, self.cameraState.isStreaming == .none && !self.cameraState.isWaitingForStream {
if case .compact = layout.metrics.widthClass { if case .compact = layout.metrics.widthClass {
switch controller.mode { switch controller.mode {
case .story: case .story:

View File

@ -96,7 +96,6 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
} }
if let repeatPeriod = message.scheduleRepeatPeriod { if let repeatPeriod = message.scheduleRepeatPeriod {
//TODO:localize
let repeatString: String let repeatString: String
switch repeatPeriod { switch repeatPeriod {
case 60: case 60:
@ -104,27 +103,27 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
case 300: case 300:
repeatString = "5 min" repeatString = "5 min"
case 86400: case 86400:
repeatString = "daily" repeatString = strings.Message_RepeatPeriod_Daily
case 7 * 86400: case 7 * 86400:
repeatString = "weekly" repeatString = strings.Message_RepeatPeriod_Weekly
case 14 * 86400: case 14 * 86400:
repeatString = "biweekly" repeatString = strings.Message_RepeatPeriod_Biweekly
case 30 * 86400: case 30 * 86400:
repeatString = "monthly" repeatString = strings.Message_RepeatPeriod_Monthly
case 91 * 86400: case 91 * 86400:
repeatString = "every 3 months" repeatString = strings.Message_RepeatPeriod_3Months
case 182 * 86400: case 182 * 86400:
repeatString = "every 6 months" repeatString = strings.Message_RepeatPeriod_6Months
case 365 * 86400: case 365 * 86400:
repeatString = "yearly" repeatString = strings.Message_RepeatPeriod_Yearly
default: default:
repeatString = "" repeatString = "\(repeatPeriod)s"
} }
dateText = "\(repeatString) at \(dateText)" dateText = strings.Message_RepeatAt(repeatString, dateText).string
} }
if message.id.namespace == Namespaces.Message.ScheduledCloud, let _ = message.pendingProcessingAttribute { if message.id.namespace == Namespaces.Message.ScheduledCloud, let _ = message.pendingProcessingAttribute {
return "appx. \(dateText)" return strings.Message_Approximate(dateText).string
} }
if displayFullDate { if displayFullDate {

View File

@ -159,6 +159,8 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
self.component = component self.component = component
self.state = state self.state = state
let strings = environment.strings
let sideInset: CGFloat = 39.0 let sideInset: CGFloat = 39.0
var contentHeight: CGFloat = 0.0 var contentHeight: CGFloat = 0.0
@ -201,9 +203,9 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
let title: String let title: String
switch component.mode { switch component.mode {
case .scheduledMessages: case .scheduledMessages:
title = environment.strings.Conversation_ScheduleMessage_Title title = strings.Conversation_ScheduleMessage_Title
case .reminders: case .reminders:
title = environment.strings.Conversation_SetReminder_Title title = strings.Conversation_SetReminder_Title
} }
let titleSize = self.title.update( let titleSize = self.title.update(
transition: transition, transition: transition,
@ -232,7 +234,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
} else { } else {
datePicker = DatePickerNode( datePicker = DatePickerNode(
theme: DatePickerTheme(theme: environment.theme), theme: DatePickerTheme(theme: environment.theme),
strings: environment.strings, strings: strings,
dateTimeFormat: environment.dateTimeFormat, dateTimeFormat: environment.dateTimeFormat,
hasValueRow: false hasValueRow: false
) )
@ -295,7 +297,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
let timeTitleSize = self.timeTitle.update( let timeTitleSize = self.timeTitle.update(
transition: transition, transition: transition,
component: AnyComponent( component: AnyComponent(
Text(text: "Time", font: Font.regular(17.0), color: environment.theme.actionSheet.primaryTextColor) Text(text: strings.ScheduleMessage_Time, font: Font.regular(17.0), color: environment.theme.actionSheet.primaryTextColor)
), ),
environment: {}, environment: {},
containerSize: availableSize containerSize: availableSize
@ -364,7 +366,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
let repeatTitleSize = self.repeatTitle.update( let repeatTitleSize = self.repeatTitle.update(
transition: transition, transition: transition,
component: AnyComponent( component: AnyComponent(
Text(text: "Repeat", font: Font.regular(17.0), color: environment.theme.actionSheet.primaryTextColor) Text(text: strings.ScheduleMessage_Repeat, font: Font.regular(17.0), color: environment.theme.actionSheet.primaryTextColor)
), ),
environment: {}, environment: {},
containerSize: availableSize containerSize: availableSize
@ -380,29 +382,25 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
let repeatString: String let repeatString: String
if let repeatPeriod = self.repeatPeriod { if let repeatPeriod = self.repeatPeriod {
switch repeatPeriod { switch repeatPeriod {
case 60:
repeatString = "Every Minute"
case 300:
repeatString = "Every 5 Minutes"
case 86400: case 86400:
repeatString = "Daily" repeatString = strings.ScheduleMessage_RepeatPeriod_Daily
case 7 * 86400: case 7 * 86400:
repeatString = "Weekly" repeatString = strings.ScheduleMessage_RepeatPeriod_Weekly
case 14 * 86400: case 14 * 86400:
repeatString = "Biweekly" repeatString = strings.ScheduleMessage_RepeatPeriod_Biweekly
case 30 * 86400: case 30 * 86400:
repeatString = "Monthly" repeatString = strings.ScheduleMessage_RepeatPeriod_Monthly
case 91 * 86400: case 91 * 86400:
repeatString = "Every 3 Months" repeatString = strings.ScheduleMessage_RepeatPeriod_3Months
case 182 * 86400: case 182 * 86400:
repeatString = "Every 6 Months" repeatString = strings.ScheduleMessage_RepeatPeriod_6Months
case 365 * 86400: case 365 * 86400:
repeatString = "Yearly" repeatString = strings.ScheduleMessage_RepeatPeriod_Yearly
default: default:
repeatString = "\(repeatPeriod)" repeatString = "\(repeatPeriod)s"
} }
} else { } else {
repeatString = "Never" repeatString = strings.ScheduleMessage_RepeatPeriod_Never
} }
let repeatValueSize = self.repeatValue.update( let repeatValueSize = self.repeatValue.update(
@ -448,19 +446,19 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
switch component.mode { switch component.mode {
case .scheduledMessages: case .scheduledMessages:
if calendar.isDateInToday(date) { if calendar.isDateInToday(date) {
buttonTitle = environment.strings.Conversation_ScheduleMessage_SendToday(time).string buttonTitle = strings.Conversation_ScheduleMessage_SendToday(time).string
} else if calendar.isDateInTomorrow(date) { } else if calendar.isDateInTomorrow(date) {
buttonTitle = environment.strings.Conversation_ScheduleMessage_SendTomorrow(time).string buttonTitle = strings.Conversation_ScheduleMessage_SendTomorrow(time).string
} else { } else {
buttonTitle = environment.strings.Conversation_ScheduleMessage_SendOn(self.dateFormatter.string(from: date), time).string buttonTitle = strings.Conversation_ScheduleMessage_SendOn(self.dateFormatter.string(from: date), time).string
} }
case .reminders: case .reminders:
if calendar.isDateInToday(date) { if calendar.isDateInToday(date) {
buttonTitle = environment.strings.Conversation_SetReminder_RemindToday(time).string buttonTitle = strings.Conversation_SetReminder_RemindToday(time).string
} else if calendar.isDateInTomorrow(date) { } else if calendar.isDateInTomorrow(date) {
buttonTitle = environment.strings.Conversation_SetReminder_RemindTomorrow(time).string buttonTitle = strings.Conversation_SetReminder_RemindTomorrow(time).string
} else { } else {
buttonTitle = environment.strings.Conversation_SetReminder_RemindOn(self.dateFormatter.string(from: date), time).string buttonTitle = strings.Conversation_SetReminder_RemindOn(self.dateFormatter.string(from: date), time).string
} }
} }
@ -517,7 +515,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8), pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8),
), ),
content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent( content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(
Text(text: environment.strings.Conversation_ScheduleMessage_SendWhenOnline, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.fillColor) Text(text: strings.Conversation_ScheduleMessage_SendWhenOnline, font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.fillColor)
)), )),
isEnabled: true, isEnabled: true,
displaysProgress: false, displaysProgress: false,
@ -608,6 +606,7 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
sourceFrame: repeatValueFrame, sourceFrame: repeatValueFrame,
component: AnyComponent(RepeatMenuComponent( component: AnyComponent(RepeatMenuComponent(
theme: environment.theme, theme: environment.theme,
strings: strings,
value: self.repeatPeriod, value: self.repeatPeriod,
valueUpdated: { [weak self] value in valueUpdated: { [weak self] value in
guard let self, let component = self.component, let environment = self.environment else { guard let self, let component = self.component, let environment = self.environment else {
@ -620,9 +619,9 @@ private final class ChatScheduleTimeSheetContentComponent: Component {
let toastController = UndoOverlayController( let toastController = UndoOverlayController(
presentationData: component.context.sharedContext.currentPresentationData.with { $0 }, presentationData: component.context.sharedContext.currentPresentationData.with { $0 },
content: .premiumPaywall( content: .premiumPaywall(
title: "Premium Required", title: strings.ScheduleMessage_PremiumRequired_Title,
text: "Subscribe to **Telegram Premium** to schedule repeating messages.", text: strings.ScheduleMessage_PremiumRequired_Text,
customUndoText: "Add", customUndoText: strings.ScheduleMessage_PremiumRequired_Add,
timeout: nil, timeout: nil,
linkAction: nil linkAction: nil
), ),
@ -1169,15 +1168,18 @@ private final class MenuComponent: Component {
private final class RepeatMenuComponent: Component { private final class RepeatMenuComponent: Component {
let theme: PresentationTheme let theme: PresentationTheme
let strings: PresentationStrings
let value: Int32? let value: Int32?
let valueUpdated: (Int32?) -> Void let valueUpdated: (Int32?) -> Void
init( init(
theme: PresentationTheme, theme: PresentationTheme,
strings: PresentationStrings,
value: Int32?, value: Int32?,
valueUpdated: @escaping (Int32?) -> Void valueUpdated: @escaping (Int32?) -> Void
) { ) {
self.theme = theme self.theme = theme
self.strings = strings
self.value = value self.value = value
self.valueUpdated = valueUpdated self.valueUpdated = valueUpdated
} }
@ -1186,6 +1188,9 @@ private final class RepeatMenuComponent: Component {
if lhs.theme !== rhs.theme { if lhs.theme !== rhs.theme {
return false return false
} }
if lhs.strings !== rhs.strings {
return false
}
if lhs.value != rhs.value { if lhs.value != rhs.value {
return false return false
} }
@ -1240,7 +1245,7 @@ private final class RepeatMenuComponent: Component {
component: AnyComponent( component: AnyComponent(
PlainButtonComponent( PlainButtonComponent(
content: AnyComponent( content: AnyComponent(
Text(text: "Never", font: Font.regular(17.0), color: textColor) Text(text: component.strings.ScheduleMessage_RepeatPeriod_Never, font: Font.regular(17.0), color: textColor)
), ),
action: { [weak self] in action: { [weak self] in
guard let self else { guard let self else {
@ -1278,26 +1283,22 @@ private final class RepeatMenuComponent: Component {
let repeatString: String let repeatString: String
switch value { switch value {
case 60:
repeatString = "Every Minute"
case 300:
repeatString = "Every 5 Minutes"
case 86400: case 86400:
repeatString = "Daily" repeatString = component.strings.ScheduleMessage_RepeatPeriod_Daily
case 7 * 86400: case 7 * 86400:
repeatString = "Weekly" repeatString = component.strings.ScheduleMessage_RepeatPeriod_Weekly
case 14 * 86400: case 14 * 86400:
repeatString = "Biweekly" repeatString = component.strings.ScheduleMessage_RepeatPeriod_Biweekly
case 30 * 86400: case 30 * 86400:
repeatString = "Monthly" repeatString = component.strings.ScheduleMessage_RepeatPeriod_Monthly
case 91 * 86400: case 91 * 86400:
repeatString = "Every 3 Months" repeatString = component.strings.ScheduleMessage_RepeatPeriod_3Months
case 182 * 86400: case 182 * 86400:
repeatString = "Every 6 Months" repeatString = component.strings.ScheduleMessage_RepeatPeriod_6Months
case 365 * 86400: case 365 * 86400:
repeatString = "Yearly" repeatString = component.strings.ScheduleMessage_RepeatPeriod_Yearly
default: default:
repeatString = "\(value)" repeatString = "\(value)s"
} }
let itemSize = itemView.update( let itemSize = itemView.update(

View File

@ -253,6 +253,7 @@ final class NewContactScreenComponent: Component {
self.environment = environment self.environment = environment
let theme = environment.theme let theme = environment.theme
let strings = environment.strings
var initialCountryCode: Int32? var initialCountryCode: Int32?
var updateFocusTag: Any? var updateFocusTag: Any?
@ -326,7 +327,7 @@ final class NewContactScreenComponent: Component {
theme: theme, theme: theme,
initialText: component.initialData.firstName ?? "", initialText: component.initialData.firstName ?? "",
resetText: nil, resetText: nil,
placeholder: "First Name", placeholder: strings.UserInfo_FirstNamePlaceholder,
autocapitalizationType: .sentences, autocapitalizationType: .sentences,
autocorrectionType: .default, autocorrectionType: .default,
returnKeyType: .next, returnKeyType: .next,
@ -347,7 +348,7 @@ final class NewContactScreenComponent: Component {
theme: theme, theme: theme,
initialText: component.initialData.lastName ?? "", initialText: component.initialData.lastName ?? "",
resetText: nil, resetText: nil,
placeholder: "Last Name", placeholder: strings.UserInfo_LastNamePlaceholder,
autocapitalizationType: .sentences, autocapitalizationType: .sentences,
autocorrectionType: .default, autocorrectionType: .default,
returnKeyType: .next, returnKeyType: .next,
@ -437,7 +438,7 @@ final class NewContactScreenComponent: Component {
style: .glass, style: .glass,
title: AnyComponent(VStack([ title: AnyComponent(VStack([
AnyComponentWithIdentity(id: "title", component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "mobile", font: Font.regular(14.0), textColor: theme.list.itemPrimaryTextColor))))), AnyComponentWithIdentity(id: "title", component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "mobile", font: Font.regular(14.0), textColor: theme.list.itemPrimaryTextColor))))),
AnyComponentWithIdentity(id: "value", component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: environment.strings.ContactInfo_PhoneNumberHidden, font: Font.regular(17.0), textColor: theme.list.itemAccentColor))))) AnyComponentWithIdentity(id: "value", component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.ContactInfo_PhoneNumberHidden, font: Font.regular(17.0), textColor: theme.list.itemAccentColor)))))
], alignment: .left, spacing: 4.0)), ], alignment: .left, spacing: 4.0)),
contentInsets: UIEdgeInsets(top: 15.0, left: 0.0, bottom: 15.0, right: 0.0), contentInsets: UIEdgeInsets(top: 15.0, left: 0.0, bottom: 15.0, right: 0.0),
accessory: nil, accessory: nil,
@ -445,7 +446,7 @@ final class NewContactScreenComponent: Component {
))) )))
) )
phoneFooterComponent = AnyComponent(MultilineTextComponent( phoneFooterComponent = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: environment.strings.AddContact_ContactWillBeSharedAfterMutual(peer.compactDisplayTitle).string, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)), text: .plain(NSAttributedString(string: strings.AddContact_ContactWillBeSharedAfterMutual(peer.compactDisplayTitle).string, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)),
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)) ))
} }
@ -454,14 +455,14 @@ final class NewContactScreenComponent: Component {
ListItemComponentAdaptor( ListItemComponentAdaptor(
itemGenerator: PhoneInputItem( itemGenerator: PhoneInputItem(
theme: theme, theme: theme,
strings: environment.strings, strings: strings,
value: (initialCountryCode, nil, ""), value: (initialCountryCode, nil, ""),
accessory: phoneAccesory, accessory: phoneAccesory,
selectCountryCode: { [weak self] in selectCountryCode: { [weak self] in
guard let self, let environment = self.environment, let controller = environment.controller() else { guard let self, let environment = self.environment, let controller = environment.controller() else {
return return
} }
let countryController = AuthorizationSequenceCountrySelectionController(strings: environment.strings, theme: environment.theme, glass: true) let countryController = AuthorizationSequenceCountrySelectionController(strings: strings, theme: environment.theme, glass: true)
countryController.completeWithCountryCode = { [weak self] code, name in countryController.completeWithCountryCode = { [weak self] code, name in
guard let self else { guard let self else {
return return
@ -550,12 +551,12 @@ final class NewContactScreenComponent: Component {
phoneFooterRawText = "" phoneFooterRawText = ""
case let .peer(_, isContact): case let .peer(_, isContact):
if isContact { if isContact {
phoneFooterRawText = "This phone number is already in your contacts. [View >]()" phoneFooterRawText = strings.AddContact_PhoneNumber_IsContact
} else { } else {
phoneFooterRawText = "This phone number is on Telegram." phoneFooterRawText = strings.AddContact_PhoneNumber_Registered
} }
case .notFound: case .notFound:
phoneFooterRawText = "This phone number is not on Telegram. [Invite >]()" phoneFooterRawText = strings.AddContact_PhoneNumber_NotRegistered
} }
let phoneFooterText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(phoneFooterRawText, attributes: footerAttributes)) let phoneFooterText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(phoneFooterRawText, attributes: footerAttributes))
if let range = phoneFooterText.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 { if let range = phoneFooterText.string.range(of: ">"), let chevronImage = self.cachedChevronImage?.0 {
@ -625,7 +626,7 @@ final class NewContactScreenComponent: Component {
style: .glass, style: .glass,
title: AnyComponent(MultilineTextComponent( title: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: "Sync Contact to Phone", string: strings.AddContact_SyncToPhone,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize), font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: theme.list.itemPrimaryTextColor textColor: theme.list.itemPrimaryTextColor
)), )),
@ -649,7 +650,7 @@ final class NewContactScreenComponent: Component {
style: .glass, style: .glass,
title: AnyComponent(MultilineTextComponent( title: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: environment.strings.AddContact_SharedContactException, string: strings.AddContact_SharedContactException,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize), font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: theme.list.itemPrimaryTextColor textColor: theme.list.itemPrimaryTextColor
)), )),
@ -666,7 +667,7 @@ final class NewContactScreenComponent: Component {
))) )))
) )
optionsFooterComponent = AnyComponent(MultilineTextComponent( optionsFooterComponent = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: environment.strings.AddContact_SharedContactExceptionInfo(peer.compactDisplayTitle).string, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)), text: .plain(NSAttributedString(string: strings.AddContact_SharedContactExceptionInfo(peer.compactDisplayTitle).string, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)),
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)) ))
} }
@ -714,8 +715,8 @@ final class NewContactScreenComponent: Component {
context: component.context, context: component.context,
style: .glass, style: .glass,
theme: theme, theme: theme,
strings: environment.strings, strings: strings,
placeholder: NSAttributedString(string: "Add notes only visible to you", font: Font.regular(17.0), textColor: theme.list.itemPlaceholderTextColor), placeholder: NSAttributedString(string: strings.AddContact_NotePlaceholder, font: Font.regular(17.0), textColor: theme.list.itemPlaceholderTextColor),
characterLimit: characterLimit, characterLimit: characterLimit,
emptyLineHandling: .allowed, emptyLineHandling: .allowed,
returnKeyAction: nil, returnKeyAction: nil,
@ -771,7 +772,7 @@ final class NewContactScreenComponent: Component {
title: AnyComponent(VStack([ title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: "Add via QR Code", string: strings.AddContact_AddQR,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize), font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: theme.list.itemAccentColor textColor: theme.list.itemAccentColor
)), )),
@ -867,7 +868,7 @@ final class NewContactScreenComponent: Component {
MultilineTextComponent( MultilineTextComponent(
text: .plain( text: .plain(
NSAttributedString( NSAttributedString(
string: "New Contact", string: strings.AddContact_Title,
font: Font.semibold(17.0), font: Font.semibold(17.0),
textColor: environment.theme.rootController.navigationBar.primaryTextColor textColor: environment.theme.rootController.navigationBar.primaryTextColor
) )

View File

@ -12,7 +12,7 @@ public final class GlassBarButtonComponent: Component {
case tintedGlass case tintedGlass
} }
public let size: CGSize public let size: CGSize?
public let backgroundColor: UIColor public let backgroundColor: UIColor
public let isDark: Bool public let isDark: Bool
public let state: DisplayState? public let state: DisplayState?
@ -21,7 +21,7 @@ public final class GlassBarButtonComponent: Component {
public let action: ((UIView) -> Void)? public let action: ((UIView) -> Void)?
public init( public init(
size: CGSize, size: CGSize?,
backgroundColor: UIColor, backgroundColor: UIColor,
isDark: Bool, isDark: Bool,
state: DisplayState? = nil, state: DisplayState? = nil,
@ -149,9 +149,17 @@ public final class GlassBarButtonComponent: Component {
transition: componentTransition, transition: componentTransition,
component: component.component.component, component: component.component.component,
environment: {}, environment: {},
containerSize: component.size containerSize: component.size ?? availableSize
) )
let componentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((component.size.width - componentSize.width) / 2.0), y: floorToScreenPixels((component.size.height - componentSize.height) / 2.0)), size: componentSize)
let containerSize: CGSize
if let size = component.size {
containerSize = size
} else {
containerSize = CGSize(width: componentSize.width + 25.0, height: max(availableSize.height, componentSize.height + 19.0))
}
let componentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((containerSize.width - componentSize.width) / 2.0), y: floorToScreenPixels((containerSize.height - componentSize.height) / 2.0)), size: componentSize)
if let view = componentView.view { if let view = componentView.view {
if view.superview == nil { if view.superview == nil {
self.containerView.addSubview(view) self.containerView.addSubview(view)
@ -175,11 +183,11 @@ public final class GlassBarButtonComponent: Component {
genericAlpha = 0.0 genericAlpha = 0.0
} }
let cornerRadius = component.size.height * 0.5 let cornerRadius = containerSize.height * 0.5
self.genericBackgroundView.update(size: component.size, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .custom, color: component.backgroundColor), transition: transition) self.genericBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .custom, color: component.backgroundColor), transition: transition)
self.glassBackgroundView.update(size: component.size, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom : .panel , color: component.backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)), transition: transition) self.glassBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: effectiveState == .tintedGlass ? .custom : .panel , color: component.backgroundColor.withMultipliedAlpha(effectiveState == .tintedGlass ? 1.0 : 0.7)), transition: transition)
let bounds = CGRect(origin: .zero, size: component.size) let bounds = CGRect(origin: .zero, size: containerSize)
transition.setFrame(view: self.containerView, frame: bounds) transition.setFrame(view: self.containerView, frame: bounds)
transition.setAlpha(view: self.genericContainerView, alpha: genericAlpha) transition.setAlpha(view: self.genericContainerView, alpha: genericAlpha)
@ -191,7 +199,7 @@ public final class GlassBarButtonComponent: Component {
transition.setFrame(view: self.genericBackgroundView, frame: bounds) transition.setFrame(view: self.genericBackgroundView, frame: bounds)
transition.setFrame(view: self.glassBackgroundView, frame: bounds) transition.setFrame(view: self.glassBackgroundView, frame: bounds)
return component.size return containerSize
} }
} }

View File

@ -239,6 +239,7 @@ final class LiveStreamSettingsScreenComponent: Component {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let theme = environment.theme.withModalBlocksBackground() let theme = environment.theme.withModalBlocksBackground()
let strings = environment.strings
guard let screenState = component.stateContext.stateValue else { guard let screenState = component.stateContext.stateValue else {
return CGSize() return CGSize()
@ -318,7 +319,7 @@ final class LiveStreamSettingsScreenComponent: Component {
let streamAsSectionHeader = AnyComponent(MultilineTextComponent( let streamAsSectionHeader = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: "START LIVE AS", string: strings.LiveStreamSettings_StartLiveAs,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: theme.list.freeTextColor textColor: theme.list.freeTextColor
)), )),
@ -587,7 +588,7 @@ final class LiveStreamSettingsScreenComponent: Component {
let privacySectionHeader = AnyComponent(MultilineTextComponent( let privacySectionHeader = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: "WHO CAN VIEW THIS LIVE", string: strings.LiveStreamSettings_WhoCanView,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: theme.list.freeTextColor textColor: theme.list.freeTextColor
)), )),
@ -596,7 +597,7 @@ final class LiveStreamSettingsScreenComponent: Component {
let privacySectionFooter = AnyComponent(MultilineTextComponent( let privacySectionFooter = AnyComponent(MultilineTextComponent(
text: .markdown( text: .markdown(
text: "[Select people]() who won't see your live.", text: strings.LiveStreamSettings_WhoCanViewInfo,
attributes: MarkdownAttributes( attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: footerTextFont, textColor: footerTextColor), body: MarkdownAttributeSet(font: footerTextFont, textColor: footerTextColor),
bold: MarkdownAttributeSet(font: footerBoldTextFont, textColor: footerTextColor), bold: MarkdownAttributeSet(font: footerBoldTextFont, textColor: footerTextColor),
@ -666,13 +667,12 @@ final class LiveStreamSettingsScreenComponent: Component {
}) })
} }
//TODO:localize
if !screenState.isEdit || (screenState.call != nil && screenState.isEdit && screenState.callIsStream) { if !screenState.isEdit || (screenState.call != nil && screenState.isEdit && screenState.callIsStream) {
let externalStreamSectionItems = [AnyComponentWithIdentity(id: 0, component: AnyComponent( let externalStreamSectionItems = [AnyComponentWithIdentity(id: 0, component: AnyComponent(
ListActionItemComponent( ListActionItemComponent(
theme: theme, theme: theme,
style: .glass, style: .glass,
title: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Connect Stream", font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: theme.list.itemPrimaryTextColor)))), title: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.LiveStreamSettings_ConnectStream, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: theme.list.itemPrimaryTextColor)))),
action: { [weak self] _ in action: { [weak self] _ in
guard let self else { guard let self else {
return return
@ -682,7 +682,7 @@ final class LiveStreamSettingsScreenComponent: Component {
) )
))] ))]
let externalStreamFooterComponent = AnyComponent(MultilineTextComponent( let externalStreamFooterComponent = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "Stream with a different app.", font: footerTextFont, textColor: footerTextColor)), text: .plain(NSAttributedString(string: strings.LiveStreamSettings_ConnectStreamInfo, font: footerTextFont, textColor: footerTextColor)),
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)) ))
@ -710,15 +710,13 @@ final class LiveStreamSettingsScreenComponent: Component {
contentHeight += sectionSpacing contentHeight += sectionSpacing
} }
//TODO:localize
var settingsSectionItems: [AnyComponentWithIdentity<Empty>] = [] var settingsSectionItems: [AnyComponentWithIdentity<Empty>] = []
settingsSectionItems.append(AnyComponentWithIdentity(id: "comments", component: AnyComponent(ListActionItemComponent( settingsSectionItems.append(AnyComponentWithIdentity(id: "comments", component: AnyComponent(ListActionItemComponent(
theme: theme, theme: theme,
style: .glass, style: .glass,
title: AnyComponent(MultilineTextComponent( title: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: "Allow Comments", string: strings.LiveStreamSettings_AllowComments,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize), font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: theme.list.itemPrimaryTextColor textColor: theme.list.itemPrimaryTextColor
)), )),
@ -735,13 +733,12 @@ final class LiveStreamSettingsScreenComponent: Component {
)))) ))))
if !(screenState.call != nil && screenState.isEdit) { if !(screenState.call != nil && screenState.isEdit) {
//TODO:localize
settingsSectionItems.append(AnyComponentWithIdentity(id: "screenshots", component: AnyComponent(ListActionItemComponent( settingsSectionItems.append(AnyComponentWithIdentity(id: "screenshots", component: AnyComponent(ListActionItemComponent(
theme: theme, theme: theme,
style: .glass, style: .glass,
title: AnyComponent(MultilineTextComponent( title: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: "Allow Screenshots", string: strings.LiveStreamSettings_AllowScreenshots,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize), font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: theme.list.itemPrimaryTextColor textColor: theme.list.itemPrimaryTextColor
)), )),
@ -789,7 +786,7 @@ final class LiveStreamSettingsScreenComponent: Component {
minValue: 0, minValue: 0,
lowerBoundTitle: "0", lowerBoundTitle: "0",
upperBoundTitle: "\(presentationStringsFormattedNumber(Int32(clamping: screenState.maxPaidMessageStars), environment.dateTimeFormat.groupingSeparator))", upperBoundTitle: "\(presentationStringsFormattedNumber(Int32(clamping: screenState.maxPaidMessageStars), environment.dateTimeFormat.groupingSeparator))",
title: screenState.paidMessageStars == 0 ? "Free" : "\(screenState.paidMessageStars) Stars", title: screenState.paidMessageStars == 0 ? strings.LiveStreamSettings_PricePerComment_Free : strings.LiveStreamSettings_PricePerComment_Stars(Int32(clamping: screenState.paidMessageStars)),
valueUpdated: { [weak self] value in valueUpdated: { [weak self] value in
guard let self, let component = self.component else { guard let self, let component = self.component else {
return return
@ -805,7 +802,7 @@ final class LiveStreamSettingsScreenComponent: Component {
if screenState.allowComments { if screenState.allowComments {
let paidMessageSectionHeader = AnyComponent(MultilineTextComponent( let paidMessageSectionHeader = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString( text: .plain(NSAttributedString(
string: "PRICE PER COMMENT", string: strings.LiveStreamSettings_PricePerComment,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: theme.list.freeTextColor textColor: theme.list.freeTextColor
)), )),
@ -813,7 +810,7 @@ final class LiveStreamSettingsScreenComponent: Component {
)) ))
let paidMessageSectionFooterComponent = AnyComponent(MultilineTextComponent( let paidMessageSectionFooterComponent = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: "The price a viewer must pay to send a comment.", font: footerTextFont, textColor: footerTextColor)), text: .plain(NSAttributedString(string: strings.LiveStreamSettings_PricePerCommentInfo, font: footerTextFont, textColor: footerTextColor)),
maximumNumberOfLines: 0 maximumNumberOfLines: 0
)) ))
@ -856,7 +853,7 @@ final class LiveStreamSettingsScreenComponent: Component {
transition.setFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame) transition.setFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
self.bottomEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition) self.bottomEdgeEffectView.update(content: theme.list.blocksBackgroundColor, blur: true, alpha: 1.0, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: edgeEffectFrame.height, transition: transition)
let title: String = screenState.isEdit ? "Live Stream" : "Live Settings" let title: String = screenState.isEdit ? strings.LiveStreamSettings_TitleEdit : strings.LiveStreamSettings_Title
let titleSize = self.title.update( let titleSize = self.title.update(
transition: transition, transition: transition,
component: AnyComponent( component: AnyComponent(
@ -961,7 +958,7 @@ final class LiveStreamSettingsScreenComponent: Component {
content: AnyComponentWithIdentity( content: AnyComponentWithIdentity(
id: "label", id: "label",
component: AnyComponent(ButtonTextContentComponent( component: AnyComponent(ButtonTextContentComponent(
text: "Save Settings", text: strings.LiveStreamSettings_SaveSettings,
badge: 0, badge: 0,
textColor: theme.list.itemCheckColors.foregroundColor, textColor: theme.list.itemCheckColors.foregroundColor,
badgeBackground: theme.list.itemCheckColors.foregroundColor, badgeBackground: theme.list.itemCheckColors.foregroundColor,