chore: prerelease 0.1.0

This commit is contained in:
IMDelewer 2026-06-12 19:26:54 +03:00
parent 6e370e06d1
commit c37205ebc7
37 changed files with 56411 additions and 110 deletions

View file

@ -13,7 +13,7 @@ python3 build-system/Make/Make.py --overrideXcodeVersion \
--cacheDir ~/telegram-bazel-cache \
build \
--configurationPath build-system/appstore-configuration.json \
--gitCodesigningRepository git@gitlab.com:peter-iakovlev/fastlanematch.git \
--gitCodesigningRepository <your-codesigning-repo> \
--gitCodesigningType development --gitCodesigningUseCurrent --buildNumber=1 --configuration=debug_sim_arm64
```

10
MAINTAINERS.md Normal file
View file

@ -0,0 +1,10 @@
# Maintainers
WinterGram is developed and maintained under the [reekeer](https://github.com/reekeer) organization.
| Maintainer | GitHub |
| :--- | :--- |
| IMDelewer | [@IMDelewer](https://github.com/IMDelewer) |
| salenyo | [@salenyo](https://github.com/salenyo) |
For questions, contributions, or issues, open a pull request or issue on the [WinterGram repository](https://github.com/reekeer/WinterGram).

192
README.md
View file

@ -1,116 +1,122 @@
# Telegram iOS Source Code Compilation Guide
<h1 align="center">WinterGram</h1>
We welcome all developers to use our API and source code to create applications on our platform.
There are several things we require from **all developers** for the moment.
<h4 align="center">WinterGram (Wnt) is a feature-rich, privacy-focused Telegram client for iPhone — a native iOS port of the AyuGram experience, built on top of Telegram-iOS.</h4>
# Creating your Telegram Application
<p align="center">
<a href="LICENSE"><img src="https://img.shields.io/badge/License-GPLv2-green?style=for-the-badge&logo=gnu&logoColor=FFFFFF" alt="License"></a>
<img src="https://img.shields.io/badge/Platform-iOS%2015%2B-black?style=for-the-badge&logo=apple&logoColor=white" alt="Platform">
<img src="https://img.shields.io/badge/Language-Swift-orange?style=for-the-badge&logo=swift&logoColor=white" alt="Swift">
<img src="https://img.shields.io/badge/Build-Bazel-43A047?style=for-the-badge&logo=bazel&logoColor=white" alt="Bazel">
<img src="https://img.shields.io/github/stars/reekeer/WinterGram?style=for-the-badge&logo=github&logoColor=white" alt="Stars">
<img src="https://img.shields.io/github/last-commit/reekeer/WinterGram?style=for-the-badge&logo=github&logoColor=white" alt="Last Commit">
</p>
1. [**Obtain your own api_id**](https://core.telegram.org/api/obtaining_api_id) for your application.
2. Please **do not** use the name Telegram for your app — or make sure your users understand that it is unofficial.
3. Kindly **do not** use our standard logo (white paper plane in a blue circle) as your app's logo.
3. Please study our [**security guidelines**](https://core.telegram.org/mtproto/security_guidelines) and take good care of your users' data and privacy.
4. Please remember to publish **your** code too in order to comply with the licences.
---
# Quick Compilation Guide
**WinterGram** brings the most-loved AyuGram features to iOS with a clean, Material-inspired interface, a configurable **Liquid Glass** appearance, and a single dedicated settings tab where everything lives. It speaks both the standard `tg://` deep links and its own `wnt://` scheme.
## Get the Code
---
## ✨ Features
### 👻 Privacy & Ghost Mode
- **Ghost Mode**: don't send read receipts, typing/upload status, or online presence — toggle it all at once.
- **Send without sound**: never / only in Ghost Mode / always.
- **Story ghost**: view stories without marking them seen, with an optional confirmation prompt.
- **Mark read after action**, **go offline after going online**, and per-toggle locks.
### 🗂 History & Recovery
- **Save deleted messages**: keep messages locally even after the other side deletes them.
- **Edit history**: store every revision of a message and browse it.
- **Semi-transparent deleted markers** and a customizable deleted / edited mark.
### 🧊 Hidden Archive ("AАrchive")
- Stash chats into a **separate, settings-only archive** — no notifications, no badge.
- Optional **auto-mark-read** for everything sent to the stash.
### 🛡️ Anti-Features
- **Disable ads** (sponsored messages).
- **Local Telegram Premium** — unlock Premium-gated UI locally.
- **Shadow ban** — silently hide a user's messages from your view.
- **Hide / disable stories**, **hide Premium statuses**, **disable open-link warning**.
### 💬 Chat Conveniences
- **Sticker / GIF / voice send confirmations**.
- **Message seconds** in timestamps and **peer ID** display (Telegram or Bot API form).
- **Message translation** with a selectable provider (Telegram / Google / Yandex / system).
- **WebView platform spoofing** (auto / iOS / Android / macOS / desktop) and taller WebViews.
### 🎨 Appearance & Customization
- **Liquid Glass** — frosted, translucent surfaces across the chat list, navigation, and tab bar, with **on/off toggle**, adjustable **transparency**, **blur radius**, **tint**, and per-surface application.
- **Material Design** switches and controls.
- **Avatar corner radius** (round → squircle → square) and **message bubble radius**, with optional single-corner mode.
- **Custom fonts** (UI + monospace).
- **App icons**, including the bundled WinterGram dark icon, plus **AyuGram / exteraGram icon-pack compatibility**.
- **Custom emoji** support, with an option to show only your added emoji & stickers.
---
## 🔗 Deep Links
WinterGram registers and resolves two URL schemes — anything that works with `tg://` works with `wnt://`:
```
git clone --recursive -j8 https://github.com/TelegramMessenger/Telegram-iOS.git
tg://resolve?domain=durov
wnt://resolve?domain=durov
```
## Setup Xcode
`wnt://` links are normalized to the standard resolver at the entry point, so they route through exactly the same handling as native Telegram links.
Install Xcode (directly from https://developer.apple.com/download/applications or using the App Store).
---
## Adjust Configuration
## 🚀 Build
1. Generate a random identifier:
```
openssl rand -hex 8
```
2. Create a new Xcode project. Use `Telegram` as the Product Name. Use `org.{identifier from step 1}` as the Organization Identifier.
3. Open `Keychain Access` and navigate to `Certificates`. Locate `Apple Development: your@email.address (XXXXXXXXXX)` and double tap the certificate. Under `Details`, locate `Organizational Unit`. This is the Team ID.
4. Edit `build-system/template_minimal_development_configuration.json`. Use data from the previous steps.
WinterGram builds with the standard Telegram-iOS toolchain (Bazel via the `Make.py` wrapper) on **macOS with Xcode**.
## Generate an Xcode project
```
python3 build-system/Make/Make.py \
--cacheDir="$HOME/telegram-bazel-cache" \
generateProject \
--configurationPath=build-system/template_minimal_development_configuration.json \
--xcodeManagedCodesigning
```sh
python3 build-system/Make/Make.py --overrideXcodeVersion \
--cacheDir ~/telegram-bazel-cache \
build \
--configurationPath build-system/appstore-configuration.json \
--gitCodesigningRepository <your-codesigning-repo> \
--gitCodesigningType development --gitCodesigningUseCurrent \
--buildNumber=1 --configuration=debug_sim_arm64
```
# Advanced Compilation Guide
See [`docs/wintergram-features.md`](docs/wintergram-features.md) for the feature → implementation map and the project's architecture notes.
## Xcode
---
1. Copy and edit `build-system/appstore-configuration.json`.
2. Copy `build-system/fake-codesigning`. Create and download provisioning profiles, using the `profiles` folder as a reference for the entitlements.
3. Generate an Xcode project:
```
python3 build-system/Make/Make.py \
--cacheDir="$HOME/telegram-bazel-cache" \
generateProject \
--configurationPath=configuration_from_step_1.json \
--codesigningInformationPath=directory_from_step_2
```
## ⚙️ Configuration
## IPA
All WinterGram options live in a single settings store (`WinterGramSettings`), persisted with the app's shared-data system and exposed through reactive signals. There is one dedicated **WinterGram** tab in Settings — no scattered toggles.
1. Repeat the steps from the previous section. Use distribution provisioning profiles.
2. Run:
```
python3 build-system/Make/Make.py \
--cacheDir="$HOME/telegram-bazel-cache" \
build \
--configurationPath=...see previous section... \
--codesigningInformationPath=...see previous section... \
--buildNumber=100001 \
--configuration=release_arm64
```
---
# FAQ
## Xcode is stuck at "build-request.json not updated yet"
Occasionally, you might observe the following message in your build log:
```
"/Users/xxx/Library/Developer/Xcode/DerivedData/Telegram-xxx/Build/Intermediates.noindex/XCBuildData/xxx.xcbuilddata/build-request.json" not updated yet, waiting...
```
Should this occur, simply cancel the ongoing build and initiate a new one.
## Telegram_xcodeproj: no such package
Following a system restart, the auto-generated Xcode project might encounter a build failure accompanied by this error:
```
ERROR: Skipping '@rules_xcodeproj_generated//generator/Telegram/Telegram_xcodeproj:Telegram_xcodeproj': no such package '@rules_xcodeproj_generated//generator/Telegram/Telegram_xcodeproj': BUILD file not found in directory 'generator/Telegram/Telegram_xcodeproj' of external repository @rules_xcodeproj_generated. Add a BUILD file to a directory to mark it as a package.
```
If you encounter this issue, re-run the project generation steps in the README.
# Tips
## Codesigning is not required for simulator-only builds
Add `--disableProvisioningProfiles`:
```
python3 build-system/Make/Make.py \
--cacheDir="$HOME/telegram-bazel-cache" \
generateProject \
--configurationPath=path-to-configuration.json \
--codesigningInformationPath=path-to-provisioning-data \
--disableProvisioningProfiles
```
## Versions
Each release is built using a specific Xcode version (see `versions.json`). The helper script checks the versions of the installed software and reports an error if they don't match the ones specified in `versions.json`. It is possible to bypass these checks:
## 🗂 Structure
```
python3 build-system/Make/Make.py --overrideXcodeVersion build ... # Don't check the version of Xcode
WinterGram/
├── Telegram/ ← App entry points and extensions
├── submodules/ ← Feature libraries (Swift / Obj-C)
│ └── TelegramUIPreferences/
│ └── Sources/WinterGramSettings.swift ← all WinterGram options
├── branding/ ← WinterGram icons and brand assets
├── docs/ ← Architecture and feature documentation
├── build-system/ ← Bazel build wrapper (Make.py)
└── README.md
```
---
## 🤝 Contributing
Contributions are welcome. WinterGram is maintained by [**IMDelewer**](https://github.com/IMDelewer) and [**salenyo**](https://github.com/salenyo) under the [reekeer](https://github.com/reekeer) organization. See [`MAINTAINERS.md`](MAINTAINERS.md).
---
<p align="center">
Built on Telegram-iOS · inspired by AyuGram
</p>
<p align="center"><sub><a href="LICENSE">GPLv2</a> © reekeer</sub></p>

View file

@ -0,0 +1,116 @@
{
"images" : [
{
"filename" : "Icon-40.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "Icon-60.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "Icon-58.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "Icon-87.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "Icon-80.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon-120.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "Icon-120.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "Icon-180.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "Icon-20.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"filename" : "Icon-40.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "Icon-29.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"filename" : "Icon-58.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "Icon-40.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"filename" : "Icon-80.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon-76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "Icon-152.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "Icon-167.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "Icon-1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "reekeer",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -212,6 +212,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>tg</string>
<string>wnt</string>
<string>$(APP_SPECIFIC_URL_SCHEME)</string>
</array>
</dict>

View file

@ -46,6 +46,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>tg</string>
<string>wnt</string>
<string>$(APP_SPECIFIC_URL_SCHEME)</string>
</array>
</dict>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

54931
branding/wintergram-icon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 MiB

View file

@ -0,0 +1,96 @@
# WinterGram — feature → implementation map
This document maps every WinterGram option to where it is (or needs to be) wired into the
Telegram-iOS codebase. The settings themselves live in a single store:
- `submodules/TelegramUIPreferences/Sources/WinterGramSettings.swift` — the `WinterGramSettings`
`Codable` struct, its sub-structs (`WinterGramLiquidGlass`) and enums, plus
`updateWinterGramSettingsInteractively(...)` and `winterGramSettings(accountManager:)`.
- `submodules/TelegramUIPreferences/Sources/PostboxKeys.swift` — the shared-data key
`ApplicationSpecificSharedDataKeys.winterGramSettings` (value `23`).
Read the store anywhere that already has an `AccountContext` / `AccountManager` via
`winterGramSettings(accountManager:)`, and write it via `updateWinterGramSettingsInteractively`.
## Status legend
- **Store** — the setting exists and persists (done in this repo).
- **Hook** — the place in the app where behavior must read the setting.
## Privacy & Ghost Mode
| Setting | Hook |
| :-- | :-- |
| `ghostModeEnabled` | Master switch read by the read-receipt / online-status / typing senders below. |
| `sendReadReceipts` | `TelegramCore` history read — gate `_internal_applyMaxReadIndex` / outgoing `messages.readHistory` calls. |
| `sendReadStories` | Story view reporting — gate `markStoryAsSeen` network calls. |
| `sendOnlineStatus` | Online presence — gate `account.updatePresence` / `updateStatus`. |
| `sendUploadProgress` | Typing/upload activity — gate `ChatActivity` / `setTyping` reporting. |
| `sendOfflineAfterOnline` | Emit a one-shot offline presence packet after the app goes online. |
| `markReadAfterAction` | After replying/reacting, locally mark the chat read without sending receipts. |
| `useScheduledMessages` | "Отложка" — when ghosting, send via the scheduled-messages path. |
| `sendWithoutSound` | Outgoing message flags — set `silent` per `shouldSendWithoutSound`. |
| `suggestGhostBeforeStory` | Story viewer — present the ghost confirmation before opening. |
## History & Recovery
| Setting | Hook |
| :-- | :-- |
| `saveDeletedMessages` | Hook the deletion path in `Postbox` history removal; mirror messages into a local store before they are purged. |
| `saveMessagesHistory` | On `EditMessage` updates, append the previous version to a local edit-history store. |
| `semiTransparentDeletedMessages` | `ChatMessageItemView` — render saved-deleted bubbles at reduced alpha. |
| `deletedMark` / `editedMark` | Message footer rendering in the bubble content nodes. |
## Hidden Archive ("AАrchive")
| Setting | Hook |
| :-- | :-- |
| `stashedPeerIds` | Filter the chat list to hide these peers from the main list; show them only in the dedicated WinterGram archive screen. |
| `stashMuteNotifications` | Notification service extension — suppress notifications for stashed peers. |
| `stashAutoMarkRead` | On receiving from a stashed peer, locally mark read (respecting Ghost Mode). |
## Anti-Features
| Setting | Hook |
| :-- | :-- |
| `disableAds` | Sponsored-messages fetch in `TelegramCore` (`getAdMessages`) — return empty when disabled. |
| `localPremium` | `isPremium` resolution in the UI layer — treat as Premium locally for gated UI. |
| `shadowBanIds` | Chat history filtering — drop incoming messages from these peers from the rendered list. |
| `disableStories` | Story list assembly — hide the stories strip. |
| `hidePremiumStatuses` | Peer title rendering — drop Premium/emoji-status badges. |
| `disableOpenLinkWarning` | URL open path — skip the "open this link?" confirmation. |
## Chat Conveniences
| Setting | Hook |
| :-- | :-- |
| `stickerConfirmation` / `gifConfirmation` / `voiceConfirmation` | Send paths in the chat input panel — present a confirm alert before sending. |
| `showMessageSeconds` | Timestamp formatting in the bubble footer. |
| `showPeerId` | Peer info / chat title — append the ID in Telegram or Bot API form. |
| `translateMessages` / `translationProvider` | Message context menu translate action + provider selection. |
| `webviewSpoofPlatform` / `increaseWebviewHeight` | WebApp controller — set the spoofed `tg_platform` / viewport height. |
## Appearance & Customization
| Setting | Hook |
| :-- | :-- |
| `liquidGlass.*` | `Display` blur/material layers behind the chat list, nav bar, and tab bar; read `enabled`, `transparency`, `blurRadius`, `tintColor`, per-surface flags. |
| `materialDesign` | Switch/control styling in `ItemListUI` components. |
| `avatarCornerRadius` / `singleCornerRadius` | Avatar node corner rounding in `AvatarNode`. |
| `messageBubbleRadius` / `removeMessageTail` | Bubble background drawing in the chat message backgrounds. |
| `customFont` / `monoFont` | `PresentationData` font resolution. |
| `appIcon` / `iconPack` | Alternate-icon switching via `UIApplication.setAlternateIconName`; see `Telegram/Telegram-iOS/DefaultAppIcon.xcassets/WinterGramDarkIcon.appiconset`. |
| `showOnlyAddedEmojisAndStickers` | Emoji/sticker panel data sources — filter to installed packs. |
## Deep links — `wnt://`
Registered in `Telegram/Telegram-iOS/Info.plist` and `InfoBazel.plist` (alongside `tg`).
Normalized to `tg://` at the app entry by `normalizeWinterGramUrlScheme(_:)` in
`submodules/TelegramUI/Sources/AppDelegate.swift`, so every `tg://` route also accepts `wnt://`.
## Settings UI
A dedicated **WinterGram** entry should be added to the settings list
(`submodules/SettingsUI` / the PeerInfo settings screen) that opens an `ItemListController`
backed by `winterGramSettings(accountManager:)` and writing through
`updateWinterGramSettingsInteractively`. Group the rows by the sections above.

View file

@ -4,8 +4,8 @@ import TelegramCore
import TelegramUIPreferences
import AccountContext
public let maximumNumberOfAccounts = 3
public let maximumPremiumNumberOfAccounts = 4
public let maximumNumberOfAccounts = 100
public let maximumPremiumNumberOfAccounts = 100
public func activeAccountsAndPeers(context: AccountContext, includePrimary: Bool = false) -> Signal<((AccountContext, EnginePeer)?, [(AccountContext, EnginePeer, Int32)]), NoError> {
let sharedContext = context.sharedContext

View file

@ -0,0 +1,607 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import AccountContext
private final class WinterGramSettingsArguments {
let updateSettings: (@escaping (WinterGramSettings) -> WinterGramSettings) -> Void
let presentSendWithoutSound: () -> Void
let presentPeerId: () -> Void
let presentTranslationProvider: () -> Void
let presentWebviewPlatform: () -> Void
let presentIconPack: () -> Void
init(
updateSettings: @escaping (@escaping (WinterGramSettings) -> WinterGramSettings) -> Void,
presentSendWithoutSound: @escaping () -> Void,
presentPeerId: @escaping () -> Void,
presentTranslationProvider: @escaping () -> Void,
presentWebviewPlatform: @escaping () -> Void,
presentIconPack: @escaping () -> Void
) {
self.updateSettings = updateSettings
self.presentSendWithoutSound = presentSendWithoutSound
self.presentPeerId = presentPeerId
self.presentTranslationProvider = presentTranslationProvider
self.presentWebviewPlatform = presentWebviewPlatform
self.presentIconPack = presentIconPack
}
}
private enum WinterGramSettingsSection: Int32 {
case ghost
case history
case stash
case antiFeatures
case confirmations
case chat
case appearance
case liquidGlass
}
private enum WinterGramSettingsEntry: ItemListNodeEntry {
case ghostHeader
case ghostEnabled(Bool)
case ghostReadReceipts(Bool)
case ghostReadStories(Bool)
case ghostOnlineStatus(Bool)
case ghostUploadProgress(Bool)
case ghostOfflineAfterOnline(Bool)
case ghostMarkReadAfterAction(Bool)
case ghostUseScheduled(Bool)
case ghostSendWithoutSound(String)
case ghostSuggestBeforeStory(Bool)
case ghostFooter
case historyHeader
case historySaveDeleted(Bool)
case historySaveEdits(Bool)
case historySemiTransparent(Bool)
case historyFooter
case stashHeader
case stashMute(Bool)
case stashAutoRead(Bool)
case stashFooter
case antiHeader
case antiDisableAds(Bool)
case antiLocalPremium(Bool)
case antiDisableStories(Bool)
case antiHidePremiumStatuses(Bool)
case antiDisableLinkWarning(Bool)
case antiFooter
case confirmHeader
case confirmStickers(Bool)
case confirmGif(Bool)
case confirmVoice(Bool)
case chatHeader
case chatShowSeconds(Bool)
case chatShowPeerId(String)
case chatTranslate(Bool)
case chatTranslateProvider(String)
case chatWebviewPlatform(String)
case chatWebviewHeight(Bool)
case chatOnlyAddedEmoji(Bool)
case appearanceHeader
case appearanceMaterial(Bool)
case appearanceSingleCorner(Bool)
case appearanceIconPack(String)
case glassHeader
case glassEnabled(Bool)
case glassVibrancy(Bool)
case glassChatList(Bool)
case glassNavBars(Bool)
case glassTabBar(Bool)
case glassBubbles(Bool)
case glassFooter
var section: ItemListSectionId {
switch self {
case .ghostHeader, .ghostEnabled, .ghostReadReceipts, .ghostReadStories, .ghostOnlineStatus, .ghostUploadProgress, .ghostOfflineAfterOnline, .ghostMarkReadAfterAction, .ghostUseScheduled, .ghostSendWithoutSound, .ghostSuggestBeforeStory, .ghostFooter:
return WinterGramSettingsSection.ghost.rawValue
case .historyHeader, .historySaveDeleted, .historySaveEdits, .historySemiTransparent, .historyFooter:
return WinterGramSettingsSection.history.rawValue
case .stashHeader, .stashMute, .stashAutoRead, .stashFooter:
return WinterGramSettingsSection.stash.rawValue
case .antiHeader, .antiDisableAds, .antiLocalPremium, .antiDisableStories, .antiHidePremiumStatuses, .antiDisableLinkWarning, .antiFooter:
return WinterGramSettingsSection.antiFeatures.rawValue
case .confirmHeader, .confirmStickers, .confirmGif, .confirmVoice:
return WinterGramSettingsSection.confirmations.rawValue
case .chatHeader, .chatShowSeconds, .chatShowPeerId, .chatTranslate, .chatTranslateProvider, .chatWebviewPlatform, .chatWebviewHeight, .chatOnlyAddedEmoji:
return WinterGramSettingsSection.chat.rawValue
case .appearanceHeader, .appearanceMaterial, .appearanceSingleCorner, .appearanceIconPack:
return WinterGramSettingsSection.appearance.rawValue
case .glassHeader, .glassEnabled, .glassVibrancy, .glassChatList, .glassNavBars, .glassTabBar, .glassBubbles, .glassFooter:
return WinterGramSettingsSection.liquidGlass.rawValue
}
}
var stableId: Int32 {
switch self {
case .ghostHeader: return 0
case .ghostEnabled: return 1
case .ghostReadReceipts: return 2
case .ghostReadStories: return 3
case .ghostOnlineStatus: return 4
case .ghostUploadProgress: return 5
case .ghostOfflineAfterOnline: return 6
case .ghostMarkReadAfterAction: return 7
case .ghostUseScheduled: return 8
case .ghostSendWithoutSound: return 9
case .ghostSuggestBeforeStory: return 10
case .ghostFooter: return 11
case .historyHeader: return 12
case .historySaveDeleted: return 13
case .historySaveEdits: return 14
case .historySemiTransparent: return 15
case .historyFooter: return 16
case .stashHeader: return 17
case .stashMute: return 18
case .stashAutoRead: return 19
case .stashFooter: return 20
case .antiHeader: return 21
case .antiDisableAds: return 22
case .antiLocalPremium: return 23
case .antiDisableStories: return 24
case .antiHidePremiumStatuses: return 25
case .antiDisableLinkWarning: return 26
case .antiFooter: return 27
case .confirmHeader: return 28
case .confirmStickers: return 29
case .confirmGif: return 30
case .confirmVoice: return 31
case .chatHeader: return 32
case .chatShowSeconds: return 33
case .chatShowPeerId: return 34
case .chatTranslate: return 35
case .chatTranslateProvider: return 36
case .chatWebviewPlatform: return 37
case .chatWebviewHeight: return 38
case .chatOnlyAddedEmoji: return 39
case .appearanceHeader: return 40
case .appearanceMaterial: return 41
case .appearanceSingleCorner: return 42
case .appearanceIconPack: return 43
case .glassHeader: return 44
case .glassEnabled: return 45
case .glassVibrancy: return 46
case .glassChatList: return 47
case .glassNavBars: return 48
case .glassTabBar: return 49
case .glassBubbles: return 50
case .glassFooter: return 51
}
}
static func <(lhs: WinterGramSettingsEntry, rhs: WinterGramSettingsEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! WinterGramSettingsArguments
switch self {
case .ghostHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "GHOST MODE", sectionId: self.section)
case let .ghostEnabled(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Ghost Mode", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.ghostModeEnabled = value; return s }
})
case let .ghostReadReceipts(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Send Read Receipts", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.sendReadReceipts = value; return s }
})
case let .ghostReadStories(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Send Story Views", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.sendReadStories = value; return s }
})
case let .ghostOnlineStatus(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Send Online Status", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.sendOnlineStatus = value; return s }
})
case let .ghostUploadProgress(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Send Typing & Upload Status", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.sendUploadProgress = value; return s }
})
case let .ghostOfflineAfterOnline(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Go Offline After Online", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.sendOfflineAfterOnline = value; return s }
})
case let .ghostMarkReadAfterAction(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Mark Read After Action", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.markReadAfterAction = value; return s }
})
case let .ghostUseScheduled(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Use Scheduled Messages", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.useScheduledMessages = value; return s }
})
case let .ghostSendWithoutSound(value):
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Without Sound", label: value, sectionId: self.section, style: .blocks, action: {
arguments.presentSendWithoutSound()
})
case let .ghostSuggestBeforeStory(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Ask Before Viewing Stories", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.suggestGhostBeforeStory = value; return s }
})
case .ghostFooter:
return ItemListTextItem(presentationData: presentationData, text: .plain("When Ghost Mode is on, WinterGram stops sending read receipts, online status and typing activity."), sectionId: self.section)
case .historyHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "HISTORY", sectionId: self.section)
case let .historySaveDeleted(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Save Deleted Messages", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.saveDeletedMessages = value; return s }
})
case let .historySaveEdits(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Save Edit History", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.saveMessagesHistory = value; return s }
})
case let .historySemiTransparent(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Dim Deleted Messages", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.semiTransparentDeletedMessages = value; return s }
})
case .historyFooter:
return ItemListTextItem(presentationData: presentationData, text: .plain("Deleted and edited messages are kept locally on this device only."), sectionId: self.section)
case .stashHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "HIDDEN ARCHIVE", sectionId: self.section)
case let .stashMute(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Mute Notifications", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.stashMuteNotifications = value; return s }
})
case let .stashAutoRead(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Auto Mark as Read", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.stashAutoMarkRead = value; return s }
})
case .stashFooter:
return ItemListTextItem(presentationData: presentationData, text: .plain("Stashed chats are hidden from the main list and accessible only here."), sectionId: self.section)
case .antiHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "FEATURES", sectionId: self.section)
case let .antiDisableAds(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Disable Ads", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.disableAds = value; return s }
})
case let .antiLocalPremium(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Local Premium", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.localPremium = value; return s }
})
case let .antiDisableStories(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Hide Stories", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.disableStories = value; return s }
})
case let .antiHidePremiumStatuses(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Hide Premium Statuses", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.hidePremiumStatuses = value; return s }
})
case let .antiDisableLinkWarning(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Disable Open Link Warning", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.disableOpenLinkWarning = value; return s }
})
case .antiFooter:
return ItemListTextItem(presentationData: presentationData, text: .plain("Local Premium unlocks Premium-only UI on this device; it does not grant server-side Premium."), sectionId: self.section)
case .confirmHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "SEND CONFIRMATIONS", sectionId: self.section)
case let .confirmStickers(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Confirm Stickers", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.stickerConfirmation = value; return s }
})
case let .confirmGif(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Confirm GIFs", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.gifConfirmation = value; return s }
})
case let .confirmVoice(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Confirm Voice Messages", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.voiceConfirmation = value; return s }
})
case .chatHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "CHAT", sectionId: self.section)
case let .chatShowSeconds(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Show Message Seconds", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.showMessageSeconds = value; return s }
})
case let .chatShowPeerId(value):
return ItemListDisclosureItem(presentationData: presentationData, title: "Show Peer ID", label: value, sectionId: self.section, style: .blocks, action: {
arguments.presentPeerId()
})
case let .chatTranslate(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Message Translation", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.translateMessages = value; return s }
})
case let .chatTranslateProvider(value):
return ItemListDisclosureItem(presentationData: presentationData, title: "Translation Provider", label: value, sectionId: self.section, style: .blocks, action: {
arguments.presentTranslationProvider()
})
case let .chatWebviewPlatform(value):
return ItemListDisclosureItem(presentationData: presentationData, title: "WebView Platform", label: value, sectionId: self.section, style: .blocks, action: {
arguments.presentWebviewPlatform()
})
case let .chatWebviewHeight(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Increase WebView Height", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.increaseWebviewHeight = value; return s }
})
case let .chatOnlyAddedEmoji(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Only Added Emoji & Stickers", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.showOnlyAddedEmojisAndStickers = value; return s }
})
case .appearanceHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "APPEARANCE", sectionId: self.section)
case let .appearanceMaterial(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Material Design", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.materialDesign = value; return s }
})
case let .appearanceSingleCorner(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Single Corner Radius", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.singleCornerRadius = value; return s }
})
case let .appearanceIconPack(value):
return ItemListDisclosureItem(presentationData: presentationData, title: "Icon Pack", label: value, sectionId: self.section, style: .blocks, action: {
arguments.presentIconPack()
})
case .glassHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: "LIQUID GLASS", sectionId: self.section)
case let .glassEnabled(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Liquid Glass", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.liquidGlass.enabled = value; return s }
})
case let .glassVibrancy(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Vibrancy", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.liquidGlass.vibrancy = value; return s }
})
case let .glassChatList(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Apply to Chat List", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.liquidGlass.applyToChatList = value; return s }
})
case let .glassNavBars(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Apply to Navigation Bars", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.liquidGlass.applyToNavigationBars = value; return s }
})
case let .glassTabBar(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Apply to Tab Bar", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.liquidGlass.applyToTabBar = value; return s }
})
case let .glassBubbles(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Apply to Bubbles", value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateSettings { var s = $0; s.liquidGlass.applyToBubbles = value; return s }
})
case .glassFooter:
return ItemListTextItem(presentationData: presentationData, text: .plain("Transparency, blur and tint can be fine-tuned per surface. Turn Liquid Glass off for the standard opaque look."), sectionId: self.section)
}
}
}
private func sendWithoutSoundLabel(_ value: WinterGramSendWithoutSound) -> String {
switch value {
case .never: return "Never"
case .inGhostMode: return "In Ghost Mode"
case .always: return "Always"
}
}
private func peerIdLabel(_ value: WinterGramPeerIdDisplay) -> String {
switch value {
case .hidden: return "Hidden"
case .telegramApi: return "Telegram API"
case .botApi: return "Bot API"
}
}
private func translationProviderLabel(_ value: WinterGramTranslationProvider) -> String {
switch value {
case .telegram: return "Telegram"
case .google: return "Google"
case .yandex: return "Yandex"
case .system: return "System"
}
}
private func webviewPlatformLabel(_ value: WinterGramWebviewPlatform) -> String {
switch value {
case .auto: return "Automatic"
case .ios: return "iOS"
case .android: return "Android"
case .macos: return "macOS"
case .desktop: return "Desktop"
}
}
private func iconPackLabel(_ value: WinterGramIconPack) -> String {
switch value {
case .wintergram: return "WinterGram"
case .ayugram: return "AyuGram"
case .exteragram: return "exteraGram"
case .telegram: return "Telegram"
}
}
private func winterGramSettingsEntries(settings: WinterGramSettings) -> [WinterGramSettingsEntry] {
var entries: [WinterGramSettingsEntry] = []
entries.append(.ghostHeader)
entries.append(.ghostEnabled(settings.ghostModeEnabled))
entries.append(.ghostReadReceipts(settings.sendReadReceipts))
entries.append(.ghostReadStories(settings.sendReadStories))
entries.append(.ghostOnlineStatus(settings.sendOnlineStatus))
entries.append(.ghostUploadProgress(settings.sendUploadProgress))
entries.append(.ghostOfflineAfterOnline(settings.sendOfflineAfterOnline))
entries.append(.ghostMarkReadAfterAction(settings.markReadAfterAction))
entries.append(.ghostUseScheduled(settings.useScheduledMessages))
entries.append(.ghostSendWithoutSound(sendWithoutSoundLabel(settings.sendWithoutSound)))
entries.append(.ghostSuggestBeforeStory(settings.suggestGhostBeforeStory))
entries.append(.ghostFooter)
entries.append(.historyHeader)
entries.append(.historySaveDeleted(settings.saveDeletedMessages))
entries.append(.historySaveEdits(settings.saveMessagesHistory))
entries.append(.historySemiTransparent(settings.semiTransparentDeletedMessages))
entries.append(.historyFooter)
entries.append(.stashHeader)
entries.append(.stashMute(settings.stashMuteNotifications))
entries.append(.stashAutoRead(settings.stashAutoMarkRead))
entries.append(.stashFooter)
entries.append(.antiHeader)
entries.append(.antiDisableAds(settings.disableAds))
entries.append(.antiLocalPremium(settings.localPremium))
entries.append(.antiDisableStories(settings.disableStories))
entries.append(.antiHidePremiumStatuses(settings.hidePremiumStatuses))
entries.append(.antiDisableLinkWarning(settings.disableOpenLinkWarning))
entries.append(.antiFooter)
entries.append(.confirmHeader)
entries.append(.confirmStickers(settings.stickerConfirmation))
entries.append(.confirmGif(settings.gifConfirmation))
entries.append(.confirmVoice(settings.voiceConfirmation))
entries.append(.chatHeader)
entries.append(.chatShowSeconds(settings.showMessageSeconds))
entries.append(.chatShowPeerId(peerIdLabel(settings.showPeerId)))
entries.append(.chatTranslate(settings.translateMessages))
entries.append(.chatTranslateProvider(translationProviderLabel(settings.translationProvider)))
entries.append(.chatWebviewPlatform(webviewPlatformLabel(settings.webviewSpoofPlatform)))
entries.append(.chatWebviewHeight(settings.increaseWebviewHeight))
entries.append(.chatOnlyAddedEmoji(settings.showOnlyAddedEmojisAndStickers))
entries.append(.appearanceHeader)
entries.append(.appearanceMaterial(settings.materialDesign))
entries.append(.appearanceSingleCorner(settings.singleCornerRadius))
entries.append(.appearanceIconPack(iconPackLabel(settings.iconPack)))
entries.append(.glassHeader)
entries.append(.glassEnabled(settings.liquidGlass.enabled))
entries.append(.glassVibrancy(settings.liquidGlass.vibrancy))
entries.append(.glassChatList(settings.liquidGlass.applyToChatList))
entries.append(.glassNavBars(settings.liquidGlass.applyToNavigationBars))
entries.append(.glassTabBar(settings.liquidGlass.applyToTabBar))
entries.append(.glassBubbles(settings.liquidGlass.applyToBubbles))
entries.append(.glassFooter)
return entries
}
public func winterGramSettingsController(context: AccountContext) -> ViewController {
let accountManager = context.sharedContext.accountManager
var presentControllerImpl: ((ViewController, Any?) -> Void)?
let updateSettings: (@escaping (WinterGramSettings) -> WinterGramSettings) -> Void = { f in
let _ = updateWinterGramSettingsInteractively(accountManager: accountManager, f).start()
}
func presentChoice<T>(title: String, options: [(String, T)], apply: @escaping (T, WinterGramSettings) -> WinterGramSettings) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = ActionSheetController(presentationData: presentationData)
var items: [ActionSheetItem] = []
items.append(ActionSheetTextItem(title: title))
for (label, value) in options {
items.append(ActionSheetButtonItem(title: label, action: { [weak controller] in
controller?.dismissAnimated()
updateSettings { apply(value, $0) }
}))
}
controller.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak controller] in
controller?.dismissAnimated()
})
])
])
presentControllerImpl?(controller, nil)
}
let arguments = WinterGramSettingsArguments(
updateSettings: updateSettings,
presentSendWithoutSound: {
presentChoice(title: "Send Without Sound", options: [
("Never", WinterGramSendWithoutSound.never),
("In Ghost Mode", WinterGramSendWithoutSound.inGhostMode),
("Always", WinterGramSendWithoutSound.always)
], apply: { value, settings in
var settings = settings
settings.sendWithoutSound = value
return settings
})
},
presentPeerId: {
presentChoice(title: "Show Peer ID", options: [
("Hidden", WinterGramPeerIdDisplay.hidden),
("Telegram API", WinterGramPeerIdDisplay.telegramApi),
("Bot API", WinterGramPeerIdDisplay.botApi)
], apply: { value, settings in
var settings = settings
settings.showPeerId = value
return settings
})
},
presentTranslationProvider: {
presentChoice(title: "Translation Provider", options: [
("Telegram", WinterGramTranslationProvider.telegram),
("Google", WinterGramTranslationProvider.google),
("Yandex", WinterGramTranslationProvider.yandex),
("System", WinterGramTranslationProvider.system)
], apply: { value, settings in
var settings = settings
settings.translationProvider = value
return settings
})
},
presentWebviewPlatform: {
presentChoice(title: "WebView Platform", options: [
("Automatic", WinterGramWebviewPlatform.auto),
("iOS", WinterGramWebviewPlatform.ios),
("Android", WinterGramWebviewPlatform.android),
("macOS", WinterGramWebviewPlatform.macos),
("Desktop", WinterGramWebviewPlatform.desktop)
], apply: { value, settings in
var settings = settings
settings.webviewSpoofPlatform = value
return settings
})
},
presentIconPack: {
presentChoice(title: "Icon Pack", options: [
("WinterGram", WinterGramIconPack.wintergram),
("AyuGram", WinterGramIconPack.ayugram),
("exteraGram", WinterGramIconPack.exteragram),
("Telegram", WinterGramIconPack.telegram)
], apply: { value, settings in
var settings = settings
settings.iconPack = value
return settings
})
}
)
let signal = combineLatest(queue: .mainQueue(),
context.sharedContext.presentationData,
winterGramSettings(accountManager: accountManager)
)
|> deliverOnMainQueue
|> map { presentationData, settings -> (ItemListControllerState, (ItemListNodeState, Any)) in
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("WinterGram"), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: winterGramSettingsEntries(settings: settings), style: .blocks, animateChanges: true)
return (controllerState, (listState, arguments))
}
let controller = ItemListController(context: context, state: signal)
presentControllerImpl = { [weak controller] c, a in
controller?.present(c, in: .window(.root), with: a)
}
return controller
}

