Разные аккаунты с разным контентом, управление настройками пользователей

Внизу я добавил изображение текущей структуры моего приложения и текущий код для включенных классов данных/сущностей.

На данный момент в моем приложении пользователь вставляет URL-адрес и код во фрагмент входа, нажимая кнопку «Сохранить», начинается запрос на получение токена. В случае успеха токен передается другим запросам для получения данных категорий. Различные категории, которые я получаю из ответа, затем отображаются в recyclerview. По клику на категорию пользователь попадает на фильмы/сериалы по жанру Fragment, там у меня есть еще один recyclerview со списком фильмов или сериалов.

Когда запрос токена успешен, URL-адрес и код также отправляются в класс данных (сущность) с именем AccountData, дополнительно имеется уникальная строка, составленная из URL-адреса и кода, которая работает как первичный ключ. AccountData отображается в recyclerview во фрагменте управления учетной записью, который является начальным экраном приложения. Теперь я хочу дать пользователю возможность выбирать для каждой учетной записи категории, которые он хочет показать. Имея возможность изменить свои предпочтения каждый раз, когда он хочет. Например:

AccountA имеет 10 категорий фильмов, пользователь хочет показать только 5 из них. AccountB имеет 15 категорий фильмов, пользователь хочет показать только 6 из них.

Моя идея состоит в том, чтобы создать новый фрагмент, MovieCategorySelectFragment или около того, где пользователь может щелкнуть категории, которые он хочет, передав выбранные категории во фрагмент категорий фильмов, например, в список избранного. Для реализации этого я думаю о Room. Итак, я сделал класс данных MovieCategory сущностью, используя «Id» в качестве первичного ключа, а затем, учитывая, что это отношение «один ко многим» (надеюсь, я прав), я добавил первичный ключ из сущности AccountData в Сущность MovieCategory. Я сделал строку обнуляемой -> val accountData: String?, чтобы не получать ошибку NullpointerException.

Но теперь я застрял, было бы лучше создать новый класс/сущность данных, назвав его f.e. SelectedMovieCategory и передать ему выбранный элемент/категорию (из MovieCategorySelectFragment, который не является частью базы данных) и использовать базу данных комнаты, а затем отобразить выбранные категории во фрагменте категорий фильмов. Или я должен сделать запрос на категории и сохранить их сразу в рум-базе и обрабатывать потом процесс выбора?

И, наконец, в обоих методах, как я могу передать первичный ключ из AccountData в MovieCategory? Иначе между ними нет отношений? Должен ли я создать функцию в Дао, чтобы справиться с этим?

В конце фрагмента управления учетными записями пользователь должен иметь возможность щелкнуть учетную запись, которую он хочет загрузить, загрузив для каждой учетной записи только те категории, которые он выбрал ранее. С возможностью изменить свои предпочтения в MovieCategorySelectFragment и добавить или удалить некоторые категории из своего «списка избранного».

Надеюсь, кто-то может помочь мне найти лучший и самый простой способ справиться с этим.

Разные аккаунты с разным контентом, управление настройками пользователей

Это классы данных:

data class MovieCategoryResponse(
    val js: List<MovieCategory>
)

@Entity
@Parcelize
data class MovieCategory(
    @PrimaryKey(autoGenerate = false)
    val id: String,
    val number: Int,
    val title: String,
    
    val accountData: String? 
) : Parcelable


@Entity
data class AccountData(
    val url: String,
    val code: String,
    @PrimaryKey(autoGenerate = false)
    val totalAccountData: String
)

Разные аккаунты с разным контентом, управление настройками пользователей

0
0
74
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Похоже, вам нужны учетные записи, категории фильмов, категории сериалов и средства связывания/связывания учетных записей с рядом категорий фильмов и, возможно, категорий сериалов, а также возможность ограничить количество перечисленных категорий фильмов в соответствии с предпочтениями учетных записей, которые могут меняться.

Решением может быть отношение «многие-многие» между учетной записью и категориями фильмов (и, возможно, категориями сериалов).

Однако, прежде чем двигаться дальше, вы говорите:

