chore: prerelease 0.1.0
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 771 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -212,6 +212,7 @@
|
|||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>tg</string>
|
||||
<string>wnt</string>
|
||||
<string>$(APP_SPECIFIC_URL_SCHEME)</string>
|
||||
</array>
|
||||
</dict>
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>tg</string>
|
||||
<string>wnt</string>
|
||||
<string>$(APP_SPECIFIC_URL_SCHEME)</string>
|
||||
</array>
|
||||
</dict>
|
||||
|
|
|
|||
BIN
branding/wintergram-icon.png
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
54931
branding/wintergram-icon.svg
Normal file
|
After Width: | Height: | Size: 12 MiB |
96
docs/wintergram-features.md
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
607
submodules/SettingsUI/Sources/WinterGramSettingsController.swift
Normal 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
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ enum PeerInfoSettingsSection {
|
|||
case premiumManagement
|
||||
case stars
|
||||
case ton
|
||||
case winterGram
|
||||
}
|
||||
|
||||
enum PeerInfoReportType {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}))
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Item List/Icons/WinterGram.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "wintergram_snowball_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Item List/Icons/WinterGram.imageset/wintergram_snowball_30.pdf
vendored
Normal 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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||