View file

@ -279,8 +279,10 @@ public func performAppGroupUpgrades(appGroupPath: String, rootPath: String) {
}
do {
// WinterGram: include account data in the device's iCloud/iTunes backup so accounts
// survive a restore. Tradeoff: session auth keys are then part of the iCloud backup.
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
resourceValues.isExcludedFromBackup = false
var mutableUrl = URL(fileURLWithPath: rootPath)
try mutableUrl.setResourceValues(resourceValues)
} catch let e {

View file

@ -131,7 +131,8 @@ public struct PresentationResourcesSettings {
public static let powerSaving = renderSettingsIcon(name: "Item List/Icons/PowerSaving", backgroundColors: [colorOrange])
public static let business = renderSettingsIcon(name: "Item List/Icons/Business", backgroundColors: [UIColor(rgb: 0xA95CE3), UIColor(rgb: 0xF16B80)])
public static let myProfile = renderSettingsIcon(name: "Item List/Icons/Profile", backgroundColors: [colorRed])
public static let winterGram = renderSettingsIcon(name: "Item List/Icons/WinterGram", backgroundColors: [colorLightBlue])
public static let birthday = renderSettingsIcon(name: "Item List/Icons/Cake", backgroundColors: [colorBlue])
public static let aiTools = renderSettingsIcon(name: "Item List/Icons/AITools", backgroundColors: [colorPurple])
public static let yourColor = renderSettingsIcon(name: "Item List/Icons/Brush", backgroundColors: [colorLightBlue])

View file

@ -18,6 +18,7 @@ swift_library(
"//submodules/OverlayStatusController",
"//submodules/UrlWhitelist",
"//submodules/TelegramPresentationData",
"//submodules/TelegramUIPreferences",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent",
"//submodules/TelegramUI/Components/AlertComponent/AlertWebpagePreviewComponent",

View file

@ -8,6 +8,7 @@ import AccountContext
import OverlayStatusController
import UrlWhitelist
import TelegramPresentationData
import TelegramUIPreferences
import AlertComponent
import AlertCheckComponent
import AlertWebpagePreviewComponent
@ -111,7 +112,7 @@ public func openUserGeneratedUrl(
}
}
if concealed && !skipConcealedAlert {
if concealed && !skipConcealedAlert && !currentWinterGramSettings.disableOpenLinkWarning {
var rawDisplayUrl: String = parsedString
let maxLength = 180
if rawDisplayUrl.count > maxLength {

View file

@ -188,6 +188,7 @@ enum PeerInfoSettingsSection {
case premiumManagement
case stars
case ton
case winterGram
}
enum PeerInfoReportType {

View file

@ -217,15 +217,15 @@ extension PeerInfoScreenNode {
guard let strongSelf = self else {
return
}
var maximumAvailableAccounts: Int = 3
var maximumAvailableAccounts: Int = maximumNumberOfAccounts
if accountAndPeer?.1.isPremium == true && !strongSelf.context.account.testingEnvironment {
maximumAvailableAccounts = 4
maximumAvailableAccounts = maximumPremiumNumberOfAccounts
}
var count: Int = 1
for (accountContext, peer, _) in accountsAndPeers {
if !accountContext.account.testingEnvironment {
if peer.isPremium {
maximumAvailableAccounts = 4
maximumAvailableAccounts = maximumPremiumNumberOfAccounts
}
count += 1
}
@ -271,6 +271,8 @@ extension PeerInfoScreenNode {
self.interaction.editingOpenNameColorSetup()
case .powerSaving:
push(energySavingSettingsScreen(context: self.context))
case .winterGram:
push(winterGramSettingsController(context: self.context))
case .businessSetup:
guard let controller = self.controller, !controller.presentAccountFrozenInfoIfNeeded() else {
return

View file

@ -14,6 +14,7 @@ import TelegramStringFormatting
import PeerNameColorItem
enum SettingsSection: Int, CaseIterable {
case winterGram
case edit
case phone
case accounts
@ -36,7 +37,11 @@ func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentat
for section in SettingsSection.allCases {
items[section] = []
}
items[.winterGram]!.append(PeerInfoScreenDisclosureItem(id: 0, text: "WinterGram", icon: PresentationResourcesSettings.winterGram, action: {
interaction.openSettings(.winterGram)
}))
let setPhotoTitle: String
if let peer = data.peer, !peer.profileImageRepresentations.isEmpty {
setPhotoTitle = presentationData.strings.Settings_ChangeProfilePhoto
@ -239,7 +244,7 @@ func settingsItems(data: PeerInfoScreenData?, context: AccountContext, presentat
items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 3, text: presentationData.strings.Settings_Appearance, icon: PresentationResourcesSettings.appearance, action: {
interaction.openSettings(.appearance)
}))
items[.advanced]!.append(PeerInfoScreenDisclosureItem(id: 6, label: .text(data.isPowerSavingEnabled == true ? presentationData.strings.Settings_PowerSavingOn : presentationData.strings.Settings_PowerSavingOff), text: presentationData.strings.Settings_PowerSaving, icon: PresentationResourcesSettings.powerSaving, action: {
interaction.openSettings(.powerSaving)
}))

View file

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

View file

@ -56,6 +56,17 @@ private let handleVoipNotifications = false
private var testIsLaunched = false
func normalizeWinterGramUrlScheme(_ url: URL) -> URL {
guard let scheme = url.scheme, scheme.lowercased() == "wnt" else {
return url
}
guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
return url
}
components.scheme = "tg"
return components.url ?? url
}
private func isKeyboardWindow(window: NSObject) -> Bool {
let typeName = NSStringFromClass(type(of: window))
if #available(iOS 9.0, *) {
@ -248,7 +259,8 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
private let openNotificationSettingsWhenReadyDisposable = MetaDisposable()
private let openChatWhenReadyDisposable = MetaDisposable()
private let openUrlWhenReadyDisposable = MetaDisposable()
private let winterGramSettingsDisposable = MetaDisposable()
private let badgeDisposable = MetaDisposable()
private let quickActionsDisposable = MetaDisposable()
@ -1191,6 +1203,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
})
let sharedApplicationContext = SharedApplicationContext(sharedContext: sharedContext, notificationManager: notificationManager, wakeupManager: wakeupManager)
sharedApplicationContext.sharedContext.mediaManager.overlayMediaManager.attachOverlayMediaController(sharedApplicationContext.overlayMediaController)
self.winterGramSettingsDisposable.set(observeWinterGramSettings(accountManager: accountManager))
return accountManager.transaction { transaction -> (SharedApplicationContext, LoggingSettings) in
return (sharedApplicationContext, transaction.getSharedData(SharedDataKeys.loggingSettings)?.get(LoggingSettings.self) ?? LoggingSettings.defaultSettings)
@ -1513,10 +1526,10 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
})
if let url = launchOptions?[.url] {
if let url = url as? URL, url.scheme == "tg" || url.scheme == buildConfig.appSpecificUrlScheme {
self.openUrlWhenReady(url: url, external: true)
} else if let urlString = url as? String, urlString.lowercased().hasPrefix("tg:") || urlString.lowercased().hasPrefix("\(buildConfig.appSpecificUrlScheme):"), let url = URL(string: urlString) {
self.openUrlWhenReady(url: url, external: true)
if let url = url as? URL, url.scheme == "tg" || url.scheme == "wnt" || url.scheme == buildConfig.appSpecificUrlScheme {
self.openUrlWhenReady(url: normalizeWinterGramUrlScheme(url), external: true)
} else if let urlString = url as? String, urlString.lowercased().hasPrefix("tg:") || urlString.lowercased().hasPrefix("wnt:") || urlString.lowercased().hasPrefix("\(buildConfig.appSpecificUrlScheme):"), let url = URL(string: urlString) {
self.openUrlWhenReady(url: normalizeWinterGramUrlScheme(url), external: true)
}
}
@ -2500,6 +2513,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
private func openUrl(url: URL) {
let url = normalizeWinterGramUrlScheme(url)
let _ = (self.sharedContextPromise.get()
|> take(1)
|> mapToSignal { sharedApplicationContext -> Signal<(SharedAccountContextImpl, AuthorizedApplicationContext?, UnauthorizedApplicationContext?), NoError> in

View file

@ -6,6 +6,7 @@ import TemporaryCachedPeerDataManager
import Emoji
import AccountContext
import TelegramPresentationData
import TelegramUIPreferences
import ChatHistoryEntry
import ChatMessageItemCommon
import TextFormat
@ -687,7 +688,7 @@ func chatHistoryEntriesForView(
}
if view.laterId == nil && !view.isLoading {
if !entries.isEmpty, case let .MessageEntry(lastMessage, _, _, _, _, _) = entries[entries.count - 1], let message = adMessage {
if !entries.isEmpty, !currentWinterGramSettings.disableAds, case let .MessageEntry(lastMessage, _, _, _, _, _) = entries[entries.count - 1], let message = adMessage {
var nextAdMessageId: Int32 = 10000
let updatedMessage = Message(
stableId: ChatHistoryListNodeImpl.fixedAdMessageStableId,

View file

@ -51,6 +51,7 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 {
case mediaDisplaySettings = 20
case updateSettings = 21
case chatSettings = 22
case winterGramSettings = 23
}
public struct ApplicationSpecificSharedDataKeys {
@ -77,6 +78,7 @@ public struct ApplicationSpecificSharedDataKeys {
public static let mediaDisplaySettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.mediaDisplaySettings.rawValue)
public static let updateSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.updateSettings.rawValue)
public static let chatSettings: EngineDataBuffer = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.chatSettings.rawValue)
public static let winterGramSettings: EngineDataBuffer = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.winterGramSettings.rawValue)
}
private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {

View file

@ -0,0 +1,491 @@
import Foundation
import TelegramCore
import SwiftSignalKit
public enum WinterGramSendWithoutSound: Int32, Codable {
case never = 0
case inGhostMode = 1
case always = 2
}
public enum WinterGramPeerIdDisplay: Int32, Codable {
case hidden = 0
case telegramApi = 1
case botApi = 2
}
public enum WinterGramTranslationProvider: Int32, Codable {
case telegram = 0
case google = 1
case yandex = 2
case system = 3
}
public enum WinterGramWebviewPlatform: Int32, Codable {
case auto = 0
case ios = 1
case android = 2
case macos = 3
case desktop = 4
}
public enum WinterGramIconPack: Int32, Codable {
case wintergram = 0
case ayugram = 1
case exteragram = 2
case telegram = 3
}
public struct WinterGramLiquidGlass: Codable, Equatable {
public var enabled: Bool
public var transparency: Double
public var blurRadius: Double
public var tintColor: Int32?
public var vibrancy: Bool
public var applyToChatList: Bool
public var applyToBubbles: Bool
public var applyToNavigationBars: Bool
public var applyToTabBar: Bool
public static var defaultSettings: WinterGramLiquidGlass {
return WinterGramLiquidGlass(
enabled: true,
transparency: 0.75,
blurRadius: 30.0,
tintColor: nil,
vibrancy: true,
applyToChatList: true,
applyToBubbles: false,
applyToNavigationBars: true,
applyToTabBar: true
)
}
public init(
enabled: Bool,
transparency: Double,
blurRadius: Double,
tintColor: Int32?,
vibrancy: Bool,
applyToChatList: Bool,
applyToBubbles: Bool,
applyToNavigationBars: Bool,
applyToTabBar: Bool
) {
self.enabled = enabled
self.transparency = transparency
self.blurRadius = blurRadius
self.tintColor = tintColor
self.vibrancy = vibrancy
self.applyToChatList = applyToChatList
self.applyToBubbles = applyToBubbles
self.applyToNavigationBars = applyToNavigationBars
self.applyToTabBar = applyToTabBar
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
self.enabled = try container.decodeIfPresent(Bool.self, forKey: "enabled") ?? true
self.transparency = try container.decodeIfPresent(Double.self, forKey: "transparency") ?? 0.75
self.blurRadius = try container.decodeIfPresent(Double.self, forKey: "blurRadius") ?? 30.0
self.tintColor = try container.decodeIfPresent(Int32.self, forKey: "tintColor")
self.vibrancy = try container.decodeIfPresent(Bool.self, forKey: "vibrancy") ?? true
self.applyToChatList = try container.decodeIfPresent(Bool.self, forKey: "applyToChatList") ?? true
self.applyToBubbles = try container.decodeIfPresent(Bool.self, forKey: "applyToBubbles") ?? false
self.applyToNavigationBars = try container.decodeIfPresent(Bool.self, forKey: "applyToNavigationBars") ?? true
self.applyToTabBar = try container.decodeIfPresent(Bool.self, forKey: "applyToTabBar") ?? true
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.enabled, forKey: "enabled")
try container.encode(self.transparency, forKey: "transparency")
try container.encode(self.blurRadius, forKey: "blurRadius")
try container.encodeIfPresent(self.tintColor, forKey: "tintColor")
try container.encode(self.vibrancy, forKey: "vibrancy")
try container.encode(self.applyToChatList, forKey: "applyToChatList")
try container.encode(self.applyToBubbles, forKey: "applyToBubbles")
try container.encode(self.applyToNavigationBars, forKey: "applyToNavigationBars")
try container.encode(self.applyToTabBar, forKey: "applyToTabBar")
}
}
public struct WinterGramSettings: Codable, Equatable {
// Ghost mode & privacy
public var ghostModeEnabled: Bool
public var sendReadReceipts: Bool
public var sendReadStories: Bool
public var sendOnlineStatus: Bool
public var sendUploadProgress: Bool
public var sendOfflineAfterOnline: Bool
public var markReadAfterAction: Bool
public var useScheduledMessages: Bool
public var sendWithoutSound: WinterGramSendWithoutSound
public var suggestGhostBeforeStory: Bool
// Local history
public var saveDeletedMessages: Bool
public var saveMessagesHistory: Bool
public var saveForBots: Bool
public var hideFromBlocked: Bool
public var semiTransparentDeletedMessages: Bool
// Hidden archive ("ААрхив")
public var stashedPeerIds: [Int64]
public var stashMuteNotifications: Bool
public var stashAutoMarkRead: Bool
// Anti-features
public var disableAds: Bool
public var localPremium: Bool
public var shadowBanIds: [Int64]
public var disableStories: Bool
public var hidePremiumStatuses: Bool
public var disableOpenLinkWarning: Bool
// Confirmations
public var stickerConfirmation: Bool
public var gifConfirmation: Bool
public var voiceConfirmation: Bool
// Message decorations
public var showMessageSeconds: Bool
public var showPeerId: WinterGramPeerIdDisplay
public var deletedMark: String
public var editedMark: String
public var recentStickersCount: Int32
// Translation
public var translateMessages: Bool
public var translationProvider: WinterGramTranslationProvider
// Webview / platform spoofing
public var webviewSpoofPlatform: WinterGramWebviewPlatform
public var increaseWebviewHeight: Bool
// Appearance & customization
public var liquidGlass: WinterGramLiquidGlass
public var materialDesign: Bool
public var avatarCornerRadius: Int32
public var singleCornerRadius: Bool
public var messageBubbleRadius: Int32
public var removeMessageTail: Bool
public var customFont: String?
public var monoFont: String?
public var appIcon: String
public var iconPack: WinterGramIconPack
public var showOnlyAddedEmojisAndStickers: Bool
public static var defaultSettings: WinterGramSettings {
return WinterGramSettings(
ghostModeEnabled: false,
sendReadReceipts: true,
sendReadStories: true,
sendOnlineStatus: true,
sendUploadProgress: true,
sendOfflineAfterOnline: false,
markReadAfterAction: true,
useScheduledMessages: false,
sendWithoutSound: .never,
suggestGhostBeforeStory: true,
saveDeletedMessages: true,
saveMessagesHistory: true,
saveForBots: false,
hideFromBlocked: false,
semiTransparentDeletedMessages: false,
stashedPeerIds: [],
stashMuteNotifications: true,
stashAutoMarkRead: false,
disableAds: true,
localPremium: false,
shadowBanIds: [],
disableStories: false,
hidePremiumStatuses: false,
disableOpenLinkWarning: false,
stickerConfirmation: false,
gifConfirmation: false,
voiceConfirmation: false,
showMessageSeconds: false,
showPeerId: .botApi,
deletedMark: "🧹",
editedMark: "",
recentStickersCount: 100,
translateMessages: false,
translationProvider: .telegram,
webviewSpoofPlatform: .auto,
increaseWebviewHeight: false,
liquidGlass: .defaultSettings,
materialDesign: true,
avatarCornerRadius: 50,
singleCornerRadius: false,
messageBubbleRadius: 16,
removeMessageTail: false,
customFont: nil,
monoFont: nil,
appIcon: "WinterGramDark",
iconPack: .wintergram,
showOnlyAddedEmojisAndStickers: false
)
}
public init(
ghostModeEnabled: Bool,
sendReadReceipts: Bool,
sendReadStories: Bool,
sendOnlineStatus: Bool,
sendUploadProgress: Bool,
sendOfflineAfterOnline: Bool,
markReadAfterAction: Bool,
useScheduledMessages: Bool,
sendWithoutSound: WinterGramSendWithoutSound,
suggestGhostBeforeStory: Bool,
saveDeletedMessages: Bool,
saveMessagesHistory: Bool,
saveForBots: Bool,
hideFromBlocked: Bool,
semiTransparentDeletedMessages: Bool,
stashedPeerIds: [Int64],
stashMuteNotifications: Bool,
stashAutoMarkRead: Bool,
disableAds: Bool,
localPremium: Bool,
shadowBanIds: [Int64],
disableStories: Bool,
hidePremiumStatuses: Bool,
disableOpenLinkWarning: Bool,
stickerConfirmation: Bool,
gifConfirmation: Bool,
voiceConfirmation: Bool,
showMessageSeconds: Bool,
showPeerId: WinterGramPeerIdDisplay,
deletedMark: String,
editedMark: String,
recentStickersCount: Int32,
translateMessages: Bool,
translationProvider: WinterGramTranslationProvider,
webviewSpoofPlatform: WinterGramWebviewPlatform,
increaseWebviewHeight: Bool,
liquidGlass: WinterGramLiquidGlass,
materialDesign: Bool,
avatarCornerRadius: Int32,
singleCornerRadius: Bool,
messageBubbleRadius: Int32,
removeMessageTail: Bool,
customFont: String?,
monoFont: String?,
appIcon: String,
iconPack: WinterGramIconPack,
showOnlyAddedEmojisAndStickers: Bool
) {
self.ghostModeEnabled = ghostModeEnabled
self.sendReadReceipts = sendReadReceipts
self.sendReadStories = sendReadStories
self.sendOnlineStatus = sendOnlineStatus
self.sendUploadProgress = sendUploadProgress
self.sendOfflineAfterOnline = sendOfflineAfterOnline
self.markReadAfterAction = markReadAfterAction
self.useScheduledMessages = useScheduledMessages
self.sendWithoutSound = sendWithoutSound
self.suggestGhostBeforeStory = suggestGhostBeforeStory
self.saveDeletedMessages = saveDeletedMessages
self.saveMessagesHistory = saveMessagesHistory
self.saveForBots = saveForBots
self.hideFromBlocked = hideFromBlocked
self.semiTransparentDeletedMessages = semiTransparentDeletedMessages
self.stashedPeerIds = stashedPeerIds
self.stashMuteNotifications = stashMuteNotifications
self.stashAutoMarkRead = stashAutoMarkRead
self.disableAds = disableAds
self.localPremium = localPremium
self.shadowBanIds = shadowBanIds
self.disableStories = disableStories
self.hidePremiumStatuses = hidePremiumStatuses
self.disableOpenLinkWarning = disableOpenLinkWarning
self.stickerConfirmation = stickerConfirmation
self.gifConfirmation = gifConfirmation
self.voiceConfirmation = voiceConfirmation
self.showMessageSeconds = showMessageSeconds
self.showPeerId = showPeerId
self.deletedMark = deletedMark
self.editedMark = editedMark
self.recentStickersCount = recentStickersCount
self.translateMessages = translateMessages
self.translationProvider = translationProvider
self.webviewSpoofPlatform = webviewSpoofPlatform
self.increaseWebviewHeight = increaseWebviewHeight
self.liquidGlass = liquidGlass
self.materialDesign = materialDesign
self.avatarCornerRadius = avatarCornerRadius
self.singleCornerRadius = singleCornerRadius
self.messageBubbleRadius = messageBubbleRadius
self.removeMessageTail = removeMessageTail
self.customFont = customFont
self.monoFont = monoFont
self.appIcon = appIcon
self.iconPack = iconPack
self.showOnlyAddedEmojisAndStickers = showOnlyAddedEmojisAndStickers
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringCodingKey.self)
let defaults = WinterGramSettings.defaultSettings
self.ghostModeEnabled = try container.decodeIfPresent(Bool.self, forKey: "ghostModeEnabled") ?? defaults.ghostModeEnabled
self.sendReadReceipts = try container.decodeIfPresent(Bool.self, forKey: "sendReadReceipts") ?? defaults.sendReadReceipts
self.sendReadStories = try container.decodeIfPresent(Bool.self, forKey: "sendReadStories") ?? defaults.sendReadStories
self.sendOnlineStatus = try container.decodeIfPresent(Bool.self, forKey: "sendOnlineStatus") ?? defaults.sendOnlineStatus
self.sendUploadProgress = try container.decodeIfPresent(Bool.self, forKey: "sendUploadProgress") ?? defaults.sendUploadProgress
self.sendOfflineAfterOnline = try container.decodeIfPresent(Bool.self, forKey: "sendOfflineAfterOnline") ?? defaults.sendOfflineAfterOnline
self.markReadAfterAction = try container.decodeIfPresent(Bool.self, forKey: "markReadAfterAction") ?? defaults.markReadAfterAction
self.useScheduledMessages = try container.decodeIfPresent(Bool.self, forKey: "useScheduledMessages") ?? defaults.useScheduledMessages
self.sendWithoutSound = try container.decodeIfPresent(WinterGramSendWithoutSound.self, forKey: "sendWithoutSound") ?? defaults.sendWithoutSound
self.suggestGhostBeforeStory = try container.decodeIfPresent(Bool.self, forKey: "suggestGhostBeforeStory") ?? defaults.suggestGhostBeforeStory
self.saveDeletedMessages = try container.decodeIfPresent(Bool.self, forKey: "saveDeletedMessages") ?? defaults.saveDeletedMessages
self.saveMessagesHistory = try container.decodeIfPresent(Bool.self, forKey: "saveMessagesHistory") ?? defaults.saveMessagesHistory
self.saveForBots = try container.decodeIfPresent(Bool.self, forKey: "saveForBots") ?? defaults.saveForBots
self.hideFromBlocked = try container.decodeIfPresent(Bool.self, forKey: "hideFromBlocked") ?? defaults.hideFromBlocked
self.semiTransparentDeletedMessages = try container.decodeIfPresent(Bool.self, forKey: "semiTransparentDeletedMessages") ?? defaults.semiTransparentDeletedMessages
self.stashedPeerIds = try container.decodeIfPresent([Int64].self, forKey: "stashedPeerIds") ?? defaults.stashedPeerIds
self.stashMuteNotifications = try container.decodeIfPresent(Bool.self, forKey: "stashMuteNotifications") ?? defaults.stashMuteNotifications
self.stashAutoMarkRead = try container.decodeIfPresent(Bool.self, forKey: "stashAutoMarkRead") ?? defaults.stashAutoMarkRead
self.disableAds = try container.decodeIfPresent(Bool.self, forKey: "disableAds") ?? defaults.disableAds
self.localPremium = try container.decodeIfPresent(Bool.self, forKey: "localPremium") ?? defaults.localPremium
self.shadowBanIds = try container.decodeIfPresent([Int64].self, forKey: "shadowBanIds") ?? defaults.shadowBanIds
self.disableStories = try container.decodeIfPresent(Bool.self, forKey: "disableStories") ?? defaults.disableStories
self.hidePremiumStatuses = try container.decodeIfPresent(Bool.self, forKey: "hidePremiumStatuses") ?? defaults.hidePremiumStatuses
self.disableOpenLinkWarning = try container.decodeIfPresent(Bool.self, forKey: "disableOpenLinkWarning") ?? defaults.disableOpenLinkWarning
self.stickerConfirmation = try container.decodeIfPresent(Bool.self, forKey: "stickerConfirmation") ?? defaults.stickerConfirmation
self.gifConfirmation = try container.decodeIfPresent(Bool.self, forKey: "gifConfirmation") ?? defaults.gifConfirmation
self.voiceConfirmation = try container.decodeIfPresent(Bool.self, forKey: "voiceConfirmation") ?? defaults.voiceConfirmation
self.showMessageSeconds = try container.decodeIfPresent(Bool.self, forKey: "showMessageSeconds") ?? defaults.showMessageSeconds
self.showPeerId = try container.decodeIfPresent(WinterGramPeerIdDisplay.self, forKey: "showPeerId") ?? defaults.showPeerId
self.deletedMark = try container.decodeIfPresent(String.self, forKey: "deletedMark") ?? defaults.deletedMark
self.editedMark = try container.decodeIfPresent(String.self, forKey: "editedMark") ?? defaults.editedMark
self.recentStickersCount = try container.decodeIfPresent(Int32.self, forKey: "recentStickersCount") ?? defaults.recentStickersCount
self.translateMessages = try container.decodeIfPresent(Bool.self, forKey: "translateMessages") ?? defaults.translateMessages
self.translationProvider = try container.decodeIfPresent(WinterGramTranslationProvider.self, forKey: "translationProvider") ?? defaults.translationProvider
self.webviewSpoofPlatform = try container.decodeIfPresent(WinterGramWebviewPlatform.self, forKey: "webviewSpoofPlatform") ?? defaults.webviewSpoofPlatform
self.increaseWebviewHeight = try container.decodeIfPresent(Bool.self, forKey: "increaseWebviewHeight") ?? defaults.increaseWebviewHeight
self.liquidGlass = try container.decodeIfPresent(WinterGramLiquidGlass.self, forKey: "liquidGlass") ?? defaults.liquidGlass
self.materialDesign = try container.decodeIfPresent(Bool.self, forKey: "materialDesign") ?? defaults.materialDesign
self.avatarCornerRadius = try container.decodeIfPresent(Int32.self, forKey: "avatarCornerRadius") ?? defaults.avatarCornerRadius
self.singleCornerRadius = try container.decodeIfPresent(Bool.self, forKey: "singleCornerRadius") ?? defaults.singleCornerRadius
self.messageBubbleRadius = try container.decodeIfPresent(Int32.self, forKey: "messageBubbleRadius") ?? defaults.messageBubbleRadius
self.removeMessageTail = try container.decodeIfPresent(Bool.self, forKey: "removeMessageTail") ?? defaults.removeMessageTail
self.customFont = try container.decodeIfPresent(String.self, forKey: "customFont")
self.monoFont = try container.decodeIfPresent(String.self, forKey: "monoFont")
self.appIcon = try container.decodeIfPresent(String.self, forKey: "appIcon") ?? defaults.appIcon
self.iconPack = try container.decodeIfPresent(WinterGramIconPack.self, forKey: "iconPack") ?? defaults.iconPack
self.showOnlyAddedEmojisAndStickers = try container.decodeIfPresent(Bool.self, forKey: "showOnlyAddedEmojisAndStickers") ?? defaults.showOnlyAddedEmojisAndStickers
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringCodingKey.self)
try container.encode(self.ghostModeEnabled, forKey: "ghostModeEnabled")
try container.encode(self.sendReadReceipts, forKey: "sendReadReceipts")
try container.encode(self.sendReadStories, forKey: "sendReadStories")
try container.encode(self.sendOnlineStatus, forKey: "sendOnlineStatus")
try container.encode(self.sendUploadProgress, forKey: "sendUploadProgress")
try container.encode(self.sendOfflineAfterOnline, forKey: "sendOfflineAfterOnline")
try container.encode(self.markReadAfterAction, forKey: "markReadAfterAction")
try container.encode(self.useScheduledMessages, forKey: "useScheduledMessages")
try container.encode(self.sendWithoutSound, forKey: "sendWithoutSound")
try container.encode(self.suggestGhostBeforeStory, forKey: "suggestGhostBeforeStory")
try container.encode(self.saveDeletedMessages, forKey: "saveDeletedMessages")
try container.encode(self.saveMessagesHistory, forKey: "saveMessagesHistory")
try container.encode(self.saveForBots, forKey: "saveForBots")
try container.encode(self.hideFromBlocked, forKey: "hideFromBlocked")
try container.encode(self.semiTransparentDeletedMessages, forKey: "semiTransparentDeletedMessages")
try container.encode(self.stashedPeerIds, forKey: "stashedPeerIds")
try container.encode(self.stashMuteNotifications, forKey: "stashMuteNotifications")
try container.encode(self.stashAutoMarkRead, forKey: "stashAutoMarkRead")
try container.encode(self.disableAds, forKey: "disableAds")
try container.encode(self.localPremium, forKey: "localPremium")
try container.encode(self.shadowBanIds, forKey: "shadowBanIds")
try container.encode(self.disableStories, forKey: "disableStories")
try container.encode(self.hidePremiumStatuses, forKey: "hidePremiumStatuses")
try container.encode(self.disableOpenLinkWarning, forKey: "disableOpenLinkWarning")
try container.encode(self.stickerConfirmation, forKey: "stickerConfirmation")
try container.encode(self.gifConfirmation, forKey: "gifConfirmation")
try container.encode(self.voiceConfirmation, forKey: "voiceConfirmation")
try container.encode(self.showMessageSeconds, forKey: "showMessageSeconds")
try container.encode(self.showPeerId, forKey: "showPeerId")
try container.encode(self.deletedMark, forKey: "deletedMark")
try container.encode(self.editedMark, forKey: "editedMark")
try container.encode(self.recentStickersCount, forKey: "recentStickersCount")
try container.encode(self.translateMessages, forKey: "translateMessages")
try container.encode(self.translationProvider, forKey: "translationProvider")
try container.encode(self.webviewSpoofPlatform, forKey: "webviewSpoofPlatform")
try container.encode(self.increaseWebviewHeight, forKey: "increaseWebviewHeight")
try container.encode(self.liquidGlass, forKey: "liquidGlass")
try container.encode(self.materialDesign, forKey: "materialDesign")
try container.encode(self.avatarCornerRadius, forKey: "avatarCornerRadius")
try container.encode(self.singleCornerRadius, forKey: "singleCornerRadius")
try container.encode(self.messageBubbleRadius, forKey: "messageBubbleRadius")
try container.encode(self.removeMessageTail, forKey: "removeMessageTail")
try container.encodeIfPresent(self.customFont, forKey: "customFont")
try container.encodeIfPresent(self.monoFont, forKey: "monoFont")
try container.encode(self.appIcon, forKey: "appIcon")
try container.encode(self.iconPack, forKey: "iconPack")
try container.encode(self.showOnlyAddedEmojisAndStickers, forKey: "showOnlyAddedEmojisAndStickers")
}
public func isShadowBanned(_ peerId: Int64) -> Bool {
return self.shadowBanIds.contains(peerId)
}
public func isStashed(_ peerId: Int64) -> Bool {
return self.stashedPeerIds.contains(peerId)
}
public var shouldSendWithoutSound: Bool {
switch self.sendWithoutSound {
case .never:
return false
case .inGhostMode:
return self.ghostModeEnabled
case .always:
return true
}
}
}
public func updateWinterGramSettingsInteractively(accountManager: AccountManager<TelegramAccountManagerTypes>, _ f: @escaping (WinterGramSettings) -> WinterGramSettings) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.winterGramSettings, { entry in
let currentSettings: WinterGramSettings
if let entry = entry?.get(WinterGramSettings.self) {
currentSettings = entry
} else {
currentSettings = .defaultSettings
}
return SharedPreferencesEntry(f(currentSettings))
})
}
}
public func winterGramSettings(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<WinterGramSettings, NoError> {
return accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.winterGramSettings])
|> map { sharedData -> WinterGramSettings in
return sharedData.entries[ApplicationSpecificSharedDataKeys.winterGramSettings]?.get(WinterGramSettings.self) ?? .defaultSettings
}
}
private let currentWinterGramSettingsValue = Atomic<WinterGramSettings>(value: .defaultSettings)
// Synchronous snapshot of the latest settings, for hook points that cannot subscribe to a Signal.
// Kept up to date by observeWinterGramSettings(accountManager:) at app startup.
public var currentWinterGramSettings: WinterGramSettings {
return currentWinterGramSettingsValue.with { $0 }
}
public func setCurrentWinterGramSettings(_ settings: WinterGramSettings) {
let _ = currentWinterGramSettingsValue.swap(settings)
}
public func observeWinterGramSettings(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Disposable {
return (winterGramSettings(accountManager: accountManager)
|> deliverOnMainQueue).start(next: { settings in
setCurrentWinterGramSettings(settings)
})
}