Итак, я сделал класс данных MovieCategory сущностью, используя «Id» в качестве первичного ключа, а затем, учитывая, что это отношение «один ко многим» (надеюсь, я прав), я добавил первичный ключ из сущности AccountData в Сущность MovieCategory.

По поводу вашей надежды. Я считаю, что вы ошибаетесь. Есть 3 типа отношений: -

  1. 1-1, где каждая строка в таблице будет иметь средства уникальной идентификации строки, обычно через один столбец, в другой таблице (если подходит отдельная таблица (может быть достаточно одной таблицы)). Отношения 1-1 обычно не обслуживаются с помощью таблиц.

  2. 1-many, где строка в родительской таблице (учетная запись) может иметь много дочерних элементов (категория фильмов), НО категория фильмов может иметь только 1 родителя. В таком случае столбец в категории фильмов будет содержать значение, которое однозначно идентифицирует родителя.

  3. многие-многие расширенное 1-М, которое позволяет каждой стороне относиться к любому числу другой стороны. Таким образом, учетная запись может относиться ко многим категориям фильмов, к которым могут относиться другие учетные записи. Типичное решение состоит в том, чтобы иметь промежуточную таблицу с двумя основными столбцами. Один хранит значение, которое однозначно идентифицирует одну сторону связи, а другой хранит значение, которое однозначно идентифицирует другую сторону. Обычно 2 столбца предназначены для первичного ключа.

    • такая промежуточная таблица имеет множество терминов для описания такой таблицы, таких как ассоциативная таблица, таблица сопоставления, справочная таблица....
  • Обратите внимание, как выделен идентификатор. Простое создание столбца с именем id в таблице (Entity) не создает отношения, а только поддерживает возможность установления отношений.

У вас может возникнуть проблема с отметкой флажков для отношения «многие-многие» и, следовательно, для дополнительной таблицы (2, если учетная запись-secriescategories).

В этой таблице будет столбец для значения, которое однозначно идентифицирует строку accountData (totalAccountData).

  • поскольку totalAccountData является первичным ключом (т.е. он помечен @PrimaryKey) и что PrimaryKey неявно уникален

В таблице будет второй столбец для столбца идентификатора movieCategory.

Итак, вы могли бы начать с

@Entity
data class AccountMovieMap(
    val accountDataMap: String,
    val movieCategoryMap: String
) 

Однако нет PrimaryKey, для которого требуется помещение, НО аннотация @PrimaryKey применяется только к одному столбцу. Если бы использовался любой из них, то из-за неявной уникальности отношение было бы ограничено 1-множеством. Требуется составной (несколько столбцов/значений) первичный ключ, который обеспечивает уникальность в соответствии с объединенными значениями. Для указания составного PrimaryKey в Room используется параметр primaryKeys аннотации @Entity.

Таким образом, AccountMovieMap становится: -

@Entity(
    primaryKeys = ["accountDataMap","movieCategoryMap"]
)
data class AccountMovieMap(
    val accountDataMap: String,
    val movieCategoryMap: String
)

В нынешнем виде существует потенциальная проблема с вышеизложенным, поскольку можно вставить данные в один или оба столбца, которые не являются значением в соответствующей таблице. То есть целостности отношений в такой ситуации не существует.

SQLite и, следовательно, Room (как и многие реляционные базы данных) обеспечивают соблюдение ссылочной целостности. SQLite делает это с помощью предложений ForeignKey. Room использует параметр foreignKeys аннотации @Entity для предоставления списка ForeignKeys.

  • В дополнение к обеспечению ссылочной целостности SQlite имеет 2 пункта ON DELETE и ON UPDATE, которые помогают поддерживать ссылочную целостность (в зависимости от указанного действия, наиболее полезным из которых является CASCADE, который разрешает изменения, которые могут нарушить ссылочную целостность, применяя изменения к родительскому элементу для дочерних элементов). ).

  • Room также предупредит, если индекс не существует там, где, по его мнению, следует, например. warning: movieCategoryMap column references a foreign key but it is not part of an index. This may trigger full table scans whenever parent table is modified so you are highly advised to create an index that covers this column. Таким образом, аннотацию @ColumnInfo можно использовать для добавления индекса в столбец movieCategoryMap.

Таким образом, AccountMovieMap может быть полнее: -

