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.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()
}
})
if let layout = strongSelf.validLayout, layout.metrics.isTablet {
controller.navigationPresentation = .standaloneFlatModal
} else {
controller.navigationPresentation = .flatModal
}
navigationController.pushViewController(controller)
}
})

View File

@ -75,6 +75,12 @@ struct CameraState: Equatable {
}
}
enum StreamingMode {
case none
case camera
case rtmp
}
let mode: CameraMode
let position: Camera.Position
let flashMode: Camera.FlashMode
@ -87,7 +93,7 @@ struct CameraState: Equatable {
let isCollageEnabled: Bool
let collageGrid: Camera.CollageGrid
let collageProgress: Float
let isStreaming: Bool
let isStreaming: StreamingMode
let isWaitingForStream: Bool
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)
}
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)
}
@ -1133,7 +1139,7 @@ private final class CameraScreenComponent: CombinedComponent {
}
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)
})
}
@ -1155,9 +1161,9 @@ private final class CameraScreenComponent: CombinedComponent {
return
}
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
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)
return
}
@ -1180,17 +1186,17 @@ private final class CameraScreenComponent: CombinedComponent {
let alertController = textAlertController(
context: self.context,
forceTheme: defaultDarkColorPresentationTheme,
title: "End Live Stream",
text: "Are you sure you want to end this live stream?",
title: presentationData.strings.Camera_LiveStream_End_Title,
text: presentationData.strings.Camera_LiveStream_End_Text,
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 {
return
}
let _ = self.liveStreamCall?.leave(terminateIfPossible: true).startStandalone()
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 {
return
}
@ -1206,11 +1212,11 @@ private final class CameraScreenComponent: CombinedComponent {
let alertController = textAlertController(
context: self.context,
forceTheme: defaultDarkColorPresentationTheme,
title: "End Live Stream",
text: "Are you sure you want to end this live stream?",
title: presentationData.strings.Camera_LiveStream_End_Title,
text: presentationData.strings.Camera_LiveStream_End_Text,
actions: [
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 {
return
}
@ -1435,7 +1441,7 @@ private final class CameraScreenComponent: CombinedComponent {
case .video:
shutterState = .video
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(
component: CameraLiveStreamComponent(
context: component.context,
@ -1607,7 +1613,7 @@ private final class CameraScreenComponent: CombinedComponent {
let topControlVerticalInset: CGFloat = 12.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(
component: PlainButtonComponent(
content: AnyComponent(
@ -1647,7 +1653,7 @@ private final class CameraScreenComponent: CombinedComponent {
}
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(
component: CameraButton(
content: AnyComponentWithIdentity(
@ -1728,22 +1734,23 @@ private final class CameraScreenComponent: CombinedComponent {
}
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 {
let endStreamButton = endStreamButton.update(
component: GlassBarButtonComponent(
size: CGSize(width: 56.0, height: 40.0),
size: nil,
backgroundColor: UIColor.black.withAlphaComponent(0.5),
isDark: true,
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
if let state {
state.endLiveStream()
}
}
),
availableSize: CGSize(width: 40.0, height: 40.0),
availableSize: CGSize(width: 120.0, height: 40.0),
transition: .immediate
)
context.add(endStreamButton
@ -1751,12 +1758,13 @@ private final class CameraScreenComponent: CombinedComponent {
.appear(.default(scale: true))
.disappear(.default(scale: true))
)
endStreamButtonWidth = endStreamButton.size.width
}
let rightMostButtonWidth: CGFloat
if component.cameraState.mode == .live {
if component.cameraState.isStreaming {
rightMostButtonWidth = -25.0
if component.cameraState.isStreaming != .none {
rightMostButtonWidth = -25.0 + (endStreamButtonWidth - 56.0) * 2.0
} else {
rightMostButtonWidth = -55.0
}
@ -1824,7 +1832,7 @@ private final class CameraScreenComponent: CombinedComponent {
if !isSticker && !isAvatar && !isTablet {
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(
component: CameraButton(
content: AnyComponentWithIdentity(
@ -1856,7 +1864,35 @@ private final class CameraScreenComponent: CombinedComponent {
}
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 {
let collageButton = collageButton.update(
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
if isTablet {
availableModeControlSize = CGSize(width: panelWidth, height: 120.0)
@ -2474,7 +2510,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
isCollageEnabled: false,
collageGrid: collageGrids[6],
collageProgress: 0.0,
isStreaming: false,
isStreaming: .none,
isWaitingForStream: false
)
@ -2900,7 +2936,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
case .began:
break
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 {
switch controller.mode {
case .story:

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ public final class GlassBarButtonComponent: Component {
case tintedGlass
}
public let size: CGSize
public let size: CGSize?
public let backgroundColor: UIColor
public let isDark: Bool
public let state: DisplayState?
@ -21,7 +21,7 @@ public final class GlassBarButtonComponent: Component {
public let action: ((UIView) -> Void)?
public init(
size: CGSize,
size: CGSize?,
backgroundColor: UIColor,
isDark: Bool,
state: DisplayState? = nil,
@ -149,9 +149,17 @@ public final class GlassBarButtonComponent: Component {
transition: componentTransition,
component: component.component.component,
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 view.superview == nil {
self.containerView.addSubview(view)
@ -175,11 +183,11 @@ public final class GlassBarButtonComponent: Component {
genericAlpha = 0.0
}
let cornerRadius = component.size.height * 0.5
self.genericBackgroundView.update(size: component.size, 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)
let cornerRadius = containerSize.height * 0.5
self.genericBackgroundView.update(size: containerSize, cornerRadius: cornerRadius, isDark: component.isDark, tintColor: .init(kind: .custom, color: component.backgroundColor), 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.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.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 theme = environment.theme.withModalBlocksBackground()
let strings = environment.strings
guard let screenState = component.stateContext.stateValue else {
return CGSize()
@ -318,7 +319,7 @@ final class LiveStreamSettingsScreenComponent: Component {
let streamAsSectionHeader = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "START LIVE AS",
string: strings.LiveStreamSettings_StartLiveAs,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: theme.list.freeTextColor
)),
@ -587,7 +588,7 @@ final class LiveStreamSettingsScreenComponent: Component {
let privacySectionHeader = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "WHO CAN VIEW THIS LIVE",
string: strings.LiveStreamSettings_WhoCanView,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: theme.list.freeTextColor
)),
@ -596,7 +597,7 @@ final class LiveStreamSettingsScreenComponent: Component {
let privacySectionFooter = AnyComponent(MultilineTextComponent(
text: .markdown(
text: "[Select people]() who won't see your live.",
text: strings.LiveStreamSettings_WhoCanViewInfo,
attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: footerTextFont, 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) {
let externalStreamSectionItems = [AnyComponentWithIdentity(id: 0, component: AnyComponent(
ListActionItemComponent(
theme: theme,
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
guard let self else {
return
@ -682,7 +682,7 @@ final class LiveStreamSettingsScreenComponent: Component {
)
))]
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
))
@ -710,15 +710,13 @@ final class LiveStreamSettingsScreenComponent: Component {
contentHeight += sectionSpacing
}
//TODO:localize
var settingsSectionItems: [AnyComponentWithIdentity<Empty>] = []
settingsSectionItems.append(AnyComponentWithIdentity(id: "comments", component: AnyComponent(ListActionItemComponent(
theme: theme,
style: .glass,
title: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Allow Comments",
string: strings.LiveStreamSettings_AllowComments,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: theme.list.itemPrimaryTextColor
)),
@ -735,13 +733,12 @@ final class LiveStreamSettingsScreenComponent: Component {
))))
if !(screenState.call != nil && screenState.isEdit) {
//TODO:localize
settingsSectionItems.append(AnyComponentWithIdentity(id: "screenshots", component: AnyComponent(ListActionItemComponent(
theme: theme,
style: .glass,
title: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "Allow Screenshots",
string: strings.LiveStreamSettings_AllowScreenshots,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: theme.list.itemPrimaryTextColor
)),
@ -789,7 +786,7 @@ final class LiveStreamSettingsScreenComponent: Component {
minValue: 0,
lowerBoundTitle: "0",
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
guard let self, let component = self.component else {
return
@ -805,7 +802,7 @@ final class LiveStreamSettingsScreenComponent: Component {
if screenState.allowComments {
let paidMessageSectionHeader = AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: "PRICE PER COMMENT",
string: strings.LiveStreamSettings_PricePerComment,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: theme.list.freeTextColor
)),
@ -813,7 +810,7 @@ final class LiveStreamSettingsScreenComponent: Component {
))
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
))
@ -856,7 +853,7 @@ final class LiveStreamSettingsScreenComponent: Component {
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)
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(
transition: transition,
component: AnyComponent(
@ -961,7 +958,7 @@ final class LiveStreamSettingsScreenComponent: Component {
content: AnyComponentWithIdentity(
id: "label",
component: AnyComponent(ButtonTextContentComponent(
text: "Save Settings",
text: strings.LiveStreamSettings_SaveSettings,
badge: 0,
textColor: theme.list.itemCheckColors.foregroundColor,
badgeBackground: theme.list.itemCheckColors.foregroundColor,