Compare commits
	
		
			4 Commits
		
	
	
		
			58c841b5c7
			...
			b466864350
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| b466864350 | |||
| 107318ef21 | |||
| e3cf374893 | |||
| 6eed966fc9 | 
@ -61,6 +61,12 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "2FA включена" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок уведомления об успешной активации 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "2FA отключена" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок уведомления об отключении 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Companion ID" : {
 | 
					    "Companion ID" : {
 | 
				
			||||||
      "comment" : "Search placeholder companion title"
 | 
					      "comment" : "Search placeholder companion title"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -83,6 +89,12 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Email" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок экрана настроек email"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Email не подтверждён. Подтвердите, чтобы активировать дополнительные проверки." : {
 | 
				
			||||||
 | 
					      "comment" : "Описание необходимости подтверждения email"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Fun Fest" : {
 | 
					    "Fun Fest" : {
 | 
				
			||||||
      "comment" : "Fun Fest",
 | 
					      "comment" : "Fun Fest",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -218,6 +230,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Безопасность" : {
 | 
					    "Безопасность" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок экрана настроек безопасности",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
        "en" : {
 | 
					        "en" : {
 | 
				
			||||||
          "stringUnit" : {
 | 
					          "stringUnit" : {
 | 
				
			||||||
@ -247,6 +260,12 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Введите код из приложения" : {
 | 
				
			||||||
 | 
					      "comment" : "Поле ввода кода 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Введите пароль" : {
 | 
				
			||||||
 | 
					      "comment" : "Поле ввода пароля на приложение"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Веб" : {
 | 
					    "Веб" : {
 | 
				
			||||||
      "comment" : "Тип сессии — веб"
 | 
					      "comment" : "Тип сессии — веб"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -285,6 +304,12 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "Включено" : {
 | 
					    "Включено" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Включить" : {
 | 
				
			||||||
 | 
					      "comment" : "Кнопка подтверждения включения 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Включить 2FA" : {
 | 
				
			||||||
 | 
					      "comment" : "Тоггл активации 2FA"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Включить автоудаление аккаунта" : {
 | 
					    "Включить автоудаление аккаунта" : {
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -296,6 +321,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Включить двухфакторную аутентификацию?" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок подтверждения включения 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Вложение" : {
 | 
					    "Вложение" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -335,6 +363,9 @@
 | 
				
			|||||||
    "Всего сессий" : {
 | 
					    "Всего сессий" : {
 | 
				
			||||||
      "comment" : "Сводка по количеству сессий"
 | 
					      "comment" : "Сводка по количеству сессий"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Вход и защита аккаунта (заглушка)" : {
 | 
				
			||||||
 | 
					      "comment" : "Раздел настроек безопасности для аутентификации"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Вы" : {
 | 
					    "Вы" : {
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
        "en" : {
 | 
					        "en" : {
 | 
				
			||||||
@ -345,12 +376,18 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Вы всегда можете отключить двухфакторную защиту, но мы рекомендуем оставлять её включённой для безопасности." : {
 | 
				
			||||||
 | 
					      "comment" : "Рекомендация оставить 2FA включенной"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Вы выйдете из выбранной сессии." : {
 | 
					    "Вы выйдете из выбранной сессии." : {
 | 
				
			||||||
      "comment" : "Описание подтверждения завершения конкретной сессии"
 | 
					      "comment" : "Описание подтверждения завершения конкретной сессии"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Вы выйдете со всех устройств, кроме текущего." : {
 | 
					    "Вы выйдете со всех устройств, кроме текущего." : {
 | 
				
			||||||
      "comment" : "Описание подтверждения завершения сессий"
 | 
					      "comment" : "Описание подтверждения завершения сессий"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Вы можете включить защиту снова в любой момент." : {
 | 
				
			||||||
 | 
					      "comment" : "Сообщение после отключения 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Выберите оценку — это поможет нам понять настроение." : {
 | 
					    "Выберите оценку — это поможет нам понять настроение." : {
 | 
				
			||||||
      "comment" : "feedback: rating hint",
 | 
					      "comment" : "feedback: rating hint",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -403,6 +440,7 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Двухфакторная аутентификация" : {
 | 
					    "Двухфакторная аутентификация" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок экрана 2FA\nПереход к настройкам двухфакторной аутентификации",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
        "en" : {
 | 
					        "en" : {
 | 
				
			||||||
          "stringUnit" : {
 | 
					          "stringUnit" : {
 | 
				
			||||||
@ -412,6 +450,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Двухфакторная аутентификация настроена." : {
 | 
				
			||||||
 | 
					      "comment" : "Сообщение после успешного подтверждения кода 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Десктоп" : {
 | 
					    "Десктоп" : {
 | 
				
			||||||
      "comment" : "Тип сессии — десктоп"
 | 
					      "comment" : "Тип сессии — десктоп"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -432,6 +473,9 @@
 | 
				
			|||||||
    "Добавьте контакты, чтобы быстрее выходить на связь." : {
 | 
					    "Добавьте контакты, чтобы быстрее выходить на связь." : {
 | 
				
			||||||
      "comment" : "Contacts empty state subtitle"
 | 
					      "comment" : "Contacts empty state subtitle"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Добавьте новый аккаунт в приложении аутентификации и введите следующий ключ:" : {
 | 
				
			||||||
 | 
					      "comment" : "Инструкция по добавлению ключа 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Другие устройства (%d)" : {
 | 
					    "Другие устройства (%d)" : {
 | 
				
			||||||
      "comment" : "Заголовок секции других устройств с количеством"
 | 
					      "comment" : "Заголовок секции других устройств с количеством"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -474,9 +518,6 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "Заглушка: Push-уведомления" : {
 | 
					    "Заглушка: Push-уведомления" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "Заглушка: Двухфакторная аутентификация" : {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Заглушка: Другие настройки" : {
 | 
					    "Заглушка: Другие настройки" : {
 | 
				
			||||||
      "extractionState" : "stale",
 | 
					      "extractionState" : "stale",
 | 
				
			||||||
@ -570,6 +611,12 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Защита входа" : {
 | 
				
			||||||
 | 
					      "comment" : "Раздел защиты входа через email"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Защита приложением будет добавлена в будущих обновлениях." : {
 | 
				
			||||||
 | 
					      "comment" : "Сообщение заглушки пароля на приложение"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Здесь не будут чаты" : {
 | 
					    "Здесь не будут чаты" : {
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
        "en" : {
 | 
					        "en" : {
 | 
				
			||||||
@ -583,6 +630,9 @@
 | 
				
			|||||||
    "Здесь появится информация о собеседнике и существующих чатах." : {
 | 
					    "Здесь появится информация о собеседнике и существующих чатах." : {
 | 
				
			||||||
      "comment" : "Search placeholder description"
 | 
					      "comment" : "Search placeholder description"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Значение сохранено в буфере обмена." : {
 | 
				
			||||||
 | 
					      "comment" : "Сообщение после копирования"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Идея" : {
 | 
					    "Идея" : {
 | 
				
			||||||
      "comment" : "feedback category: idea",
 | 
					      "comment" : "feedback category: idea",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -663,6 +713,12 @@
 | 
				
			|||||||
    "Код дружбы" : {
 | 
					    "Код дружбы" : {
 | 
				
			||||||
      "comment" : "Friend code badge"
 | 
					      "comment" : "Friend code badge"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Код принят" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок успешного подтверждения кода 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Коды восстановления" : {
 | 
				
			||||||
 | 
					      "comment" : "Раздел кодов восстановления 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Контактов пока нет" : {
 | 
					    "Контактов пока нет" : {
 | 
				
			||||||
      "comment" : "Contacts empty state title"
 | 
					      "comment" : "Contacts empty state title"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -816,6 +872,9 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Мессенджер-режим сейчас проработан примерно на 50%." : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Мессенджер-режим сейчас проработан примерно на 60%." : {
 | 
					    "Мессенджер-режим сейчас проработан примерно на 60%." : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -867,6 +926,12 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Мы отправим код подтверждения на привязанный email каждый раз при входе." : {
 | 
				
			||||||
 | 
					      "comment" : "Описание работы кодов при входе"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Мы отправим письмо, как только функция будет готова." : {
 | 
				
			||||||
 | 
					      "comment" : "Сообщение при недоступной отправке письма"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Мы постараемся всё исправить. Напишите, что смутило." : {
 | 
					    "Мы постараемся всё исправить. Напишите, что смутило." : {
 | 
				
			||||||
      "comment" : "feedback: rating description 2",
 | 
					      "comment" : "feedback: rating description 2",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -958,6 +1023,12 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Настоящая защита приложения появится позже. Пока вы можете ознакомится с макетом." : {
 | 
				
			||||||
 | 
					      "comment" : "Описание заглушки для пароля на приложение"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Настройка приложения" : {
 | 
				
			||||||
 | 
					      "comment" : "Раздел инструкций подключения"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Настройки" : {
 | 
					    "Настройки" : {
 | 
				
			||||||
      "comment" : "Settings",
 | 
					      "comment" : "Settings",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -969,6 +1040,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Настройки email" : {
 | 
				
			||||||
 | 
					      "comment" : "Переход к настройкам безопасности email"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Настройки приватности" : {
 | 
					    "Настройки приватности" : {
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
        "en" : {
 | 
					        "en" : {
 | 
				
			||||||
@ -1112,6 +1186,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Неверный код" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок ошибки неправильного кода 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Неверный код приглашения." : {
 | 
					    "Неверный код приглашения." : {
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
        "en" : {
 | 
					        "en" : {
 | 
				
			||||||
@ -1363,6 +1440,12 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "Основной режим находится в ранней разработке (около 10%)." : {
 | 
					    "Основной режим находится в ранней разработке (около 10%)." : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Отключить" : {
 | 
				
			||||||
 | 
					      "comment" : "Кнопка подтверждения отключения 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Отключить двухфакторную аутентификацию?" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок подтверждения отключения 2FA"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Открыть правила" : {
 | 
					    "Открыть правила" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1384,6 +1467,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Отправить письмо подтверждения" : {
 | 
				
			||||||
 | 
					      "comment" : "Кнопка отправки письма подтверждения"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Отправляем..." : {
 | 
					    "Отправляем..." : {
 | 
				
			||||||
      "comment" : "feedback: sending state",
 | 
					      "comment" : "feedback: sending state",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -1503,7 +1589,7 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Пароли не совпадают" : {
 | 
					    "Пароли не совпадают" : {
 | 
				
			||||||
      "comment" : "Пароли не совпадают",
 | 
					      "comment" : "Заголовок ошибки несовпадения паролей\nПароли не совпадают",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
        "en" : {
 | 
					        "en" : {
 | 
				
			||||||
          "stringUnit" : {
 | 
					          "stringUnit" : {
 | 
				
			||||||
@ -1556,6 +1642,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Пароль на приложение" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок экрана пароля на приложение\nПереход к настройкам пароля на приложение"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Пароль не удовлетворяет требованиям" : {
 | 
					    "Пароль не удовлетворяет требованиям" : {
 | 
				
			||||||
      "extractionState" : "manual",
 | 
					      "extractionState" : "manual",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -1577,12 +1666,18 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Пароль-приложение" : {
 | 
				
			||||||
 | 
					      "comment" : "Раздел формы установки пароля на приложение"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Первый вход: %@" : {
 | 
					    "Первый вход: %@" : {
 | 
				
			||||||
      "comment" : "Дата первого входа в сессию"
 | 
					      "comment" : "Дата первого входа в сессию"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : {
 | 
					    "Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : {
 | 
				
			||||||
      "comment" : "FAQ answer: reset password"
 | 
					      "comment" : "FAQ answer: reset password"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Повторите пароль" : {
 | 
				
			||||||
 | 
					      "comment" : "Поле подтверждения пароля на приложение"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Повторить" : {
 | 
					    "Повторить" : {
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
        "en" : {
 | 
					        "en" : {
 | 
				
			||||||
@ -1630,6 +1725,12 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "Подключение" : {
 | 
					    "Подключение" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Подтвердить" : {
 | 
				
			||||||
 | 
					      "comment" : "Кнопка подтверждения кода 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Подтверждение email" : {
 | 
				
			||||||
 | 
					      "comment" : "Раздел подтверждения email"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Подтверждение пароля" : {
 | 
					    "Подтверждение пароля" : {
 | 
				
			||||||
      "comment" : "Подтверждение пароля",
 | 
					      "comment" : "Подтверждение пароля",
 | 
				
			||||||
@ -1702,6 +1803,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Получать коды на email при входе" : {
 | 
				
			||||||
 | 
					      "comment" : "Переключатель отправки кодов при входе"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Получить ответ от команды" : {
 | 
					    "Получить ответ от команды" : {
 | 
				
			||||||
      "comment" : "feedback: contact toggle",
 | 
					      "comment" : "feedback: contact toggle",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -1783,6 +1887,9 @@
 | 
				
			|||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Приватность и контроль" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Приватные чаты" : {
 | 
					    "Приватные чаты" : {
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -1885,6 +1992,12 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Проверочный код" : {
 | 
				
			||||||
 | 
					      "comment" : "Раздел верификации 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Проверьте ввод и попробуйте снова." : {
 | 
				
			||||||
 | 
					      "comment" : "Сообщение ошибки несовпадения паролей"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Проверьте данные и повторите попытку." : {
 | 
					    "Проверьте данные и повторите попытку." : {
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
        "en" : {
 | 
					        "en" : {
 | 
				
			||||||
@ -1895,6 +2008,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Проверьте цифры и попробуйте снова." : {
 | 
				
			||||||
 | 
					      "comment" : "Описание ошибки неверного кода 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Произошла неизвестная ошибка." : {
 | 
					    "Произошла неизвестная ошибка." : {
 | 
				
			||||||
      "comment" : "Search unknown error"
 | 
					      "comment" : "Search unknown error"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -2092,6 +2208,9 @@
 | 
				
			|||||||
    "Связаться с разработчиками" : {
 | 
					    "Связаться с разработчиками" : {
 | 
				
			||||||
      "comment" : "FAQ: contact developers link"
 | 
					      "comment" : "FAQ: contact developers link"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Сгенерируйте резервные коды и сохраните их в надежном месте." : {
 | 
				
			||||||
 | 
					      "comment" : "Подсказка о необходимости генерации кодов"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Сервер вернул ошибку (%d)." : {
 | 
					    "Сервер вернул ошибку (%d)." : {
 | 
				
			||||||
      "comment" : "Search error server status"
 | 
					      "comment" : "Search error server status"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -2140,11 +2259,20 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Скопировано" : {
 | 
				
			||||||
 | 
					      "comment" : "Заголовок уведомления о копировании"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Скопировать" : {
 | 
					    "Скопировать" : {
 | 
				
			||||||
      "comment" : "Search placeholder copy"
 | 
					      "comment" : "Search placeholder copy"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Скопировать ключ" : {
 | 
				
			||||||
 | 
					      "comment" : "Кнопка копирования секретного ключа"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Скопировать код" : {
 | 
				
			||||||
 | 
					      "comment" : "Кнопка копирования кода восстановления"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Скоро" : {
 | 
					    "Скоро" : {
 | 
				
			||||||
      "comment" : "Add blocked user placeholder title\nContacts placeholder title"
 | 
					      "comment" : "Add blocked user placeholder title\nContacts placeholder title\nЗаголовок заглушки"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Скоро появится мини-игра, где можно заработать очки для кастомизации профиля. Следите за обновлениями!" : {
 | 
					    "Скоро появится мини-игра, где можно заработать очки для кастомизации профиля. Следите за обновлениями!" : {
 | 
				
			||||||
      "comment" : "Concept tab placeholder description"
 | 
					      "comment" : "Concept tab placeholder description"
 | 
				
			||||||
@ -2187,6 +2315,9 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "Согласиться с правилами" : {
 | 
					    "Согласиться с правилами" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Создать новые коды" : {
 | 
				
			||||||
 | 
					      "comment" : "Кнопка генерации резервных кодов"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Сообщение" : {
 | 
					    "Сообщение" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -2205,6 +2336,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Сохраните секретный ключ и введите код из приложения, чтобы завершить настройку." : {
 | 
				
			||||||
 | 
					      "comment" : "Сообщение после активации 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Сохранить изменения" : {
 | 
					    "Сохранить изменения" : {
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
        "en" : {
 | 
					        "en" : {
 | 
				
			||||||
@ -2215,6 +2349,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Сохранить пароль" : {
 | 
				
			||||||
 | 
					      "comment" : "Кнопка сохранения пароля на приложение"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Спасибо! Мы получили ваш отзыв" : {
 | 
					    "Спасибо! Мы получили ваш отзыв" : {
 | 
				
			||||||
      "comment" : "feedback: success title",
 | 
					      "comment" : "feedback: success title",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
@ -2258,6 +2395,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Статус защиты" : {
 | 
				
			||||||
 | 
					      "comment" : "Раздел состояния 2FA"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Стикеры" : {
 | 
					    "Стикеры" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@ -2363,6 +2503,9 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Функция пока недоступна." : {
 | 
				
			||||||
 | 
					      "comment" : "Сообщение заглушки"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Центр авторов" : {
 | 
					    "Центр авторов" : {
 | 
				
			||||||
      "comment" : "Creator Center",
 | 
					      "comment" : "Creator Center",
 | 
				
			||||||
      "localizations" : {
 | 
					      "localizations" : {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,6 +22,7 @@ class LoginViewModel: ObservableObject {
 | 
				
			|||||||
    @Published var isLoadingTerms: Bool = false
 | 
					    @Published var isLoadingTerms: Bool = false
 | 
				
			||||||
    @Published var termsContent: String = ""
 | 
					    @Published var termsContent: String = ""
 | 
				
			||||||
    @Published var termsErrorMessage: String?
 | 
					    @Published var termsErrorMessage: String?
 | 
				
			||||||
 | 
					    @Published var onboardingDestination: OnboardingDestination?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private let authService = AuthService()
 | 
					    private let authService = AuthService()
 | 
				
			||||||
    private let socketService = SocketService.shared
 | 
					    private let socketService = SocketService.shared
 | 
				
			||||||
@ -32,6 +33,10 @@ class LoginViewModel: ObservableObject {
 | 
				
			|||||||
        case loading
 | 
					        case loading
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    enum OnboardingDestination: Equatable {
 | 
				
			||||||
 | 
					        case securitySettings
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private enum DefaultsKeys {
 | 
					    private enum DefaultsKeys {
 | 
				
			||||||
        static let currentUser = "currentUser"
 | 
					        static let currentUser = "currentUser"
 | 
				
			||||||
        static let userId = "userId"
 | 
					        static let userId = "userId"
 | 
				
			||||||
@ -127,6 +132,7 @@ class LoginViewModel: ObservableObject {
 | 
				
			|||||||
                    self?.isLoggedIn = true // 👈 переключаем на главный экран после автологина
 | 
					                    self?.isLoggedIn = true // 👈 переключаем на главный экран после автологина
 | 
				
			||||||
                    self?.loadStoredUser()
 | 
					                    self?.loadStoredUser()
 | 
				
			||||||
                    self?.socketService.connectForCurrentUser()
 | 
					                    self?.socketService.connectForCurrentUser()
 | 
				
			||||||
 | 
					                    self?.onboardingDestination = .securitySettings
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    self?.socketService.disconnect()
 | 
					                    self?.socketService.disconnect()
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,7 @@ struct MainView: View {
 | 
				
			|||||||
    @State private var isQrPresented = false
 | 
					    @State private var isQrPresented = false
 | 
				
			||||||
    @State private var deepLinkChatItem: PrivateChatListItem?
 | 
					    @State private var deepLinkChatItem: PrivateChatListItem?
 | 
				
			||||||
    @State private var isDeepLinkChatActive = false
 | 
					    @State private var isDeepLinkChatActive = false
 | 
				
			||||||
 | 
					    @State private var hasTriggeredSecuritySettingsOnboarding = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var tabTitle: String {
 | 
					    private var tabTitle: String {
 | 
				
			||||||
        switch selectedTab {
 | 
					        switch selectedTab {
 | 
				
			||||||
@ -172,9 +173,14 @@ struct MainView: View {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        .onAppear {
 | 
					        .onAppear {
 | 
				
			||||||
            enforceTabSelectionForMessengerMode()
 | 
					            enforceTabSelectionForMessengerMode()
 | 
				
			||||||
 | 
					            handleTwoFactorOnboardingIfNeeded()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        .onChange(of: isMessengerModeEnabled) { _ in
 | 
					        .onChange(of: isMessengerModeEnabled) { _ in
 | 
				
			||||||
            enforceTabSelectionForMessengerMode()
 | 
					            enforceTabSelectionForMessengerMode()
 | 
				
			||||||
 | 
					            handleTwoFactorOnboardingIfNeeded()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .onChange(of: viewModel.onboardingDestination) { _ in
 | 
				
			||||||
 | 
					            handleTwoFactorOnboardingIfNeeded()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        .onChange(of: messageCenter.pendingNavigation?.id) { _ in
 | 
					        .onChange(of: messageCenter.pendingNavigation?.id) { _ in
 | 
				
			||||||
            guard !AppConfig.PRESENT_CHAT_AS_SHEET,
 | 
					            guard !AppConfig.PRESENT_CHAT_AS_SHEET,
 | 
				
			||||||
@ -217,6 +223,29 @@ private extension MainView {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func handleTwoFactorOnboardingIfNeeded() {
 | 
				
			||||||
 | 
					        guard viewModel.onboardingDestination == .securitySettings else {
 | 
				
			||||||
 | 
					            hasTriggeredSecuritySettingsOnboarding = false
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        guard !hasTriggeredSecuritySettingsOnboarding else { return }
 | 
				
			||||||
 | 
					        hasTriggeredSecuritySettingsOnboarding = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if isMessengerModeEnabled {
 | 
				
			||||||
 | 
					            if selectedTab != 5 {
 | 
				
			||||||
 | 
					                selectedTab = 5
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            if selectedTab != 3 {
 | 
				
			||||||
 | 
					                selectedTab = 3
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            DispatchQueue.main.async {
 | 
				
			||||||
 | 
					                isSettingsPresented = true
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var deepLinkNavigationLink: some View {
 | 
					    var deepLinkNavigationLink: some View {
 | 
				
			||||||
        NavigationLink(
 | 
					        NavigationLink(
 | 
				
			||||||
            destination: deepLinkChatDestination,
 | 
					            destination: deepLinkChatDestination,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										82
									
								
								yobble/Views/Tab/Settings/Security/AppLockSettingsView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								yobble/Views/Tab/Settings/Security/AppLockSettingsView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct AppLockSettingsView: View {
 | 
				
			||||||
 | 
					    @State private var desiredPassword: String = ""
 | 
				
			||||||
 | 
					    @State private var confirmationPassword: String = ""
 | 
				
			||||||
 | 
					    @State private var activeAlert: AppLockAlert?
 | 
				
			||||||
 | 
					    @FocusState private var focusedField: Field?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private enum Field: Hashable {
 | 
				
			||||||
 | 
					        case desired
 | 
				
			||||||
 | 
					        case confirmation
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        Form {
 | 
				
			||||||
 | 
					            Section(header: Text(NSLocalizedString("Пароль-приложение", comment: "Раздел формы установки пароля на приложение"))) {
 | 
				
			||||||
 | 
					                SecureField(NSLocalizedString("Введите пароль", comment: "Поле ввода пароля на приложение"), text: $desiredPassword)
 | 
				
			||||||
 | 
					                    .focused($focusedField, equals: .desired)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                SecureField(NSLocalizedString("Повторите пароль", comment: "Поле подтверждения пароля на приложение"), text: $confirmationPassword)
 | 
				
			||||||
 | 
					                    .focused($focusedField, equals: .confirmation)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Button(NSLocalizedString("Сохранить пароль", comment: "Кнопка сохранения пароля на приложение")) {
 | 
				
			||||||
 | 
					                    handleSaveTapped()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .disabled(desiredPassword.isEmpty || confirmationPassword.isEmpty)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Section {
 | 
				
			||||||
 | 
					                Text(NSLocalizedString("Настоящая защита приложения появится позже. Пока вы можете ознакомится с макетом.", comment: "Описание заглушки для пароля на приложение"))
 | 
				
			||||||
 | 
					                    .font(.callout)
 | 
				
			||||||
 | 
					                    .foregroundColor(.secondary)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .navigationTitle(NSLocalizedString("Пароль на приложение", comment: "Заголовок экрана пароля на приложение"))
 | 
				
			||||||
 | 
					        .navigationBarTitleDisplayMode(.inline)
 | 
				
			||||||
 | 
					        .onAppear {
 | 
				
			||||||
 | 
					            DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
 | 
				
			||||||
 | 
					                focusedField = .desired
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .alert(item: $activeAlert) { alert in
 | 
				
			||||||
 | 
					            Alert(
 | 
				
			||||||
 | 
					                title: Text(alert.title),
 | 
				
			||||||
 | 
					                message: Text(alert.message),
 | 
				
			||||||
 | 
					                dismissButton: .default(Text(NSLocalizedString("OK", comment: "Общий текст кнопки OK")))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private func handleSaveTapped() {
 | 
				
			||||||
 | 
					        guard !desiredPassword.isEmpty, desiredPassword == confirmationPassword else {
 | 
				
			||||||
 | 
					            activeAlert = AppLockAlert(
 | 
				
			||||||
 | 
					                title: NSLocalizedString("Пароли не совпадают", comment: "Заголовок ошибки несовпадения паролей"),
 | 
				
			||||||
 | 
					                message: NSLocalizedString("Проверьте ввод и попробуйте снова.", comment: "Сообщение ошибки несовпадения паролей"))
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        activeAlert = AppLockAlert(
 | 
				
			||||||
 | 
					            title: NSLocalizedString("Скоро", comment: "Заголовок заглушки"),
 | 
				
			||||||
 | 
					            message: NSLocalizedString("Защита приложением будет добавлена в будущих обновлениях.", comment: "Сообщение заглушки пароля на приложение")
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        desiredPassword.removeAll()
 | 
				
			||||||
 | 
					        confirmationPassword.removeAll()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private struct AppLockAlert: Identifiable {
 | 
				
			||||||
 | 
					    let id = UUID()
 | 
				
			||||||
 | 
					    let title: String
 | 
				
			||||||
 | 
					    let message: String
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if DEBUG
 | 
				
			||||||
 | 
					struct AppLockSettingsView_Previews: PreviewProvider {
 | 
				
			||||||
 | 
					    static var previews: some View {
 | 
				
			||||||
 | 
					        NavigationView {
 | 
				
			||||||
 | 
					            AppLockSettingsView()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@ -0,0 +1,65 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct EmailSecuritySettingsView: View {
 | 
				
			||||||
 | 
					    @State private var isLoginCodesEnabled = false
 | 
				
			||||||
 | 
					    @State private var activeAlert: EmailSecurityAlert?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        Form {
 | 
				
			||||||
 | 
					            Section(header: Text(NSLocalizedString("Защита входа", comment: "Раздел защиты входа через email"))) {
 | 
				
			||||||
 | 
					                Toggle(NSLocalizedString("Получать коды на email при входе", comment: "Переключатель отправки кодов при входе"), isOn: Binding(
 | 
				
			||||||
 | 
					                    get: { isLoginCodesEnabled },
 | 
				
			||||||
 | 
					                    set: { _ in
 | 
				
			||||||
 | 
					                        activeAlert = EmailSecurityAlert(
 | 
				
			||||||
 | 
					                            title: NSLocalizedString("Скоро", comment: "Заголовок заглушки"),
 | 
				
			||||||
 | 
					                            message: NSLocalizedString("Функция пока недоступна.", comment: "Сообщение заглушки")
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        isLoginCodesEnabled = false
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                ))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Text(NSLocalizedString("Мы отправим код подтверждения на привязанный email каждый раз при входе.", comment: "Описание работы кодов при входе"))
 | 
				
			||||||
 | 
					                    .font(.footnote)
 | 
				
			||||||
 | 
					                    .foregroundColor(.secondary)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Section(header: Text(NSLocalizedString("Подтверждение email", comment: "Раздел подтверждения email"))) {
 | 
				
			||||||
 | 
					                Text(NSLocalizedString("Email не подтверждён. Подтвердите, чтобы активировать дополнительные проверки.", comment: "Описание необходимости подтверждения email"))
 | 
				
			||||||
 | 
					                    .font(.callout)
 | 
				
			||||||
 | 
					                    .foregroundColor(.secondary)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Button(NSLocalizedString("Отправить письмо подтверждения", comment: "Кнопка отправки письма подтверждения")) {
 | 
				
			||||||
 | 
					                    activeAlert = EmailSecurityAlert(
 | 
				
			||||||
 | 
					                        title: NSLocalizedString("Скоро", comment: "Заголовок заглушки"),
 | 
				
			||||||
 | 
					                        message: NSLocalizedString("Мы отправим письмо, как только функция будет готова.", comment: "Сообщение при недоступной отправке письма")
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .navigationTitle(NSLocalizedString("Email", comment: "Заголовок экрана настроек email"))
 | 
				
			||||||
 | 
					        .navigationBarTitleDisplayMode(.inline)
 | 
				
			||||||
 | 
					        .alert(item: $activeAlert) { alert in
 | 
				
			||||||
 | 
					            Alert(
 | 
				
			||||||
 | 
					                title: Text(alert.title),
 | 
				
			||||||
 | 
					                message: Text(alert.message),
 | 
				
			||||||
 | 
					                dismissButton: .default(Text(NSLocalizedString("OK", comment: "Общий текст кнопки OK")))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private struct EmailSecurityAlert: Identifiable {
 | 
				
			||||||
 | 
					    let id = UUID()
 | 
				
			||||||
 | 
					    let title: String
 | 
				
			||||||
 | 
					    let message: String
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if DEBUG
 | 
				
			||||||
 | 
					struct EmailSecuritySettingsView_Previews: PreviewProvider {
 | 
				
			||||||
 | 
					    static var previews: some View {
 | 
				
			||||||
 | 
					        NavigationView {
 | 
				
			||||||
 | 
					            EmailSecuritySettingsView()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										226
									
								
								yobble/Views/Tab/Settings/Security/TwoFactorAuthView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								yobble/Views/Tab/Settings/Security/TwoFactorAuthView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,226 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					#if canImport(UIKit)
 | 
				
			||||||
 | 
					import UIKit
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct TwoFactorAuthView: View {
 | 
				
			||||||
 | 
					    @State private var isTwoFactorEnabled = false
 | 
				
			||||||
 | 
					    @State private var showEnableConfirmation = false
 | 
				
			||||||
 | 
					    @State private var showDisableConfirmation = false
 | 
				
			||||||
 | 
					    @State private var secretKey: String = TwoFactorAuthView.generateSecret()
 | 
				
			||||||
 | 
					    @State private var verificationCode: String = ""
 | 
				
			||||||
 | 
					    @State private var backupCodes: [String] = []
 | 
				
			||||||
 | 
					    @State private var activeAlert: TwoFactorAlert?
 | 
				
			||||||
 | 
					    @FocusState private var isCodeFieldFocused: Bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        List {
 | 
				
			||||||
 | 
					            Section(header: Text(NSLocalizedString("Статус защиты", comment: "Раздел состояния 2FA"))) {
 | 
				
			||||||
 | 
					                Toggle(isOn: Binding(
 | 
				
			||||||
 | 
					                    get: { isTwoFactorEnabled },
 | 
				
			||||||
 | 
					                    set: { handleToggleChange($0) }
 | 
				
			||||||
 | 
					                )) {
 | 
				
			||||||
 | 
					                    Label(NSLocalizedString("Включить 2FA", comment: "Тоггл активации 2FA"), systemImage: "lock.shield")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if isTwoFactorEnabled {
 | 
				
			||||||
 | 
					                Section(header: Text(NSLocalizedString("Настройка приложения", comment: "Раздел инструкций подключения"))) {
 | 
				
			||||||
 | 
					                    Text(NSLocalizedString("Добавьте новый аккаунт в приложении аутентификации и введите следующий ключ:", comment: "Инструкция по добавлению ключа 2FA"))
 | 
				
			||||||
 | 
					                        .font(.callout)
 | 
				
			||||||
 | 
					                    keyRow
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Section(header: Text(NSLocalizedString("Проверочный код", comment: "Раздел верификации 2FA"))) {
 | 
				
			||||||
 | 
					                    VStack(alignment: .leading, spacing: 12) {
 | 
				
			||||||
 | 
					                        TextField(NSLocalizedString("Введите код из приложения", comment: "Поле ввода кода 2FA"), text: $verificationCode)
 | 
				
			||||||
 | 
					                            .keyboardType(.numberPad)
 | 
				
			||||||
 | 
					                            .focused($isCodeFieldFocused)
 | 
				
			||||||
 | 
					                            .onChange(of: verificationCode) { newValue in
 | 
				
			||||||
 | 
					                                verificationCode = newValue.trimmingCharacters(in: .whitespacesAndNewlines)
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        Button(action: verifyCode) {
 | 
				
			||||||
 | 
					                            Text(NSLocalizedString("Подтвердить", comment: "Кнопка подтверждения кода 2FA"))
 | 
				
			||||||
 | 
					                                .frame(maxWidth: .infinity)
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        .buttonStyle(.borderedProminent)
 | 
				
			||||||
 | 
					                        .disabled(verificationCode.isEmpty)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    .padding(.vertical, 4)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Section(header: Text(NSLocalizedString("Коды восстановления", comment: "Раздел кодов восстановления 2FA"))) {
 | 
				
			||||||
 | 
					                    if backupCodes.isEmpty {
 | 
				
			||||||
 | 
					                        Text(NSLocalizedString("Сгенерируйте резервные коды и сохраните их в надежном месте.", comment: "Подсказка о необходимости генерации кодов"))
 | 
				
			||||||
 | 
					                            .font(.callout)
 | 
				
			||||||
 | 
					                            .foregroundColor(.secondary)
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        ForEach(backupCodes, id: \.self) { code in
 | 
				
			||||||
 | 
					                            HStack {
 | 
				
			||||||
 | 
					                                Text(code)
 | 
				
			||||||
 | 
					                                    .font(.system(.body, design: .monospaced))
 | 
				
			||||||
 | 
					                                Spacer()
 | 
				
			||||||
 | 
					                                Button(action: { copyToPasteboard(code) }) {
 | 
				
			||||||
 | 
					                                    Image(systemName: "doc.on.doc")
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                .buttonStyle(.plain)
 | 
				
			||||||
 | 
					                                .accessibilityLabel(NSLocalizedString("Скопировать код", comment: "Кнопка копирования кода восстановления"))
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Button(action: generateBackupCodes) {
 | 
				
			||||||
 | 
					                        Label(NSLocalizedString("Создать новые коды", comment: "Кнопка генерации резервных кодов"), systemImage: "arrow.clockwise")
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Section(footer: Text(NSLocalizedString("Вы всегда можете отключить двухфакторную защиту, но мы рекомендуем оставлять её включённой для безопасности.", comment: "Рекомендация оставить 2FA включенной"))) {
 | 
				
			||||||
 | 
					                    EmptyView()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .listStyle(.insetGrouped)
 | 
				
			||||||
 | 
					        .navigationTitle(NSLocalizedString("Двухфакторная аутентификация", comment: "Заголовок экрана 2FA"))
 | 
				
			||||||
 | 
					        .navigationBarTitleDisplayMode(.inline)
 | 
				
			||||||
 | 
					        .alert(item: $activeAlert) { alert in
 | 
				
			||||||
 | 
					            Alert(
 | 
				
			||||||
 | 
					                title: Text(alert.title),
 | 
				
			||||||
 | 
					                message: Text(alert.message),
 | 
				
			||||||
 | 
					                dismissButton: .default(Text(NSLocalizedString("OK", comment: "Общий текст кнопки OK")))
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .confirmationDialog(
 | 
				
			||||||
 | 
					            NSLocalizedString("Включить двухфакторную аутентификацию?", comment: "Заголовок подтверждения включения 2FA"),
 | 
				
			||||||
 | 
					            isPresented: $showEnableConfirmation,
 | 
				
			||||||
 | 
					            titleVisibility: .visible
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            Button(NSLocalizedString("Включить", comment: "Кнопка подтверждения включения 2FA"), role: .destructive) {
 | 
				
			||||||
 | 
					                enableTwoFactor()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Button(NSLocalizedString("Отмена", comment: "Общий текст кнопки отмены"), role: .cancel) {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .confirmationDialog(
 | 
				
			||||||
 | 
					            NSLocalizedString("Отключить двухфакторную аутентификацию?", comment: "Заголовок подтверждения отключения 2FA"),
 | 
				
			||||||
 | 
					            isPresented: $showDisableConfirmation,
 | 
				
			||||||
 | 
					            titleVisibility: .visible
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            Button(NSLocalizedString("Отключить", comment: "Кнопка подтверждения отключения 2FA"), role: .destructive) {
 | 
				
			||||||
 | 
					                disableTwoFactor()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Button(NSLocalizedString("Отмена", comment: "Общий текст кнопки отмены"), role: .cancel) {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private extension TwoFactorAuthView {
 | 
				
			||||||
 | 
					    var keyRow: some View {
 | 
				
			||||||
 | 
					        HStack(alignment: .center, spacing: 12) {
 | 
				
			||||||
 | 
					            Text(secretKey)
 | 
				
			||||||
 | 
					                .font(.system(.body, design: .monospaced))
 | 
				
			||||||
 | 
					                .textSelection(.enabled)
 | 
				
			||||||
 | 
					            Spacer()
 | 
				
			||||||
 | 
					            Button(action: { copyToPasteboard(secretKey) }) {
 | 
				
			||||||
 | 
					                Image(systemName: "doc.on.doc")
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            .buttonStyle(.plain)
 | 
				
			||||||
 | 
					            .accessibilityLabel(NSLocalizedString("Скопировать ключ", comment: "Кнопка копирования секретного ключа"))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .padding(8)
 | 
				
			||||||
 | 
					        .background(Color(UIColor.secondarySystemBackground))
 | 
				
			||||||
 | 
					        .cornerRadius(10)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func handleToggleChange(_ newValue: Bool) {
 | 
				
			||||||
 | 
					        if newValue {
 | 
				
			||||||
 | 
					            showEnableConfirmation = true
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            showDisableConfirmation = true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func enableTwoFactor() {
 | 
				
			||||||
 | 
					        isTwoFactorEnabled = true
 | 
				
			||||||
 | 
					        showEnableConfirmation = false
 | 
				
			||||||
 | 
					        secretKey = Self.generateSecret()
 | 
				
			||||||
 | 
					        verificationCode = ""
 | 
				
			||||||
 | 
					        generateBackupCodes()
 | 
				
			||||||
 | 
					        activeAlert = TwoFactorAlert(
 | 
				
			||||||
 | 
					            title: NSLocalizedString("2FA включена", comment: "Заголовок уведомления об успешной активации 2FA"),
 | 
				
			||||||
 | 
					            message: NSLocalizedString("Сохраните секретный ключ и введите код из приложения, чтобы завершить настройку.", comment: "Сообщение после активации 2FA")
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
 | 
				
			||||||
 | 
					            isCodeFieldFocused = true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func disableTwoFactor() {
 | 
				
			||||||
 | 
					        isTwoFactorEnabled = false
 | 
				
			||||||
 | 
					        showDisableConfirmation = false
 | 
				
			||||||
 | 
					        verificationCode = ""
 | 
				
			||||||
 | 
					        backupCodes.removeAll()
 | 
				
			||||||
 | 
					        activeAlert = TwoFactorAlert(
 | 
				
			||||||
 | 
					            title: NSLocalizedString("2FA отключена", comment: "Заголовок уведомления об отключении 2FA"),
 | 
				
			||||||
 | 
					            message: NSLocalizedString("Вы можете включить защиту снова в любой момент.", comment: "Сообщение после отключения 2FA")
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func verifyCode() {
 | 
				
			||||||
 | 
					        let normalized = verificationCode.trimmingCharacters(in: .whitespacesAndNewlines)
 | 
				
			||||||
 | 
					        guard normalized.count == 6, normalized.allSatisfy(\.isNumber) else {
 | 
				
			||||||
 | 
					            activeAlert = TwoFactorAlert(
 | 
				
			||||||
 | 
					                title: NSLocalizedString("Неверный код", comment: "Заголовок ошибки неправильного кода 2FA"),
 | 
				
			||||||
 | 
					                message: NSLocalizedString("Проверьте цифры и попробуйте снова.", comment: "Описание ошибки неверного кода 2FA")
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        verificationCode = ""
 | 
				
			||||||
 | 
					        activeAlert = TwoFactorAlert(
 | 
				
			||||||
 | 
					            title: NSLocalizedString("Код принят", comment: "Заголовок успешного подтверждения кода 2FA"),
 | 
				
			||||||
 | 
					            message: NSLocalizedString("Двухфакторная аутентификация настроена.", comment: "Сообщение после успешного подтверждения кода 2FA")
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func generateBackupCodes() {
 | 
				
			||||||
 | 
					        backupCodes = Self.generateBackupCodes()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    func copyToPasteboard(_ value: String) {
 | 
				
			||||||
 | 
					#if canImport(UIKit)
 | 
				
			||||||
 | 
					        UIPasteboard.general.string = value
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					        activeAlert = TwoFactorAlert(
 | 
				
			||||||
 | 
					            title: NSLocalizedString("Скопировано", comment: "Заголовок уведомления о копировании"),
 | 
				
			||||||
 | 
					            message: NSLocalizedString("Значение сохранено в буфере обмена.", comment: "Сообщение после копирования")
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static func generateSecret() -> String {
 | 
				
			||||||
 | 
					        let alphabet = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")
 | 
				
			||||||
 | 
					        return String((0..<16).compactMap { _ in alphabet.randomElement() })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static func generateBackupCodes(count: Int = 8) -> [String] {
 | 
				
			||||||
 | 
					        let alphabet = Array("ABCDEFGHJKLMNPQRSTUVWXYZ23456789")
 | 
				
			||||||
 | 
					        return (0..<count).map { _ in
 | 
				
			||||||
 | 
					            String((0..<8).compactMap { _ in alphabet.randomElement() })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private struct TwoFactorAlert: Identifiable {
 | 
				
			||||||
 | 
					    let id = UUID()
 | 
				
			||||||
 | 
					    let title: String
 | 
				
			||||||
 | 
					    let message: String
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if DEBUG
 | 
				
			||||||
 | 
					struct TwoFactorAuthView_Previews: PreviewProvider {
 | 
				
			||||||
 | 
					    static var previews: some View {
 | 
				
			||||||
 | 
					        NavigationView {
 | 
				
			||||||
 | 
					            TwoFactorAuthView()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										76
									
								
								yobble/Views/Tab/Settings/SecuritySettingsView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								yobble/Views/Tab/Settings/SecuritySettingsView.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					import SwiftUI
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct SecuritySettingsView: View {
 | 
				
			||||||
 | 
					    @ObservedObject var viewModel: LoginViewModel
 | 
				
			||||||
 | 
					    @State private var isTwoFactorActive = false
 | 
				
			||||||
 | 
					    @State private var isEmailSettingsActive = false
 | 
				
			||||||
 | 
					    @State private var isAppLockActive = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var body: some View {
 | 
				
			||||||
 | 
					        List {
 | 
				
			||||||
 | 
					            Section(header: Text(NSLocalizedString("Вход и защита аккаунта (заглушка)", comment: "Раздел настроек безопасности для аутентификации"))) {
 | 
				
			||||||
 | 
					                NavigationLink(isActive: $isTwoFactorActive) {
 | 
				
			||||||
 | 
					                    TwoFactorAuthView()
 | 
				
			||||||
 | 
					                } label: {
 | 
				
			||||||
 | 
					                    Label(NSLocalizedString("Двухфакторная аутентификация", comment: "Переход к настройкам двухфакторной аутентификации"), systemImage: "lock.shield")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                NavigationLink(isActive: $isEmailSettingsActive) {
 | 
				
			||||||
 | 
					                    EmailSecuritySettingsView()
 | 
				
			||||||
 | 
					                } label: {
 | 
				
			||||||
 | 
					                    Label(NSLocalizedString("Настройки email", comment: "Переход к настройкам безопасности email"), systemImage: "envelope")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                NavigationLink(isActive: $isAppLockActive) {
 | 
				
			||||||
 | 
					                    AppLockSettingsView()
 | 
				
			||||||
 | 
					                } label: {
 | 
				
			||||||
 | 
					                    Label(NSLocalizedString("Пароль на приложение", comment: "Переход к настройкам пароля на приложение"), systemImage: "lock.square")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Section(header: Text(NSLocalizedString("Приватность и контроль", comment: ""))) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                NavigationLink(destination: EditPrivacyView()) {
 | 
				
			||||||
 | 
					                    Label(NSLocalizedString("Конфиденциальность", comment: ""), systemImage: "lock.fill")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                NavigationLink(destination: ChangePasswordView()) {
 | 
				
			||||||
 | 
					                    Label(NSLocalizedString("Сменить пароль", comment: ""), systemImage: "key")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                NavigationLink(destination: ActiveSessionsView()) {
 | 
				
			||||||
 | 
					                    Label(NSLocalizedString("Активные сессии", comment: ""), systemImage: "iphone")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .listStyle(.insetGrouped)
 | 
				
			||||||
 | 
					        .navigationTitle(NSLocalizedString("Безопасность", comment: "Заголовок экрана настроек безопасности"))
 | 
				
			||||||
 | 
					        .navigationBarTitleDisplayMode(.inline)
 | 
				
			||||||
 | 
					//        .onAppear { handleSecuritySettingsOnboardingIfNeeded() }
 | 
				
			||||||
 | 
					//        .onChange(of: viewModel.onboardingDestination) { _ in
 | 
				
			||||||
 | 
					//            handleSecuritySettingsOnboardingIfNeeded()
 | 
				
			||||||
 | 
					//        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//    private func handleSecuritySettingsOnboardingIfNeeded() {
 | 
				
			||||||
 | 
					//        guard viewModel.onboardingDestination == .securitySettings else { return }
 | 
				
			||||||
 | 
					//        guard !isTwoFactorActive else {
 | 
				
			||||||
 | 
					//            viewModel.onboardingDestination = nil
 | 
				
			||||||
 | 
					//            return
 | 
				
			||||||
 | 
					//        }
 | 
				
			||||||
 | 
					//        DispatchQueue.main.async {
 | 
				
			||||||
 | 
					//            isTwoFactorActive = true
 | 
				
			||||||
 | 
					//            viewModel.onboardingDestination = nil
 | 
				
			||||||
 | 
					//        }
 | 
				
			||||||
 | 
					//    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#if DEBUG
 | 
				
			||||||
 | 
					struct SecuritySettingsView_Previews: PreviewProvider {
 | 
				
			||||||
 | 
					    static var previews: some View {
 | 
				
			||||||
 | 
					        NavigationView {
 | 
				
			||||||
 | 
					            SecuritySettingsView(viewModel: LoginViewModel())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@ -4,6 +4,7 @@ struct SettingsView: View {
 | 
				
			|||||||
    @ObservedObject var viewModel: LoginViewModel
 | 
					    @ObservedObject var viewModel: LoginViewModel
 | 
				
			||||||
    @EnvironmentObject private var themeManager: ThemeManager
 | 
					    @EnvironmentObject private var themeManager: ThemeManager
 | 
				
			||||||
    @State private var isThemeExpanded = false
 | 
					    @State private var isThemeExpanded = false
 | 
				
			||||||
 | 
					    @State private var isSecurityActive = false
 | 
				
			||||||
    private let themeOptions = ThemeOption.ordered
 | 
					    private let themeOptions = ThemeOption.ordered
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var selectedThemeOption: ThemeOption {
 | 
					    private var selectedThemeOption: ThemeOption {
 | 
				
			||||||
@ -18,8 +19,8 @@ struct SettingsView: View {
 | 
				
			|||||||
//                    Label("Мой профиль", systemImage: "person.crop.circle")
 | 
					//                    Label("Мой профиль", systemImage: "person.crop.circle")
 | 
				
			||||||
//                }
 | 
					//                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                NavigationLink(destination: EditPrivacyView()) {
 | 
					                NavigationLink(destination: EditProfileView()) {
 | 
				
			||||||
                    Label(NSLocalizedString("Конфиденциальность", comment: ""), systemImage: "lock.fill")
 | 
					                    Label(NSLocalizedString("Редактировать профиль", comment: ""), systemImage: "person.crop.circle")
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                
 | 
					                
 | 
				
			||||||
                NavigationLink(destination: BlockedUsersView()) {
 | 
					                NavigationLink(destination: BlockedUsersView()) {
 | 
				
			||||||
@ -29,15 +30,21 @@ struct SettingsView: View {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            // MARK: - Безопасность
 | 
					            // MARK: - Безопасность
 | 
				
			||||||
            Section(header: Text(NSLocalizedString("Безопасность", comment: ""))) {
 | 
					            Section(header: Text(NSLocalizedString("Безопасность", comment: ""))) {
 | 
				
			||||||
 | 
					                NavigationLink(destination: EditPrivacyView()) {
 | 
				
			||||||
 | 
					                    Label(NSLocalizedString("Конфиденциальность", comment: ""), systemImage: "lock.fill")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                NavigationLink(destination: ChangePasswordView()) {
 | 
					                NavigationLink(destination: ChangePasswordView()) {
 | 
				
			||||||
                    Label(NSLocalizedString("Сменить пароль", comment: ""), systemImage: "key")
 | 
					                    Label(NSLocalizedString("Сменить пароль", comment: ""), systemImage: "key")
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                NavigationLink(destination: Text("Заглушка: Двухфакторная аутентификация")) {
 | 
					 | 
				
			||||||
                    Label("Двухфакторная аутентификация", systemImage: "lock.shield")
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                NavigationLink(destination: ActiveSessionsView()) {
 | 
					                NavigationLink(destination: ActiveSessionsView()) {
 | 
				
			||||||
                    Label(NSLocalizedString("Активные сессии", comment: ""), systemImage: "iphone")
 | 
					                    Label(NSLocalizedString("Активные сессии", comment: ""), systemImage: "iphone")
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					                NavigationLink(isActive: $isSecurityActive) {
 | 
				
			||||||
 | 
					                    SecuritySettingsView(viewModel: viewModel)
 | 
				
			||||||
 | 
					                } label: {
 | 
				
			||||||
 | 
					                    Label(NSLocalizedString("Безопасность", comment: ""), systemImage: "lock.shield")
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // MARK: - Приложение
 | 
					            // MARK: - Приложение
 | 
				
			||||||
@ -125,6 +132,12 @@ struct SettingsView: View {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        .navigationTitle("Настройки")
 | 
					        .navigationTitle("Настройки")
 | 
				
			||||||
 | 
					        .onAppear {
 | 
				
			||||||
 | 
					            handleTwoFactorOnboardingIfNeeded()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .onChange(of: viewModel.onboardingDestination) { _ in
 | 
				
			||||||
 | 
					            handleTwoFactorOnboardingIfNeeded()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    private func openLanguageSettings() {
 | 
					    private func openLanguageSettings() {
 | 
				
			||||||
@ -165,3 +178,13 @@ struct SettingsView: View {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private extension SettingsView {
 | 
				
			||||||
 | 
					    func handleTwoFactorOnboardingIfNeeded() {
 | 
				
			||||||
 | 
					        guard viewModel.onboardingDestination == .securitySettings else { return }
 | 
				
			||||||
 | 
					        guard !isSecurityActive else { return }
 | 
				
			||||||
 | 
					        DispatchQueue.main.async {
 | 
				
			||||||
 | 
					            isSecurityActive = true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user