@Entity(
    primaryKeys = ["accountDataMap","movieCategoryMap"]
    , foreignKeys = [
        ForeignKey(
            entity = AccountData::class,
            parentColumns = ["totalAccountData"],
            childColumns = ["accountDataMap"],
            /* Optional but helps to maintain Referential Integrity */
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = MovieCategory::class,
            parentColumns = ["id"],
            childColumns = ["movieCategoryMap"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class AccountMovieMap(
    val accountDataMap: String,
    @ColumnInfo(index = true)
    val movieCategoryMap: String
)

Чтобы добавить (вставить) строки, которые вы могли бы иметь/использовать (в аннотированном классе @Dao): -

@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(accountMovieMap: AccountMovieMap)
  • отмечая, что во избежание конфликтов ссылочной целостности необходимо существование ссылок/сопоставления accountData и MovieCategory, на которые ссылается/сопоставляется.

Поскольку вы хотите извлечь MovieCategories из AccountData, вам нужен POJO, который имеет AccountData со списком MovieCategory.

Это должно быть:-

data class AccountWithMovieCategoryList(
    @Embedded
    val accountData: AccountData,
    @Relation(
        entity = MovieCategory::class,
        parentColumn = "totalAccountData", /* The column referenced in the @Embedded */ 
        entityColumn = "id", /* The column referenced in the @Relation (MovieCategory) */
        /* The mapping table */
        associateBy = (
                Junction(
                    value = AccountMovieMap::class, /* The @Entity annotated class for the mapping table */
                    parentColumn = "accountDataMap", /* the column in the mapping table that references the @Embedded */
                    entityColumn = "movieCategoryMap" /* the column in the mapping table that references the @Relation */
                )
                )
    )
    val movieCategoryList: List<MovieCategory>
)

Ниже может быть функция в аннотированном интерфейсе @Dao, которая извлекает AccountWithMovieCategoryList для данной учетной записи:

@Transaction
@Query("SELECT * FROM accountData WHERE totalAccountData=:totalAccountData")
fun getAnAccountWithMovieCategoryList(totalAccountData: String): List<AccountWithMovieCategoryList>

Однако Room будет получать ВСЕ категории MovieCategories, но вы хотите иметь возможность указать ОГРАНИЧЕННОЕ количество категорий MovieCategories для учетной записи, поэтому требуется средство для переопределения методологии Room для получения ВСЕХ сопоставленных/связанных объектов.

Чтобы облегчить это, можно использовать функцию с телом, чтобы а) получить соответствующие данные учетной записи и б) затем получить список MovieCategory в соответствии с учетной записью через таблицу сопоставления с указанным LIMIT. Таким образом, 2 функции @Query для выполнения 2 вызываются всеобъемлющей функцией.

Итак, чтобы получить AccountData: -

@Query("SELECT * FROM accountData WHERE totalAccountData=:totalAccountData")
fun getSingleAccount(totalAccountData: String): AccountData

А затем, чтобы получить ограниченные MovieCategories для AccountData через (JOIN) таблицу сопоставления: -

@Query("SELECT movieCategory.* FROM accountMovieMap JOIN movieCategory ON accountMovieMap.MovieCategoryMap = movieCategory.id WHERE accountMovieMap.accountDataMap=:totalAccountData LIMIT :limit")
fun getLimitedMovieCategoriesForAnAccount(totalAccountData: String,limit: Int): List<MovieCategory>

И чтобы собрать все это вместе, то есть всеобъемлющую функцию: -

@Transaction
@Query("")
fun getAccountWithLimitedMovieCategoryList(totalAccountData: String,categoryLimit: Int): AccountWithMovieCategoryList {
    return AccountWithMovieCategoryList(
        getSingleAccount(totalAccountData),
        getLimitedMovieCategoriesForAnAccount(totalAccountData,categoryLimit)
    )
}
  • Обратите внимание, что приведенный выше код был только скомпилирован (поэтому обработка комнаты не видит проблем), поэтому это код в принципе.

  • Вы говорите «Лучший», это мнение и не лучшее, поскольку лучшим способом было бы использовать более эффективную обработку первичных ключей INTEGER в SQLite.

  • categoryLimit, то есть переданный Int может быть динамически выбран или сохранен через пользовательский интерфейс и, таким образом, сохранен. Его можно сохранить в AccountData, добавив подходящий столбец, или его можно сохранить в другом месте. AccountData может показаться самым простым и наиболее подходящим, если только не ожидается, что должно существовать много таких предпочтений на основе учетной записи.

Если, например, в AccountData был добавлен дополнительный столбец, например. :-

@Entity
data class AccountData(
    val url: String,
    val code: String,
    @PrimaryKey(autoGenerate = false)
    val totalAccountData: String,
    val movieCategoryLimit: Int /*<<<<< to store LIMIT preference */
)

Скорее всего, потребуется средство изменения предела, например следующее в аннотированном интерфейсе @Dao: -

@Query("UPDATE accountData SET movieCategoryLimit=:newLimit WHERE totalAccountData=:totalAccountData")
fun changeMovieCategoryLimit(totalAccountData: String, newLimit: Int)

Тонкое изменение в функции getAccountWithLimitedMovieCategoryList и LIMIT в соответствии с предпочтениями: -

@Transaction
@Query("")
fun getAccountWithLimitedMovieCategoryList(totalAccountData: String,categoryLimit: Int): AccountWithMovieCategoryList {
    val accountData = getSingleAccount(totalAccountData)
    return AccountWithMovieCategoryList(
        accountData,
        getLimitedMovieCategoriesForAnAccount(totalAccountData,accountData.movieCategoryLimit)
    )
}
  • т. е. вместо того, чтобы использовать полученные данные AccountData напрямую, они извлекаются в val, затем val используется для предоставления AccountData, а затем снова для предоставления значения для LIMIT.

Дополнительный

Согласно комментарию

.... Разве это не отношение один ко многим? ....

Тогда для 1-M, как объяснялось ранее, MovieCategory должен иметь столбец для хранения уникального столбца. Я думаю, что вы индексируете val accountData: String? быть за.

  • ? никогда не должен быть нулевым (сирота, которая в принципе бесполезна). В идеале, поскольку использование столбца подразумевает выбор через этот столбец, столбец должен быть проиндексирован. Поскольку предполагаемое использование является внешним ключом, то, хотя и не требуется, определение его как внешнего и обеспечение ссылочной целостности имеет смысл (а также идет к описанию/комментарию столбца как внешнего ключа). Тогда класс MovieCategory может быть

:-

/* MovieCategory modified for 1-m (1 account many categories)*/
@Entity(
    foreignKeys = [
        ForeignKey(
            entity = AccountData::class,
            parentColumns = ["totalAccountData"],
            childColumns = ["accountData"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE,
        )
    ]
)
@Parcelize
data class MovieCategory(
    @PrimaryKey(autoGenerate = false)
    val id: String,
    val number: Int,
    val title: String,
    @ColumnInfo(index = true) /* added as likely to used for selecting rows */
    val accountData: String /* should NEVER by null */
) : Parcelable

Предполагая, что AccountData включает в себя movieCategoryLimit (как объяснено выше), тогда потребуется POJO для получения учетной записи и ОГРАНИЧЕННЫЙ список MovieCategories (немного отличающийся от эквивалента m-m), который может быть: -

data class AccountDataWithMovieCategories(
    @Embedded
    val accountData: AccountData,
    @Relation(
        entity = MovieCategory::class,
        parentColumn = "totalAccountData",
        entityColumn = "accountData"
    )
    val movieCategories: List<MovieCategory>
)
  • т. е. таблица ассоциаций не задействована

Та же проблема заключается в том, что Room будет получать ВСЕ категории фильмов. Итак, следующее не то, что вы хотите: -

/* Note will get ALL MovieCategories (even with join and LIMIT) */
@Transaction
@Query("SELECT * FROM accountData WHERE totalAccountData=:totalAccountData")
fun getAccountWithMovieCategories(totalAccountData: String): List<AccountWithMovieCategoryList>

Скорее вы хотите иметь: -

@Query("SELECT * FROM movieCategory WHERE accountData=:totalAccountData LIMIT :limit")
fun getLimitedMoviesCategoriesPerAccount(totalAccountData: String, limit: Int): List<MovieCategory>
  • то есть прямой доступ к MovieCategory, а не через JOIN в таблице ассоциаций в версии m-m

И, наконец, всеобъемлющая функция:

@Transaction
@Query("")
fun getAccountWithLimitedMovieCategories(totalAccountData: String, limit: Int): AccountDataWithMovieCategories {
    val accountData = getSingleAccount(totalAccountData)
    return AccountDataWithMovieCategories(
        accountData,
        getLimitedMovieCategoriesForAnAccount(
            accountData.totalAccountData,
            accountData.movieCategoryLimit /* the limit as stored in the account */
        )
    )
}

Здравствуйте, и большое спасибо за этот подробный ответ, в настоящее время я на работе, поэтому мне нужно перечитать ответ более точно после работы (через 2 часа :-)) Одна вещь, учетная запись может иметь много категорий ... но каждая из них категории могут относиться только к одной учетной записи. Итак, учетная запись A имеет MovieCategoriesA, SeriesCategoriesA, учетная запись D имеет MovieCategoryiesD, SeriesCategoryD... категорию учетной записи A, которая не может быть также частью учетной записи D. Разве это не отношение "один ко многим"? Возможно, остальная часть вашего ответа относится к этому, тогда извините - как я уже сказал, я внимательно прочитаю после работы :)

Alex Mutschl 26.01.2023 12:01

@AlexMutschl, если 1-m, то для категорий потребуется либо столбец для хранения уникального столбца для учетной записи, либо можно использовать много-много. Однако, если категории в основном повторяются, как это видно из ваших диаграмм (например, в учетной записи a есть романтика, в учетной записи b есть романтика b ....), то это нарушает то, что называется нормализацией (т. . Когда с m-m все 3 аккаунта могут просто указывать на один роман и так далее.

MikeT 26.01.2023 12:17

@AlexMutschl добавил к вопросу 1-метровую версию как дополнительную

MikeT 26.01.2023 13:04

используя m-m для примера вашего романа, учетная запись A + B относится к одной и той же категории романтики (romanceA = RomanceB), я правильно понимаю? Но что, если список фильмов внутри категории отличается? Разве это не имеет значения для базы данных? Обычно в приложении каждая категория относится только к одной учетной записи (даже если имя такое же или идентификатор одинаковый), и одна учетная запись может иметь разные категории. Добавил еще одну схему, может первая мне немного не повезло, опишите проблему, извините. Вторая строка - категории ;-)

Alex Mutschl 26.01.2023 13:33

Теоретически могло случиться так, что пользователь добавляет 2 аккаунта, которые получают те же данные, что и по категориям, что и по списку фильмов в нем, но и в этом случае я хочу, чтобы выбор категории пользователя относился только к тому аккаунту, с которым данные были загружены/загружены; например это может произойти, когда URL-адрес тот же, но с другим кодом. Но, как уже говорилось, обычно каждая категория имеет отношение только к учетной записи, которая использовалась для получения данных. @MikeT о, отлично, спасибо, посмотрю :)

