mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-04-25 00:56:26 +02:00
feat: новые функции, исправлены критические ошибки сборки и баги интерфейса, больше подписей в файлах
This commit is contained in:
@@ -1,12 +1,22 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import SliderComponent
|
||||
|
||||
private let minDeletedMessageTransparencyPercent: Int32 = Int32(AntiDeleteManager.minDeletedMessageTransparency * 100.0)
|
||||
private let maxDeletedMessageTransparencyPercent: Int32 = Int32(AntiDeleteManager.maxDeletedMessageTransparency * 100.0)
|
||||
|
||||
private func clampDeletedMessageTransparencyPercent(_ value: Int32) -> Int32 {
|
||||
return max(minDeletedMessageTransparencyPercent, min(maxDeletedMessageTransparencyPercent, value))
|
||||
}
|
||||
|
||||
// MARK: - Entry Definition
|
||||
|
||||
@@ -17,6 +27,7 @@ private enum DeletedMessagesSection: Int32 {
|
||||
private enum DeletedMessagesEntry: ItemListNodeEntry {
|
||||
case enableToggle(PresentationTheme, String, Bool)
|
||||
case archiveMediaToggle(PresentationTheme, String, Bool)
|
||||
case transparencySlider(PresentationTheme, Int32, Bool)
|
||||
case settingsInfo(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
@@ -29,8 +40,10 @@ private enum DeletedMessagesEntry: ItemListNodeEntry {
|
||||
return 0
|
||||
case .archiveMediaToggle:
|
||||
return 1
|
||||
case .settingsInfo:
|
||||
case .transparencySlider:
|
||||
return 2
|
||||
case .settingsInfo:
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +61,12 @@ private enum DeletedMessagesEntry: ItemListNodeEntry {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case let .transparencySlider(lhsTheme, lhsValue, lhsIsEnabled):
|
||||
if case let .transparencySlider(rhsTheme, rhsValue, rhsIsEnabled) = rhs,
|
||||
lhsTheme === rhsTheme, lhsValue == rhsValue, lhsIsEnabled == rhsIsEnabled {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case let .settingsInfo(lhsTheme, lhsText):
|
||||
if case let .settingsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@@ -85,6 +104,16 @@ private enum DeletedMessagesEntry: ItemListNodeEntry {
|
||||
arguments.toggleArchiveMedia(value)
|
||||
}
|
||||
)
|
||||
case let .transparencySlider(theme, value, isEnabled):
|
||||
return DeletedMessagesTransparencySliderItem(
|
||||
theme: theme,
|
||||
value: value,
|
||||
isEnabled: isEnabled,
|
||||
sectionId: self.section,
|
||||
updated: { value in
|
||||
arguments.updateTransparency(value)
|
||||
}
|
||||
)
|
||||
case let .settingsInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
@@ -96,13 +125,16 @@ private enum DeletedMessagesEntry: ItemListNodeEntry {
|
||||
private final class DeletedMessagesControllerArguments {
|
||||
let toggleEnabled: (Bool) -> Void
|
||||
let toggleArchiveMedia: (Bool) -> Void
|
||||
let updateTransparency: (Int32) -> Void
|
||||
|
||||
init(
|
||||
toggleEnabled: @escaping (Bool) -> Void,
|
||||
toggleArchiveMedia: @escaping (Bool) -> Void
|
||||
toggleArchiveMedia: @escaping (Bool) -> Void,
|
||||
updateTransparency: @escaping (Int32) -> Void
|
||||
) {
|
||||
self.toggleEnabled = toggleEnabled
|
||||
self.toggleArchiveMedia = toggleArchiveMedia
|
||||
self.updateTransparency = updateTransparency
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,10 +143,12 @@ private final class DeletedMessagesControllerArguments {
|
||||
private struct DeletedMessagesControllerState: Equatable {
|
||||
var isEnabled: Bool
|
||||
var archiveMedia: Bool
|
||||
var transparencyPercent: Int32
|
||||
|
||||
static func ==(lhs: DeletedMessagesControllerState, rhs: DeletedMessagesControllerState) -> Bool {
|
||||
return lhs.isEnabled == rhs.isEnabled &&
|
||||
lhs.archiveMedia == rhs.archiveMedia
|
||||
lhs.archiveMedia == rhs.archiveMedia &&
|
||||
lhs.transparencyPercent == rhs.transparencyPercent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +162,8 @@ private func deletedMessagesControllerEntries(
|
||||
|
||||
entries.append(.enableToggle(presentationData.theme, "Сохранять удалённые сообщения", state.isEnabled))
|
||||
entries.append(.archiveMediaToggle(presentationData.theme, "Архивировать медиа", state.archiveMedia))
|
||||
entries.append(.settingsInfo(presentationData.theme, "Когда включено, сообщения, удалённые другими пользователями, будут сохраняться локально. Рядом со временем сообщения появится иконка корзины."))
|
||||
entries.append(.transparencySlider(presentationData.theme, state.transparencyPercent, state.isEnabled))
|
||||
entries.append(.settingsInfo(presentationData.theme, "Когда включено, сообщения, удалённые другими пользователями, будут сохраняться локально. Прозрачность влияет только на сообщения, которые уже помечены как удалённые."))
|
||||
|
||||
return entries
|
||||
}
|
||||
@@ -138,7 +173,8 @@ private func deletedMessagesControllerEntries(
|
||||
public func deletedMessagesController(context: AccountContext) -> ViewController {
|
||||
let initialState = DeletedMessagesControllerState(
|
||||
isEnabled: AntiDeleteManager.shared.isEnabled,
|
||||
archiveMedia: AntiDeleteManager.shared.archiveMedia
|
||||
archiveMedia: AntiDeleteManager.shared.archiveMedia,
|
||||
transparencyPercent: clampDeletedMessageTransparencyPercent(Int32(round(AntiDeleteManager.shared.deletedMessageTransparency * 100.0)))
|
||||
)
|
||||
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
@@ -163,6 +199,15 @@ public func deletedMessagesController(context: AccountContext) -> ViewController
|
||||
state.archiveMedia = value
|
||||
return state
|
||||
}
|
||||
},
|
||||
updateTransparency: { value in
|
||||
let clampedValue = clampDeletedMessageTransparencyPercent(value)
|
||||
AntiDeleteManager.shared.deletedMessageTransparency = Double(clampedValue) / 100.0
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.transparencyPercent = clampedValue
|
||||
return state
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -195,3 +240,225 @@ public func deletedMessagesController(context: AccountContext) -> ViewController
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
return controller
|
||||
}
|
||||
|
||||
private final class DeletedMessagesTransparencySliderItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let value: Int32
|
||||
let isEnabled: Bool
|
||||
let sectionId: ItemListSectionId
|
||||
let updated: (Int32) -> Void
|
||||
|
||||
init(theme: PresentationTheme, value: Int32, isEnabled: Bool, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) {
|
||||
self.theme = theme
|
||||
self.value = clampDeletedMessageTransparencyPercent(value)
|
||||
self.isEnabled = isEnabled
|
||||
self.sectionId = sectionId
|
||||
self.updated = updated
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = DeletedMessagesTransparencySliderItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? DeletedMessagesTransparencySliderItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class DeletedMessagesTransparencySliderItemNode: ListViewItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let leftTextNode: ImmediateTextNode
|
||||
private let rightTextNode: ImmediateTextNode
|
||||
private let centerTextNode: ImmediateTextNode
|
||||
private let slider = ComponentView<Empty>()
|
||||
|
||||
private var item: DeletedMessagesTransparencySliderItem?
|
||||
|
||||
init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.leftTextNode = ImmediateTextNode()
|
||||
self.rightTextNode = ImmediateTextNode()
|
||||
self.centerTextNode = ImmediateTextNode()
|
||||
|
||||
super.init(layerBacked: false)
|
||||
|
||||
self.addSubnode(self.leftTextNode)
|
||||
self.addSubnode(self.rightTextNode)
|
||||
self.addSubnode(self.centerTextNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: DeletedMessagesTransparencySliderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
return { item, params, neighbors in
|
||||
let separatorHeight = UIScreenPixel
|
||||
let contentSize = CGSize(width: params.width, height: 88.0)
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
|
||||
return (layout, { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.item = item
|
||||
|
||||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
|
||||
}
|
||||
if strongSelf.maskNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
|
||||
}
|
||||
|
||||
let hasCorners = itemListHasRoundedBlockLayout(params)
|
||||
var hasTopCorners = false
|
||||
var hasBottomCorners = false
|
||||
|
||||
switch neighbors.top {
|
||||
case .sameSection(false):
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
default:
|
||||
hasTopCorners = true
|
||||
strongSelf.topStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
let bottomStripeInset: CGFloat
|
||||
let bottomStripeOffset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
bottomStripeInset = 0.0
|
||||
bottomStripeOffset = 0.0
|
||||
hasBottomCorners = true
|
||||
strongSelf.bottomStripeNode.isHidden = hasCorners
|
||||
}
|
||||
|
||||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
let sideTextColor = item.theme.list.itemSecondaryTextColor.withAlphaComponent(item.isEnabled ? 1.0 : 0.6)
|
||||
let centerTextColor = item.isEnabled ? item.theme.list.itemPrimaryTextColor : item.theme.list.itemDisabledTextColor
|
||||
|
||||
strongSelf.leftTextNode.attributedText = NSAttributedString(string: "Меньше", font: Font.regular(13.0), textColor: sideTextColor)
|
||||
strongSelf.rightTextNode.attributedText = NSAttributedString(string: "Больше", font: Font.regular(13.0), textColor: sideTextColor)
|
||||
strongSelf.centerTextNode.attributedText = NSAttributedString(string: "Прозрачность \(item.value)%", font: Font.regular(16.0), textColor: centerTextColor)
|
||||
|
||||
let leftTextSize = strongSelf.leftTextNode.updateLayout(CGSize(width: 120.0, height: 100.0))
|
||||
let rightTextSize = strongSelf.rightTextNode.updateLayout(CGSize(width: 120.0, height: 100.0))
|
||||
let centerTextSize = strongSelf.centerTextNode.updateLayout(CGSize(width: params.width - params.leftInset - params.rightInset - 60.0, height: 100.0))
|
||||
|
||||
let sideInset: CGFloat = 18.0
|
||||
strongSelf.leftTextNode.frame = CGRect(origin: CGPoint(x: params.leftInset + sideInset, y: 15.0), size: leftTextSize)
|
||||
strongSelf.rightTextNode.frame = CGRect(origin: CGPoint(x: params.width - params.leftInset - sideInset - rightTextSize.width, y: 15.0), size: rightTextSize)
|
||||
strongSelf.centerTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - centerTextSize.width) / 2.0), y: 11.0), size: centerTextSize)
|
||||
|
||||
let maxRange = CGFloat(maxDeletedMessageTransparencyPercent - minDeletedMessageTransparencyPercent)
|
||||
let normalizedValue: CGFloat
|
||||
if maxRange.isZero {
|
||||
normalizedValue = 0.0
|
||||
} else {
|
||||
normalizedValue = CGFloat(item.value - minDeletedMessageTransparencyPercent) / maxRange
|
||||
}
|
||||
|
||||
let sliderSize = strongSelf.slider.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
SliderComponent(
|
||||
content: .continuous(.init(
|
||||
value: normalizedValue,
|
||||
minValue: nil,
|
||||
valueUpdated: { [weak self] value in
|
||||
guard let self, let item = self.item, item.isEnabled else {
|
||||
return
|
||||
}
|
||||
|
||||
let transparencyValue = Int32((CGFloat(minDeletedMessageTransparencyPercent) + maxRange * value).rounded())
|
||||
item.updated(clampDeletedMessageTransparencyPercent(transparencyValue))
|
||||
}
|
||||
)),
|
||||
useNative: true,
|
||||
trackBackgroundColor: item.theme.list.itemSwitchColors.frameColor,
|
||||
trackForegroundColor: item.isEnabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)
|
||||
)
|
||||
|
||||
if let sliderView = strongSelf.slider.view {
|
||||
if sliderView.superview == nil {
|
||||
strongSelf.view.addSubview(sliderView)
|
||||
}
|
||||
|
||||
sliderView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - sliderSize.width) / 2.0), y: 36.0), size: sliderSize)
|
||||
sliderView.isUserInteractionEnabled = item.isEnabled
|
||||
sliderView.alpha = item.isEnabled ? 1.0 : 0.55
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ private enum GhostgramSettingsEntry: ItemListNodeEntry {
|
||||
case misc(PresentationTheme, String, String)
|
||||
case deviceSpoof(PresentationTheme, String, String)
|
||||
case voiceMorpher(PresentationTheme, String, String)
|
||||
case sendDelay(PresentationTheme, String, String)
|
||||
case info(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
@@ -37,8 +38,10 @@ private enum GhostgramSettingsEntry: ItemListNodeEntry {
|
||||
return 3
|
||||
case .voiceMorpher:
|
||||
return 4
|
||||
case .info:
|
||||
case .sendDelay:
|
||||
return 5
|
||||
case .info:
|
||||
return 6
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +77,12 @@ private enum GhostgramSettingsEntry: ItemListNodeEntry {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case let .sendDelay(lhsTheme, lhsText, lhsValue):
|
||||
if case let .sendDelay(rhsTheme, rhsText, rhsValue) = rhs,
|
||||
lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case let .info(lhsTheme, lhsText):
|
||||
if case let .info(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@@ -144,6 +153,17 @@ private enum GhostgramSettingsEntry: ItemListNodeEntry {
|
||||
arguments.openVoiceMorpher()
|
||||
}
|
||||
)
|
||||
case let .sendDelay(_, text, value):
|
||||
return ItemListDisclosureItem(
|
||||
presentationData: presentationData,
|
||||
title: text,
|
||||
label: value,
|
||||
sectionId: self.section,
|
||||
style: .blocks,
|
||||
action: {
|
||||
arguments.openSendDelay()
|
||||
}
|
||||
)
|
||||
case let .info(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
@@ -158,19 +178,22 @@ private final class GhostgramSettingsControllerArguments {
|
||||
let openMisc: () -> Void
|
||||
let openDeviceSpoof: () -> Void
|
||||
let openVoiceMorpher: () -> Void
|
||||
let openSendDelay: () -> Void
|
||||
|
||||
init(
|
||||
openDeletedMessages: @escaping () -> Void,
|
||||
openGhostMode: @escaping () -> Void,
|
||||
openMisc: @escaping () -> Void,
|
||||
openDeviceSpoof: @escaping () -> Void,
|
||||
openVoiceMorpher: @escaping () -> Void
|
||||
openVoiceMorpher: @escaping () -> Void,
|
||||
openSendDelay: @escaping () -> Void
|
||||
) {
|
||||
self.openDeletedMessages = openDeletedMessages
|
||||
self.openGhostMode = openGhostMode
|
||||
self.openMisc = openMisc
|
||||
self.openDeviceSpoof = openDeviceSpoof
|
||||
self.openVoiceMorpher = openVoiceMorpher
|
||||
self.openSendDelay = openSendDelay
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +207,8 @@ private struct GhostgramSettingsState: Equatable {
|
||||
var miscActiveCount: Int
|
||||
var deviceSpoofEnabled: Bool
|
||||
var voiceMorpherEnabled: Bool
|
||||
var voiceMorpherPresetName: String
|
||||
var sendDelayEnabled: Bool
|
||||
|
||||
static func current() -> GhostgramSettingsState {
|
||||
return GhostgramSettingsState(
|
||||
@@ -193,7 +218,9 @@ private struct GhostgramSettingsState: Equatable {
|
||||
miscEnabled: MiscSettingsManager.shared.isEnabled,
|
||||
miscActiveCount: MiscSettingsManager.shared.activeFeatureCount,
|
||||
deviceSpoofEnabled: DeviceSpoofManager.shared.isEnabled,
|
||||
voiceMorpherEnabled: VoiceMorpherManager.shared.isEnabled
|
||||
voiceMorpherEnabled: VoiceMorpherManager.shared.isEnabled,
|
||||
voiceMorpherPresetName: VoiceMorpherManager.shared.selectedPreset.name,
|
||||
sendDelayEnabled: SendDelayManager.shared.isEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -223,9 +250,13 @@ private func ghostgramSettingsControllerEntries(
|
||||
entries.append(.deviceSpoof(presentationData.theme, "Подмена устройства", deviceSpoofStatus))
|
||||
|
||||
// Voice Morpher
|
||||
let voiceMorpherStatus = state.voiceMorpherEnabled ? VoiceMorpherManager.shared.selectedPreset.name : "Выкл"
|
||||
let voiceMorpherStatus = state.voiceMorpherEnabled ? state.voiceMorpherPresetName : "Выкл"
|
||||
entries.append(.voiceMorpher(presentationData.theme, "Голосовой двойник", voiceMorpherStatus))
|
||||
|
||||
// Send Delay
|
||||
let sendDelayStatus = state.sendDelayEnabled ? "Вкл" : "Выкл"
|
||||
entries.append(.sendDelay(presentationData.theme, "Отложка сообщений", sendDelayStatus))
|
||||
|
||||
// Info
|
||||
entries.append(.info(presentationData.theme, "Функции конфиденциальности Ghostgram. Скрытые отметки о прочтении, обход исчезающих сообщений, обход защиты от пересылки и другое."))
|
||||
|
||||
@@ -255,6 +286,9 @@ public func ghostgramSettingsController(context: AccountContext) -> ViewControll
|
||||
},
|
||||
openVoiceMorpher: {
|
||||
pushControllerImpl?(voiceMorpherController(context: context), true)
|
||||
},
|
||||
openSendDelay: {
|
||||
pushControllerImpl?(sendDelayController(context: context), true)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -328,7 +328,7 @@ public func miscController(context: AccountContext) -> ViewController {
|
||||
|
||||
let controllerState = ItemListControllerState(
|
||||
presentationData: ItemListPresentationData(presentationData),
|
||||
title: .text("Misc"),
|
||||
title: .text("Прочее"),
|
||||
leftNavigationButton: nil,
|
||||
rightNavigationButton: nil,
|
||||
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
|
||||
|
||||
@@ -90,7 +90,7 @@ class NotificationSearchItemNode: ListViewItemNode {
|
||||
required init() {
|
||||
self.searchBarNode = SearchBarPlaceholderNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
super.init(layerBacked: false)
|
||||
|
||||
self.addSubnode(self.searchBarNode)
|
||||
}
|
||||
@@ -104,16 +104,16 @@ class NotificationSearchItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: NotificationSearchItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let searchBarNodeLayout = self.searchBarNode.asyncLayout()
|
||||
let placeholder = self.placeholder
|
||||
|
||||
return { item, params in
|
||||
let baseWidth = params.width - params.leftInset - params.rightInset
|
||||
|
||||
let backgroundColor = item.theme.chatList.itemBackgroundColor
|
||||
let iconColor = UIColor(rgb: 0x8e8e93)
|
||||
let controlColor = item.theme.chat.inputPanel.panelControlColor
|
||||
|
||||
let placeholderString = NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: UIColor(rgb: 0x8e8e93))
|
||||
let (_, searchBarApply) = searchBarNodeLayout(placeholderString, placeholderString, CGSize(width: baseWidth - 16.0, height: 28.0), 1.0, UIColor(rgb: 0x8e8e93), item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 44.0), insets: UIEdgeInsets())
|
||||
|
||||
@@ -126,10 +126,9 @@ class NotificationSearchItemNode: ListViewItemNode {
|
||||
transition = .immediate
|
||||
}
|
||||
|
||||
strongSelf.searchBarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 8.0, y: 8.0), size: CGSize(width: baseWidth - 16.0, height: 28.0))
|
||||
searchBarApply()
|
||||
|
||||
strongSelf.searchBarNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: baseWidth - 16.0, height: 28.0))
|
||||
let searchBarSize = CGSize(width: baseWidth - 16.0, height: 28.0)
|
||||
strongSelf.searchBarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 8.0, y: 8.0), size: searchBarSize)
|
||||
_ = strongSelf.searchBarNode.updateLayout(placeholderString: placeholderString, compactPlaceholderString: placeholderString, constrainedSize: searchBarSize, expansionProgress: 1.0, iconColor: iconColor, foregroundColor: item.theme.chatList.regularSearchBarColor, backgroundColor: backgroundColor, controlColor: controlColor, transition: transition)
|
||||
|
||||
transition.updateBackgroundColor(node: strongSelf, color: backgroundColor)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import AccountContext
|
||||
|
||||
// MARK: - Section / Entry definitions
|
||||
|
||||
private enum SendDelaySection: Int32 {
|
||||
case main
|
||||
}
|
||||
|
||||
private enum SendDelayEntry: ItemListNodeEntry {
|
||||
case toggle(PresentationTheme, String, Bool)
|
||||
case info(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
return SendDelaySection.main.rawValue
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .toggle: return 0
|
||||
case .info: return 1
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: SendDelayEntry, rhs: SendDelayEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .toggle(lhsTheme, lhsText, lhsValue):
|
||||
if case let .toggle(rhsTheme, rhsText, rhsValue) = rhs,
|
||||
lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
case let .info(lhsTheme, lhsText):
|
||||
if case let .info(rhsTheme, rhsText) = rhs,
|
||||
lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: SendDelayEntry, rhs: SendDelayEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! SendDelayControllerArguments
|
||||
switch self {
|
||||
case let .toggle(_, text, value):
|
||||
return ItemListSwitchItem(
|
||||
presentationData: presentationData,
|
||||
title: text,
|
||||
value: value,
|
||||
sectionId: self.section,
|
||||
style: .blocks,
|
||||
updated: { newValue in
|
||||
arguments.toggleEnabled(newValue)
|
||||
}
|
||||
)
|
||||
case let .info(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Arguments
|
||||
|
||||
private final class SendDelayControllerArguments {
|
||||
let toggleEnabled: (Bool) -> Void
|
||||
init(toggleEnabled: @escaping (Bool) -> Void) {
|
||||
self.toggleEnabled = toggleEnabled
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - State
|
||||
|
||||
private struct SendDelayControllerState: Equatable {
|
||||
var isEnabled: Bool
|
||||
}
|
||||
|
||||
// MARK: - Entries builder
|
||||
|
||||
private func sendDelayControllerEntries(
|
||||
presentationData: PresentationData,
|
||||
state: SendDelayControllerState
|
||||
) -> [SendDelayEntry] {
|
||||
let theme = presentationData.theme
|
||||
return [
|
||||
.toggle(theme, "Использовать отложку", state.isEnabled),
|
||||
.info(theme, "Автоматически ставит задержку в ~12 секунд (дольше для сообщений с вложениями) при отправке сообщений. При использовании этой функции вы не будете появляться в сети.")
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: - Controller
|
||||
|
||||
public func sendDelayController(context: AccountContext) -> ViewController {
|
||||
let statePromise = ValuePromise(
|
||||
SendDelayControllerState(isEnabled: SendDelayManager.shared.isEnabled),
|
||||
ignoreRepeated: true
|
||||
)
|
||||
let stateValue = Atomic(value: SendDelayControllerState(isEnabled: SendDelayManager.shared.isEnabled))
|
||||
|
||||
let updateState: ((inout SendDelayControllerState) -> Void) -> Void = { f in
|
||||
let result = stateValue.modify { state in
|
||||
var s = state; f(&s); return s
|
||||
}
|
||||
statePromise.set(result)
|
||||
}
|
||||
|
||||
let arguments = SendDelayControllerArguments(
|
||||
toggleEnabled: { value in
|
||||
SendDelayManager.shared.isEnabled = value
|
||||
updateState { $0.isEnabled = value }
|
||||
}
|
||||
)
|
||||
|
||||
let signal = combineLatest(
|
||||
context.sharedContext.presentationData,
|
||||
statePromise.get()
|
||||
)
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let entries = sendDelayControllerEntries(presentationData: presentationData, state: state)
|
||||
|
||||
let controllerState = ItemListControllerState(
|
||||
presentationData: ItemListPresentationData(presentationData),
|
||||
title: .text("Отложка сообщений"),
|
||||
leftNavigationButton: nil,
|
||||
rightNavigationButton: nil,
|
||||
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
|
||||
animateChanges: false
|
||||
)
|
||||
let listState = ItemListNodeState(
|
||||
presentationData: ItemListPresentationData(presentationData),
|
||||
entries: entries,
|
||||
style: .blocks,
|
||||
animateChanges: true
|
||||
)
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
return ItemListController(context: context, state: signal)
|
||||
}
|
||||
Reference in New Issue
Block a user