[JavaScript, Разработка мобильных приложений, Swift, ReactJS] Как Лёня с React на Swift переезжал
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет! Меня зовут Лёня, я фронтенд-разработчик в компании KOTELOV и выпускник курса «Мидл фронтенд-разработчик» в Яндекс.Практикуме. Общаясь с коллегами, которые занимаются мобильной разработкой, я понял, что у них всё гораздо сложнее, чем в вебе. Я взял свой телефон в руки, посмотрел на известные приложения типа телеги или Яндекс.Музыки с точки зрения человека, который их пишет, и подумал: «Вау, как круто! Я тоже хочу так уметь!»
Тогда я принял твёрдое решение изучить iOS-разработку и написать собственное приложение. На выбор было два языка: Objective-C и Swift. К динозаврам моё отношение подозрительное, поэтому я принял решение набивать шишки обо что-то помягче и посвежее.
Этим постом я открываю цикл про то, как я переезжал с фронта на мобилку и с чем столкнулся по пути. Если вы тоже посматриваете в сторону мобилок, вам может пригодиться мой опыт.
оригинал
Готовимся к переходу — изучаем SwiftUI
Два года назад Apple выпустила SwiftUI — новый фреймворк для своего языка. До этого разработчики писали на UI Kit, но это дело прошлого. Думаю, что на нём до сих пор пишут лишь потому, что сложно в один день переписать весь легаси-код на новый лад. Для себя я уже давно вынес правило: никогда не изучай старое, если есть новое. Но на всякий случай я изучил различия между старым UI Kit Storyboard и SwiftUI, а также мнения людей, которые в теме.
Беглый сравнительный анализ показал, что SwiftUI отличается от UI Kit в лучшую сторону: у Swift более удобный декларативный стиль, есть двусторонний биндинг (aka «состояния в React»), нормальный встроенный роутинг и ещё куча крутых штук, которые упрощают построение архитектуры приложения. В общем, выбор очевиден.
Кстати, про архитектуру. Я React-разработчик, у нас компонентный подход. Так что сначала я разузнал про все паттерны проектирования — MVC, MVVM, MVP, VIPER. Выяснилось, что наиболее применимая для React архитектура — MVC в стеке React+Redux. В таком стеке хранилище будет выступать в роли модели, редьюсер — в роли контроллера, а компоненты — вьюх.
Рисунок 1. Использование React + Redux в качестве MVC-приложения
Чтобы изучить Swift, как любой нормальный разработчик, сначала я полез в документацию языка. Хоть она крутая и подробная, в ней нет инструкции вроде «Как за пять минут сделать какую-нибудь ерунду», как на начальной странице React, когда нужно просто изучить синтаксис. Поэтому мне пришлось смотреть длиннющий туториал по основам Swift (предупреждаю, он нудный). Не пожалел, потому что заодно и про типизацию в языке узнал.
В отличие от JS, Swift — строго типизированный язык. Подозреваю, что если бы я не знал до этого TypeScript, погряз бы с ходу в этом аду из типов. Во-первых, там есть целая куча разных типов чисел. Во-вторых, нет классических объектов: вместо них есть некие тюплы. В-третьих, сразу несколько видов разных списков. Поэтому, чтобы не запутаться в типизации Swift, советую сперва хорошенько разобраться с TypeScript.
Разобравшись с языком, я порешал пару алгоритмических задачек на нём с целью проверить, всё ли я правильно понял, написал пару простеньких игрушек и решил взяться за что-то покрупнее.
Создаём проекты
Моя конечная цель — написать два полноценно работающих мессенджера на SwiftUI и на React. Cпасибо Яндекс.Практикуму за то, что я теперь не парюсь над идеями приложений: во время учёбы нам предложили написать чатик на чистом JS, и я подумал, что это отличный тренажёр для любого языка.
Часть с написанием бэкенда я вынесу за скобки.
Начнём с вывода чего-нибудь на экран.
«Hello World» на React
Что представляет из себя «Hello World» на React? Это html-файл с div#root, index.js и App.js. Используем Create React App и создадим новый проект.
Рисунок 2. Создание нового проекта с использованием Create React App
Да, это на самом деле просто. Перейдём в созданную папку и посмотрим, что эти команды успели насоздавать.
Рисунок 3. Базовая структура папок
А успели они много интересного. Ещё занятнее то, что кроется под капотом. Но про это и без меня уже написали кучу статей, например «Под капотом у React. Пишем свою реализацию с нуля».
А вот содержимое тех файлов, что нам интересны:
./public/index.html
<!doctype html>
<html lang='ru'>
<head>
<meta charset='UTF-8'>
<meta http-equiv='X-UA-Compatible' content='ie=edge'>
<title>my-app</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
/src/index.js
import * as React from 'react';
import ReactDOM from 'react-dom';
import App from ‘@routes/App’;
ReactDOM.render(
<App />,
document.getElementById('root'),
);
./src/App.jsx
import * as React from 'react';
export default function App() {
return (
<div>
Hello world!
</div>
)
};
Такой набор файлов с использованием Create React App позволяет вывести на экран классическую фразу «Hello World». В этом же консольном интерфейсе мы можем подключить шаблоны по типу использования — Typescript или Redux, — чтобы не создавать их вручную.
Кроме всех мелочей, нужно ещё подключить сервер для раздачи артефактов — NGINX или node.js, чтобы проект заработал в интернете. А ещё полезно знать Docker и понимать, куда и как выложить свой проект.
«Hello World» на Swift
Теперь мы хотим увидеть такой же результат на экране своего смартфона. Для этого нужно скачать IDE от Apple — XCode. 14 гигов, конечно, — это серьёзно, но надо так надо.
Рисунок 4. Стартовый экран Xcode
Рисунок 5. Выбор типа создаваемого проекта
Как оказалось, там тоже ничего сложного. Интерфейс очень наглядный: мне ни разу не пришлось запускать консоль — всё, что нужно, можно с лёгкостью натыкать мышкой.
Мы будем создавать проект для iOS. Важно отметить, что это сразу включает в себя и iPhone, и iPad. Это приложение можно тонко сконфигурировать под свои нужды:
Рисунок 6. Конфигурация создаваемого проекта
Прекрасно! Операции длились несколько секунд, а на выходе у нас получилось примерно столько же файлов, сколько и при CRA. Но в отличие от CRA, Swift всегда создаёт пару дополнительных папок в новом проекте. Они нужны для тестов. А CRA располагает такие файлы рядом с созданными компонентами. Такова особенность архитектуры приложений.
Рисунок 7. Структура папок при создании проекта
Минуточку, а это что? Код в рантайме перекомпилируется и показывается прямо в IDE? Очень напоминает hot reload в Webpack, но это только видимость — пока проект собирается пару миллисекунд.
Главным образом нам тут интересны только два файла, всё остальное — системные настройки, контексты приложения и тому подобное:
TestProjectApp.swift:
import SwiftUI
@main
struct TestProjectApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
ContentView.swift:
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Рисунок 8. Меню выбора элементов
Мы реализовали «Hello World» с помощью CRA и Swift. Сделаем выводы:
- TestProjectApp.swift аналогичен index.js и без лишних слов вставляет приложение на экран, оборачивает в контексты и прочее;
- ContentView.swift аналогичен App.js — в нём лежат компоненты, отображение которых на экране можно настраивать;
- CSS в Swift — набор методов конкретной вьюхи (корректный аналог класса/компонента), которые при вызове заменяют вьюху на новую с применёнными модификаторами.
Генерация кода в Xcode
Самой улётной фичей в Xcode оказалась генерация кода без его написания. Звучит очень странно, но просто посмотрите, как это работает. На скриншотах 8, 9, 10 виден процесс добавления кнопки.
Если нажать на обведённый красным плюсик вверху экрана, мы увидим выбор из всех предлагаемых в iOS компонентов. Чекбоксы, тексты, таблицы, списки, кнопки — всё что угодно.
Рисунок 9. Добавление кнопки на экран
Например, можно легко перетащить кнопку как в код, так и сразу на экран для большей наглядности. Советую перетаскивать сразу на экран, чтобы не упустить некий vStack, который тут используется в качестве сетки.
Рисунок 10. Результат добавления элемента
На экране появилась кнопка, а в код автоматически добавилcя Button и vStack, который оборачивает стоявший ранее Text. Убойно! Но как показывает практика, разработчики перестают пользоваться этим, как только достигают максимального уровня просвещения, то есть выучивают наизусть все элементы и их модификаторы.
Взаимодействуем с экраном
Пока в обоих проектах есть лишь набор элементов на странице, с которыми вроде бы можно взаимодействовать, но без какого-либо отклика. Пойдём по классике: чтобы не терять время на сложные примеры, напишем по каунтеру в проектах и посмотрим, где удобнее.
В React
Пройдёмся по React: заведём состояние под счётчик, используем колбек, чтобы гарантированно избежать утечек памяти, положим отображалку и кнопку. Получится приблизительно такой код:
function App() {
const [count, setCount] = React.useState(0);
const bump = React.useCallback(() => {
setCount(c => c+1);
}, [])
return (
<div className="App">
<div className="label">
You clicked { count } times.
</div>
<button className="button" onClick={bump}>
bump
</button>
</div>
);
}
Результат можно увидеть в браузере, который откроется сразу после выполнения команды npm run start. Как видите, я натыкал уже 26 раз :)
Рисунок 11. Счётчик на React.js
В SwiftUi
А теперь обратимся к документации SwiftUi. Главной фичей, которая отличает новый фреймворк от старого (помимо упрощённого интерфейса), стали состояния. Да-да, ровно такие же состояния, как и в React — им явно вдохновлялись!
Чтобы это использовать, нужно добавить в класс переменную, желательно приватную, затем добавить к ней аннотацию @State, использовать её и обновлять по действию на кнопке. Обратите внимание: вся работа оптимизирована компилятором.
Рисунок 12. Код счётчика и счётчик на SwiftUi
Этот вариант отличается от React только синтаксисом. Вместо useState и декомпозиции массива тут лишь одна дополнительная аннотация. Работает она точно так же: при смене значения перерисовываются вьюхи, которые используют эту переменную.
Что я понял и что дальше
Создание проектов для браузера и мобилки отличаются кардинально. Для React есть консольный интерфейс — Create React App, который позволяет создавать конфигурацию проекта с всеми необходимыми инструментами сборки, тестирования и дополнительными библиотеками. Это всё зашито в reactscripts и инжектится в проект, что позволяет сделать более точную настройку.
Для SwiftUi процесс создания проекта кажется интуитивно понятным. В IDE можно тонко настроить проект, выбрать доступную версию iOS, базу данных в проекте и прочее. Самое главное с моей точки зрения — что для создания первого проекта необходимо знать только название проекта, который хочется завести. А остальное — дело практики!
Структура в React и SwiftUi на первый взгляд почти не отличается. Только у веб-приложения есть index.html, а у мобильного этот файл отсутствует.
Дальше я планирую создать проект с более сложной функциональностью. Для этого мне потребуются данные, которые я буду отображать, идея и немного фантазии для её визуализации. Ну и, конечно, время, чтобы это написать.
Но об этом я расскажу в другой статье.
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, JavaScript, Программирование, GitHub, Игры и игровые приставки] Разработчик сделал Doom Captcha — теперь можно проходить тест на робота играя
- [Разработка веб-сайтов, Разработка мобильных приложений, Карьера в IT-индустрии] Без тимлида не обойтись, а что насчет техлида?
- [Perl, Ruby, PHP, Python, JavaScript] Насколько вам наплевать на фичи последней версии языка?
- [Разработка мобильных приложений, Тестирование мобильных приложений] Как в Ozon пришли к релизам мобильных приложений раз в неделю
- [CMS, PHP, MySQL, JavaScript] CS Cart или через терни к черной дыре костылей и оптимизаций
- [Разработка мобильных приложений, Тестирование мобильных приложений, Управление проектами, Аналитика мобильных приложений, Читальный зал] Подборка 150+ ресурсов для управления и работы IT-команды
- [Разработка веб-сайтов, HTML, ReactJS] Немного о том, как работает виртуальный DOM в React (перевод)
- [Разработка мобильных приложений, Разработка под Android, Обработка изображений] Рисуем светом: длинная выдержка на Android
- [Разработка мобильных приложений, Разработка под Android, Дизайн мобильных приложений, Монетизация мобильных приложений] Google I/O 2021: что нового для Android-разработчиков
- [Разработка под iOS, Swift] Обертки свойств в Swift с примерами кода (перевод)
Теги для поиска: #_javascript, #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_swift, #_reactjs, #_jandeks.praktikum (Яндекс.Практикум), #_react, #_swift, #_blog_kompanii_jandeks.praktikum (
Блог компании Яндекс.Практикум
), #_javascript, #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
), #_swift, #_reactjs
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 17:24
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет! Меня зовут Лёня, я фронтенд-разработчик в компании KOTELOV и выпускник курса «Мидл фронтенд-разработчик» в Яндекс.Практикуме. Общаясь с коллегами, которые занимаются мобильной разработкой, я понял, что у них всё гораздо сложнее, чем в вебе. Я взял свой телефон в руки, посмотрел на известные приложения типа телеги или Яндекс.Музыки с точки зрения человека, который их пишет, и подумал: «Вау, как круто! Я тоже хочу так уметь!» Тогда я принял твёрдое решение изучить iOS-разработку и написать собственное приложение. На выбор было два языка: Objective-C и Swift. К динозаврам моё отношение подозрительное, поэтому я принял решение набивать шишки обо что-то помягче и посвежее. Этим постом я открываю цикл про то, как я переезжал с фронта на мобилку и с чем столкнулся по пути. Если вы тоже посматриваете в сторону мобилок, вам может пригодиться мой опыт. оригинал Готовимся к переходу — изучаем SwiftUI Два года назад Apple выпустила SwiftUI — новый фреймворк для своего языка. До этого разработчики писали на UI Kit, но это дело прошлого. Думаю, что на нём до сих пор пишут лишь потому, что сложно в один день переписать весь легаси-код на новый лад. Для себя я уже давно вынес правило: никогда не изучай старое, если есть новое. Но на всякий случай я изучил различия между старым UI Kit Storyboard и SwiftUI, а также мнения людей, которые в теме. Беглый сравнительный анализ показал, что SwiftUI отличается от UI Kit в лучшую сторону: у Swift более удобный декларативный стиль, есть двусторонний биндинг (aka «состояния в React»), нормальный встроенный роутинг и ещё куча крутых штук, которые упрощают построение архитектуры приложения. В общем, выбор очевиден. Кстати, про архитектуру. Я React-разработчик, у нас компонентный подход. Так что сначала я разузнал про все паттерны проектирования — MVC, MVVM, MVP, VIPER. Выяснилось, что наиболее применимая для React архитектура — MVC в стеке React+Redux. В таком стеке хранилище будет выступать в роли модели, редьюсер — в роли контроллера, а компоненты — вьюх. Рисунок 1. Использование React + Redux в качестве MVC-приложения Чтобы изучить Swift, как любой нормальный разработчик, сначала я полез в документацию языка. Хоть она крутая и подробная, в ней нет инструкции вроде «Как за пять минут сделать какую-нибудь ерунду», как на начальной странице React, когда нужно просто изучить синтаксис. Поэтому мне пришлось смотреть длиннющий туториал по основам Swift (предупреждаю, он нудный). Не пожалел, потому что заодно и про типизацию в языке узнал. В отличие от JS, Swift — строго типизированный язык. Подозреваю, что если бы я не знал до этого TypeScript, погряз бы с ходу в этом аду из типов. Во-первых, там есть целая куча разных типов чисел. Во-вторых, нет классических объектов: вместо них есть некие тюплы. В-третьих, сразу несколько видов разных списков. Поэтому, чтобы не запутаться в типизации Swift, советую сперва хорошенько разобраться с TypeScript. Разобравшись с языком, я порешал пару алгоритмических задачек на нём с целью проверить, всё ли я правильно понял, написал пару простеньких игрушек и решил взяться за что-то покрупнее. Создаём проекты Моя конечная цель — написать два полноценно работающих мессенджера на SwiftUI и на React. Cпасибо Яндекс.Практикуму за то, что я теперь не парюсь над идеями приложений: во время учёбы нам предложили написать чатик на чистом JS, и я подумал, что это отличный тренажёр для любого языка. Часть с написанием бэкенда я вынесу за скобки. Начнём с вывода чего-нибудь на экран. «Hello World» на React Что представляет из себя «Hello World» на React? Это html-файл с div#root, index.js и App.js. Используем Create React App и создадим новый проект. Рисунок 2. Создание нового проекта с использованием Create React App Да, это на самом деле просто. Перейдём в созданную папку и посмотрим, что эти команды успели насоздавать. Рисунок 3. Базовая структура папок А успели они много интересного. Ещё занятнее то, что кроется под капотом. Но про это и без меня уже написали кучу статей, например «Под капотом у React. Пишем свою реализацию с нуля». А вот содержимое тех файлов, что нам интересны: ./public/index.html
<!doctype html> <html lang='ru'> <head> <meta charset='UTF-8'> <meta http-equiv='X-UA-Compatible' content='ie=edge'> <title>my-app</title> </head> <body> <div id="root"></div> </body> </html> /src/index.js
import * as React from 'react'; import ReactDOM from 'react-dom'; import App from ‘@routes/App’; ReactDOM.render( <App />, document.getElementById('root'), ); ./src/App.jsx
import * as React from 'react'; export default function App() { return ( <div> Hello world! </div> ) }; Такой набор файлов с использованием Create React App позволяет вывести на экран классическую фразу «Hello World». В этом же консольном интерфейсе мы можем подключить шаблоны по типу использования — Typescript или Redux, — чтобы не создавать их вручную. Кроме всех мелочей, нужно ещё подключить сервер для раздачи артефактов — NGINX или node.js, чтобы проект заработал в интернете. А ещё полезно знать Docker и понимать, куда и как выложить свой проект. «Hello World» на Swift Теперь мы хотим увидеть такой же результат на экране своего смартфона. Для этого нужно скачать IDE от Apple — XCode. 14 гигов, конечно, — это серьёзно, но надо так надо. Рисунок 4. Стартовый экран Xcode Рисунок 5. Выбор типа создаваемого проекта Как оказалось, там тоже ничего сложного. Интерфейс очень наглядный: мне ни разу не пришлось запускать консоль — всё, что нужно, можно с лёгкостью натыкать мышкой. Мы будем создавать проект для iOS. Важно отметить, что это сразу включает в себя и iPhone, и iPad. Это приложение можно тонко сконфигурировать под свои нужды: Рисунок 6. Конфигурация создаваемого проекта Прекрасно! Операции длились несколько секунд, а на выходе у нас получилось примерно столько же файлов, сколько и при CRA. Но в отличие от CRA, Swift всегда создаёт пару дополнительных папок в новом проекте. Они нужны для тестов. А CRA располагает такие файлы рядом с созданными компонентами. Такова особенность архитектуры приложений. Рисунок 7. Структура папок при создании проекта Минуточку, а это что? Код в рантайме перекомпилируется и показывается прямо в IDE? Очень напоминает hot reload в Webpack, но это только видимость — пока проект собирается пару миллисекунд. Главным образом нам тут интересны только два файла, всё остальное — системные настройки, контексты приложения и тому подобное: TestProjectApp.swift:
import SwiftUI @main struct TestProjectApp: App { var body: some Scene { WindowGroup { ContentView() } } ContentView.swift:
import SwiftUI struct ContentView: View { var body: some View { Text("Hello, world!") .padding() } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } Рисунок 8. Меню выбора элементов Мы реализовали «Hello World» с помощью CRA и Swift. Сделаем выводы:
Генерация кода в Xcode Самой улётной фичей в Xcode оказалась генерация кода без его написания. Звучит очень странно, но просто посмотрите, как это работает. На скриншотах 8, 9, 10 виден процесс добавления кнопки. Если нажать на обведённый красным плюсик вверху экрана, мы увидим выбор из всех предлагаемых в iOS компонентов. Чекбоксы, тексты, таблицы, списки, кнопки — всё что угодно. Рисунок 9. Добавление кнопки на экран Например, можно легко перетащить кнопку как в код, так и сразу на экран для большей наглядности. Советую перетаскивать сразу на экран, чтобы не упустить некий vStack, который тут используется в качестве сетки. Рисунок 10. Результат добавления элемента На экране появилась кнопка, а в код автоматически добавилcя Button и vStack, который оборачивает стоявший ранее Text. Убойно! Но как показывает практика, разработчики перестают пользоваться этим, как только достигают максимального уровня просвещения, то есть выучивают наизусть все элементы и их модификаторы. Взаимодействуем с экраном Пока в обоих проектах есть лишь набор элементов на странице, с которыми вроде бы можно взаимодействовать, но без какого-либо отклика. Пойдём по классике: чтобы не терять время на сложные примеры, напишем по каунтеру в проектах и посмотрим, где удобнее. В React Пройдёмся по React: заведём состояние под счётчик, используем колбек, чтобы гарантированно избежать утечек памяти, положим отображалку и кнопку. Получится приблизительно такой код: function App() {
const [count, setCount] = React.useState(0); const bump = React.useCallback(() => { setCount(c => c+1); }, []) return ( <div className="App"> <div className="label"> You clicked { count } times. </div> <button className="button" onClick={bump}> bump </button> </div> ); } Результат можно увидеть в браузере, который откроется сразу после выполнения команды npm run start. Как видите, я натыкал уже 26 раз :) Рисунок 11. Счётчик на React.js В SwiftUi А теперь обратимся к документации SwiftUi. Главной фичей, которая отличает новый фреймворк от старого (помимо упрощённого интерфейса), стали состояния. Да-да, ровно такие же состояния, как и в React — им явно вдохновлялись! Чтобы это использовать, нужно добавить в класс переменную, желательно приватную, затем добавить к ней аннотацию @State, использовать её и обновлять по действию на кнопке. Обратите внимание: вся работа оптимизирована компилятором. Рисунок 12. Код счётчика и счётчик на SwiftUi Этот вариант отличается от React только синтаксисом. Вместо useState и декомпозиции массива тут лишь одна дополнительная аннотация. Работает она точно так же: при смене значения перерисовываются вьюхи, которые используют эту переменную. Что я понял и что дальше Создание проектов для браузера и мобилки отличаются кардинально. Для React есть консольный интерфейс — Create React App, который позволяет создавать конфигурацию проекта с всеми необходимыми инструментами сборки, тестирования и дополнительными библиотеками. Это всё зашито в reactscripts и инжектится в проект, что позволяет сделать более точную настройку. Для SwiftUi процесс создания проекта кажется интуитивно понятным. В IDE можно тонко настроить проект, выбрать доступную версию iOS, базу данных в проекте и прочее. Самое главное с моей точки зрения — что для создания первого проекта необходимо знать только название проекта, который хочется завести. А остальное — дело практики! Структура в React и SwiftUi на первый взгляд почти не отличается. Только у веб-приложения есть index.html, а у мобильного этот файл отсутствует. Дальше я планирую создать проект с более сложной функциональностью. Для этого мне потребуются данные, которые я буду отображать, идея и немного фантазии для её визуализации. Ну и, конечно, время, чтобы это написать. Но об этом я расскажу в другой статье. =========== Источник: habr.com =========== Похожие новости:
Блог компании Яндекс.Практикум ), #_javascript, #_razrabotka_mobilnyh_prilozhenij ( Разработка мобильных приложений ), #_swift, #_reactjs |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 17:24
Часовой пояс: UTC + 5