Alex Mutschl 26.01.2023 13:33

Кажется, мне удалось реализовать тот процесс, который я хотел. Я быстро протестировал его, и он работал так, как должен. С "лимитом" я обращался немного иначе, так как он был не совсем ясен для меня. Я добавил флажок в список категорий, отмеченные элементы я «отправляю» в другой фрагмент. Чтобы сохранить состояние проверенных категорий, я обновил текущий загруженный список и перезагрузил его. У меня сейчас семейные выходные, поэтому нет времени что-то выкладывать. Я сделаю это на следующей неделе, надеюсь, у вас будет немного времени, чтобы взглянуть на него. Еще раз спасибо за вашу помощь, это один из лучших и подробных ответов, которые я читал здесь.

Alex Mutschl 27.01.2023 17:02

@МайкТ

Извините за поздний ответ ... (я использую ответ, потому что комментарий слишком длинный, и редактирование моего вопроса было бы грязным) Я почти смог получить все, что хотел, используя этот способ (пример категорий серий):

Я сохранил извлеченные категории (используя модификацию) в новом POJO с именем SeriesCategory:

@Entity(
    foreignKeys = [
        ForeignKey(
            entity = AccountData::class,
            parentColumns = ["totalAccountData"],
            childColumns = ["accountData"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE,
        )
    ]
)
@Parcelize
data class SeriesCategory(
    @PrimaryKey(autoGenerate = false)
    val id: String,
    val title: String,
    @ColumnInfo(index = true)
    var accountData: String,
    var favorite: Boolean,
    val idByAccountData: String
) : Parcelable

с использованием...

@Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertSeriesCategory(seriescategory: List<SeriesCategory>)

                val mappedSeriesCategoryList =   seriescatresponse.js.map { SeriesCategorybyAccount(it.id, it.title, totalAccountData, isFavorite, "${it.id}$totalAccountData") }
               lifecycleScope.launch {
                   mappedSeriesCategoryList.forEach { dao.insertSeriesCategory(mappedSeriesCategoryList) }
               }

В Dao & SeriesCategoryFragment я использую следующий код, чтобы получить все категории из этой конкретной учетной записи:

@Query("SELECT * FROM seriesCategory WHERE accountData=:totalAccountData")
    fun getSeriesCategoriesPerAccount(totalAccountData: String): LiveData<List<SeriesCategory>>

        viewModel.getSeriesCategoryByAccount(totalAccountData, [email protected]()).observe(viewLifecycleOwner) {
            seriesCategoryAdapter.submitList(it)
        }

Поскольку я использую флажок в SeriesCategoryFragment-recyclerview, я управлял флажком в адаптере и использовал следующий код в Dao & SelectedSeriesCategoryFragment:

@Query("SELECT * FROM seriesCategory WHERE accountData=:totalAccountData AND favorite = 1")
    fun getSelectedSeriesCategoriesPerAccount(totalAccountData: String): LiveData<List<SeriesCategory>>

        viewModel.getSelectedSeriesCategoryByAccount(totalAccountData, [email protected]()).observe(viewLifecycleOwner) {
            selectedSeriesCategoryAdapter.submitList(it)
        }

Это работает нормально, когда в SeriesCategoryFragment установлен флажок (favorite = true), категория «отправляется» в SelectedSeriesCategoryFragment, если она не отмечена (favorite = false), она удаляется.

Но на данный момент я не могу сохранить это в базе данных моей комнаты. Итак, когда у меня есть, например, следующие категории: КатегорияA, КатегорияB, КатегорияC, КатегорияD

Пользователь проверяет CategoryA и CategoryC с помощью флажка, затем эти две категории отображаются в представлении SelectedSeriesCategoryFragment-recyclerview. Когда он снимает флажок, категория удаляется. Все в порядке.

В том же кликлистере я также использую @Update с (it = SeriesCategory):

viewModel.updateSeriesCategory(it, [email protected]())

При перезапуске приложения база данных показывает мне избранное = 1 (для истины) в категориях, которые были проверены до перезапуска. Но флажки не отмечены - @Query, который я использую в SelectedSeriesCategoryFragment (см. выше), похоже, больше не работает -> означает, что он пуст. При втором перезапуске база данных показывает все категории как избранные = 0 (ложь) - вероятно, потому, что флажок был пуст до второго перезапуска. Итак, как я понимаю, идея, которую я имею, должна работать, но только если отмеченные флажки остаются отмеченными и после перезапуска. Могу ли я справиться с этим как-то с комнатой?

Или это полностью проблема, связанная с recyclerview?

В итоге мой Viewholder в адаптере выглядит так:

inner class ViewHolder(val binding: RvItemSeriescategoryBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(category: SeriesCategory) {
            binding.apply {
                rvItemSeriescategory.text = category.title
                checkboxSeriescategory.isChecked = category.favorite
                checkboxSeriescategory.setOnClickListener {
                        if (checkboxSeriescategory.isChecked) {
                        category.favorite = true
                            onClickListener.onClick(category)
                        }
                    if (!checkboxSeriescategory.isChecked)
                        category.favorite = false
                        onClickListener.onClick(category)
                }
                if (category.favorite == true){
                    checkboxSeriescategory.isChecked = true
                }
            }
        }
    }

Другие вопросы по теме