Разработка мобильного приложения "Менеджер подписок"

Осуществление разработки мобильного приложения "Менеджер подписок", написанного на языке программирования Kotlin с использованием объектно-ориентированного подхода. Описание разработанного мобильного приложения и результатов тестирования программы.

Рубрика Программирование, компьютеры и кибернетика
Вид курсовая работа
Язык русский
Дата добавления 19.05.2023
Размер файла 951,5 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

Размещено на http://www.allbest.ru/

Кафедра ПОВТиАС

Курсовая работа

по дисциплине «Объектно-ориентированное программирование»

на тему: «Разработка мобильного приложения “Менеджер подписок”»

Выполнил: Хасбулатов М.В.,

ст. 2 курса, группы У133

Проверил: Кобзаренко Д.Н.

Факультет Компьютерных технологий, вычислительной техники и энергетики

Кафедра Программное обеспечение вычислительной техники и автоматизированных систем

Направление 09.03.04 Программная инженерия

Профиль Разработка программно-информационных систем

З А Д А Н И Е

на курсовую работу

1. Тема курсовой работы Разработка мобильного приложения “Менеджер подписок”

2. Дата выдачи задания 16.02.2023_____________________

3. Дата сдачи курсовой работы на кафедру _22.05.2023_____________________

4. Цель курсовой работы Составить программу, позволяющую регулировать подписки на сервисах

5. Исходные данные (технические; экономические, организационные и другие требования и решения для выполнения проекта)

ОС - Android, Язык программирования - Kotlin Android Framework IDE Android Studio

6. Перечень разделов, подлежащих разработке в курсовой работе

6.1. Введение

Теоретическая часть

Проектирование программной системы

Разработка программы

Результаты тестирования программы

Заключение

6.2. Список использованных источников

1. Andrew Bailey, David Greenhalgh and Josh Skeen. Kotlin Programming: The Big Nerd Ranch Guide, 2nd Edition. Big Nerd Ranch, LLC, 2021 - 909 c.

2. Bryan Sills, Brian Gardner, Kristin Marsicano and Chris Stewart. Android Programming: The Big Nerd Ranch Guide, 5th Edition. Big Nerd Ranch, LLC, 2022 - 606 c.

7. Перечень разрабатываемого графического материала:

8. Календарный план-график выполнения работ по проектированию

Содержание работ

Объём работ в %

Контрольные сроки

Введение

5%

16.02.23 - 25.02.23

Анализ задания по курсовой работе

30%

25.02.23 - 15.03.23

Разработка и описание программы

40%

15.03.23 - 20.04.23

Тестирование программы

20%

20.04.23 - 12.05.23

Заключение

5%

12.05.23 -17.05.23

Задание выдал руководитель (Кобзаренко Д. Н.)

Задание принял студент (_Хасбулатов М.В.)

Аннотация

Данная курсовая работа нацелена на разработку мобильного приложения «Менеджер подписок», написанной на языке программирования Kotlin с использованием объектно-ориентированного подхода.

Пояснительная записка содержит описание программы и алгоритма, результаты тестирования программы, а также включает в себя: страниц - 51, приложений - 1, рисунков - 15.

Ключевые слова: разработка приложения, Kotlin, ООП, Android, подписка.

Содержание

Введение

  • 1. Анализ задания по курсовой работе
  • 2. Разработка и описание программы
  • 3. Тестирование программы
  • Заключение
  • Список использованной литературы
  • Приложение

Введение

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

Пояснительная записка содержит следующие разделы:

1. Анализ задания по курсовой работе

2. Описание алгоритма работы программы

3. Тестирование программы

В разделе «Анализ задания по курсовой работе» описывается суть курсовой задачи, а также способы её решения, выбранный язык программирования, различные варианты организации данных.

В разделе «Описание алгоритма работы программы» дается подробное описание принципа работы программы, способов объявления глобальных переменных, списков, а также внутренней иерархии проекта. Также в этой главе приводится диаграмма классов, для наглядности работы программы.

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

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

1. Анализ задания по курсовой работе

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

Учитывая заявленную задачу, можно привести требования к программному продукту.

· Пользователь входит в приложение, и перед ним появляется возможность добавить подписку.

· Информация о подписке содержит название и её цену.

· Пользователю отображается сумма его подписок.

· Также есть возможность ввести свою заработную плату.

· Помимо всего этого, наглядно отображается процент зарплаты, который уходит на подписки.

Проведя анализ курсового задания, можно составить ряд требований к разработке программного продукта, а именно:

1. Операционная система

2. Язык программирования

3. Среда разработки

Целевой операционной системой для игры была выбрана «Android», так как именно эта ОС является самой распространенной среди пользователей мобильных телефонов и смартфонов.

Язык программирования Kotlin сравнительно очень молодой, но обревший значительную популярность в мобильной разработке. И это действительно оправдано, Kotlin по праву является наследником Java, так как и задумывался он разработчиками, которые были недовольны проблемами в Java. Kotlin - статически типизированный, объектно-ориентированный язык программирования, который работает поверх JVM и также может конвертироваться в JavaScript. Язык очень хорошо и лаконично реализует принципы ООП. Также он является доминирующим языком программирования, в разработке продуктов на Android. Язык был выбран для курсовой, так как является лучшим из вариантов для написания программы на Android OS.

Перед созданием мобильного приложения следует определиться с архитектурой. Google, являясь разработчиками Android OS и Android Framework, рекомендуют использовать паттерн MVVM (Model-View-ViewModel), его и используем.

Рисунок 1 демонстрирует отношения между компонентами архитектуры.

Рисунок 1 Взаимоотношение компонентов в MVVM

1. Слой View(Представление) - содержит все то, что видит и с чем взаимодействует пользователь. Тут нет никакой логики приложения. Это лишь интерфейс, который отображает данные.

2. Слой ViewModel(Модель представления) - этот компонент связывает модель и представление. Тут могут временно храниться данные, которые будут перемещаться по слоям от представления до модели. Также класс ViewModel, который предназначен для реализации MVVM на Android, позволяет избегать реконфигурации приложения, сохраняя временные данные.

3. Слой Model(Модель) - это слой бизнес-данных и он, согласно Clean Architecture(Чистая архитектура), может содержать базу данных, репозиторий и класс бизнес-логики.

Для разработки приложения на Android требуется среда разработки. Тут выбор за Android Studio. Использование этой IDE является бесплатным. Также присутствует большое количество функций, упрощающие разработку.

2. Разработка и описание программы

Так как архитектурой приложения было выбран паттерн MVVM, то и структура проекта была построена соответствующе:

1. Модуль “model” содержит в себе базу данных, которая реализована с помощью с библиотеки Room из Android Framework. Тут также находится класс бизнес-логики “Subscription” и класс, который реализует паттерн репозиторий “SubscriptionRepository”.

2. Модуль “ui” отвечает за графически интерфейс приложения. Классы, хранящиеся тут, отвечают за каждый фрагмент в приложении и отображении данных в этих фрагментах.

3. Модуль “viewModels” является реализацией ViewModel из MVVM. Все классы, которые находятся в данном модуле, выполняют функции слоя ViewModel, а конкретнее, отвечают за взаимосвязь между моделью и представлением.

На рисунке 2 показано то, как модули программы расположены в проекте.

Рисунок 2 Модули проекта

На рисунках 3-5 представлены диаграммы классов всех трех модулей, наглядно показывающие отношения классов между собой.

Рисунок 3 Диаграмма классов модуля “model”

Рисунок 4 Диаграмма классов модуля “ui”

Рисунок 5 Диаграмма классов модуля “viewModels”

Принцип работы программы состоит в следующем. Все происходит в Activity - это такой класс в Android Framework, который входит в число фундаментальных классов, являющихся основой всего приложения. В самом Activity идет установка макета, который содержит в себе BottomNavigationView (Нижняя панель навигации) и FragmentContainerView (Контейнер фрагментов). Далее рассмотрим то, что происходит внутри FragmentContainerView:

· FragmentContainerView при первом запуске приложения отображает фрагмент по умолчанию. К слову, фрагментом или Fragment в Android Framework называют класс, который представляет собой повторно использующуюся часть пользовательского интерфейса, у него есть свой собственный жизненный цикл и существовать он может исключительно в Activity, либо в другом фрагменте.

· Когда запускается приложение, FragmentContainerView показывает SubscriptionListFragment. Этот фрагмент в методе onCreateView() привязывается к своему макету и раздувает его, попутно устанавливая для представлений в макете расположение из SubscriptionMargin

· В нижней части приложения есть BottomNavigationView. Это представление позволяет перейти на один из трех фрагментов: SubscriptionListFragment, CreateSubscriptionFragment, ProfileFragment.

· CreateSubscriptionFragment, появляется при нажатии на соответствующее представление BottomNavigationView. Когда появляется CreateSubscriptionFragment в методе onCreate() создается экземпляр класса Subscription со значениями по умолчанию. Далее в методе onViewCreated() происходит определений функций представлений в макете CreateSubscriptionFragment. Это изменение существующего экземпляра Subscription и его возможное сохранение.

· Также есть ProfileFragment. В методе onViewCreated() представление userProfit вызывает лямбда-функцию, которая позволяет сохранять вписываемые значения. А приватный метод uiState(), который вызывается в onViewCreated() обновляет интерфейс во время работы приложения и при взаимодействии пользователя с ним.

· Еще один фрагмент, UpdateSubscriptionFragment, можно вызвать, если в базе данных есть хотя бы одна подписка. Эта подписка отображается в SubscriptionListFragment. При нажатии на представление в SubscriptionListFragment в методе onViewCreated() срабатывает функция navigate(), которая создает UpdateSubscriptionFragment. Этот фрагмент работает точно также, как и CreateSubscriptionFragment. Только вместо экземпляра класса Subscription, здесь при переходе из SubscriptionListFragment, в методе navigate() передается уникальный ID подписки. И тут также происходит то, что происходит в методе onViewCreated() в CreateSubscriptionFragment.

Полный текст программы приведен в приложении 1.

3. Тестирование программы

Данный этап является проверкой программного кода на работоспособность.

Запустив программу, пользователь видит главное меню, показанное на рисунке 6.

Рисунок 6 Главное меню

Здесь отображаются все существующие подписки. Также есть снизу три кнопки. Та, что посередине переносит на интерфейс, который позволит добавить подписку (рис. 7). Кнопка с краю, покажет интерфейс, который отображает некоторые данные - сумма все подписок, заработную плату, процент от заработной платы на подписки, имя пользователя (рис. 8).

Рисунок 7 Добавление подписки

Как видно на картинке, здесь есть возможность ввести цену и название подписки, а после добавить ее.

Рисунок 8 Данные о подписках и пользователе

Данная страница приложения позволит нам наглядно понять сколько уходит на содержание наших подписок. Можно ввести размер заработной платы в блоке “Your Profit”.

Давайте теперь добавим подписку. На рисунке 9 будет показано добавление подписки.

Рисунок 9 Добавление подписки

После того, как мы введем нужную нам информацию о подписке, после нажатия кнопки она будет добавлена на главную страницу (рис. 10).

Рисунок 10 Отображение подписки

Давайте добавим еще несколько подписок (рис. 11).

Рисунок 11 Добавление нескольких подписок

Теперь у нас есть определенное количество подписок. Допустим, что наш заработок составляет пятьсот долларов в месяц, узнаем какой процент зарплаты уходит на содержание подписок (рисунки 12-13).

Рисунок 12 Сумма подписок. Без ввода зарплаты

Рисунок 13 Отображение процента. Ввод зарплаты

Как мы видим после ввода зарплаты, изменилось отображение процента, вместо нуля появилась цифра 6, а также кружочек прогресса, который при случае, если сумма всех подписок будет равна заработной плате, полностью закрасится.

Допустим Telegram решил увеличить цену на свою подписку вдвое. Тогда нам придется ее изенить, ибо данные будут некорректными. На главой странице, где отображаются все наши подписки (рис. 7), при нажатии на подписку пользователя перенесет на интерфейс, в котором у него будет возможность изменить данные (рис. 14).

Рисунок 14 Обновление данных. Часть 1

Также, если пользователь захочет удалить свою подписку, то у него есть такая возможность, при нажатии на соответсвующую кнопку.

Теперь, после нажатии на кнопку “Update”, нас перенесет на главную страницу и нам будут отображаться уже обновленные данные (рис. 15).

Рисунок 15 Обновление данных. Часть 2

Заключение

В результате курсовой работы было разработано приложение для учёта подписок. Также проанализировано курсовое задание, на основании которого были выведены основные предметные области и технологии, необходимые для выполнения работы. Описан принцип действия программы и её реализация. Было проведено тестирование разработанной программы на правильность работы.

Помимо этого, были рассмотрены такие предметные области как:

· Объектно-ориентированный метод программирования на языке Kotlin.

· Работа с Android Framework.

· Работа с IDE Android Studios.

· Разработка мобильных приложений.

При решении многих мелких проблем были использованы средства отладки, особенно удобно это реализовано в Android Studio, позволяя детально рассмотреть изменение информации в программе, после каждого действия, совершенного непосредственно в самом приложении, в тот или иной момент времени и своевременно найти и устранить неполадку.

Были улучшены знания в области объектно-ориентированного программирования, шаблонах проектирования, написания мобильных приложений. Большую роль в понимании данной темы оказали изучение лекционного материала, а также различные электронные ресурсы по языку Kotlin и Android Framework, также документация от Google дала дополнительное понимание процессов.

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

1. Andrew Bailey, David Greenhalgh and Josh Skeen. Kotlin Programming: The Big Nerd Ranch Guide, 2nd Edition. Big Nerd Ranch, LLC, 2021. 909 c.

2. Bryan Sills, Brian Gardner, Kristin Marsicano and Chris Stewart. Android Programming: The Big Nerd Ranch Guide, 5th Edition. Big Nerd Ranch, LLC, 2022. 606 c.

3. Kotlin | Введение. Режим доступа: https://metanit.com/kotlin/tutorial/1.1.php

4. Основы Android в Kotlin. Режим доступа: https://developer.android.com/courses/android-basics-kotlin/course

5. SQLite | Введение. Режим доступа: https://metanit.com/sql/sqlite/1.1.php

Приложение 1

Исходный код программы

@Entity(tableName = "subscription")

data class Subscription(

@PrimaryKey val id: UUID,

val title: String,

val price: Int,)

@Dao

interface SubscriptionDao {

@Query("SELECT * FROM subscription")

fun getSubscriptions(): Flow<List<Subscription>>

@Query("SELECT * FROM subscription WHERE id=(:id)")

suspend fun getSubscription(id: UUID): Subscription

@Update

suspend fun updateSubscription(subscription: Subscription)

@Insert

suspend fun addSubscription(subscription: Subscription)

@Query("DELETE FROM subscription WHERE id=(:id)")

suspend fun deleteSubscription(id: UUID)

@Query("SELECT sum(price) FROM subscription")

fun amountPrice(): Flow<Int>

}

@Database(entities = [Subscription::class], version = 2)

abstract class SubscriptionDatabase: RoomDatabase() {

abstract fun subscriptionDao(): SubscriptionDao

}

val migration_1_2 = object: Migration(1, 2){

override fun migrate(database: SupportSQLiteDatabase) {

with(database){

execSQL("CREATE TABLE subscription_backup(id BLOB NOT NULL, title TEXT NOT NULL, price INTEGER NOT NULL, PRIMARY KEY(id))")

execSQL("INSERT INTO subscription_backup(id, title, price) SELECT id, title, price FROM subscription")

execSQL("DROP TABLE subscription")

execSQL("ALTER TABLE subscription_backup RENAME TO subscription")

}

}

}

data class ProfilePreferences(

val fullName: String,

val profit: Int

)

class SubscriptionRepository @OptIn(DelicateCoroutinesApi::class)

private constructor(

context: Context,

private val dataStore: DataStore<Preferences>,

private val coroutineScope: CoroutineScope = GlobalScope

){

private val database = Room.databaseBuilder(

context,

SubscriptionDatabase::class.java,

SUBSCRIPTION_DATABASE

)

.addMigrations(migration_1_2)

.build()

fun getSubscriptions(): Flow<List<Subscription>> = database.subscriptionDao().getSubscriptions()

suspend fun getSubscription(subscriptionID: UUID) = database.subscriptionDao().getSubscription(subscriptionID)

fun updateSubscription(subscription: Subscription) {

coroutineScope.launch {

database.subscriptionDao().updateSubscription(subscription)

}

}

suspend fun addSubscription(subscription: Subscription) = database.subscriptionDao().addSubscription(subscription)

suspend fun deleteSubscription(id: UUID) = database.subscriptionDao().deleteSubscription(id)

fun amountPrice() = database.subscriptionDao().amountPrice()

val profilePreferencesFlow: Flow<ProfilePreferences> = dataStore.data

.catch { exception ->

if (exception is IOException) {

emit(emptyPreferences())

} else {

throw exception

}

}

.map { preferences ->

val fullName = preferences[FULL_NAME]?: "User"

val profit = preferences[PROFIT]?: 50

ProfilePreferences(fullName, profit)

}.distinctUntilChanged()

suspend fun updateFullName(fullName: String) {

dataStore.edit { preferences ->

preferences[FULL_NAME] = fullName

}

}

suspend fun updateProfit(profit: Int) {

dataStore.edit { preferences ->

preferences[PROFIT] = profit

}

}

companion object{

private val FULL_NAME = stringPreferencesKey("fullName")

private val PROFIT = intPreferencesKey("profit")

private var INSTANCE: SubscriptionRepository? = null

fun initialize(context: Context){

if (INSTANCE == null){

val dataStore = PreferenceDataStoreFactory.create {

context.preferencesDataStoreFile("profile")

}

INSTANCE = SubscriptionRepository(context, dataStore)

}

}

fun get(): SubscriptionRepository {

return INSTANCE?:

throw IllegalStateException("Repository must be initialized")

}

}

}

class CreateSubscriptionViewModel(subscriptionID: UUID): ViewModel() {

private val repository = SubscriptionRepository.get()

private var _subscription: MutableStateFlow<Subscription?> = MutableStateFlow(null)

val subscription: StateFlow<Subscription?> = _subscription.asStateFlow()

init {

viewModelScope.launch {

_subscription.value = repository.getSubscription(subscriptionID)

}

}

override fun onCleared() {

super.onCleared()

subscription.value?.let { subscription ->

repository.updateSubscription(subscription)

}

Log.d("ON_CLEARED","CURRENT SUBSCRIPTION - ${subscription.value?.id}")

}

fun updateSubscription(onUpdate: (Subscription) -> Subscription) {

_subscription.update { oldSub ->

oldSub?.let { onUpdate(it) }

}

}

suspend fun deleteSubscription(id: UUID) = repository.deleteSubscription(id)

}

class CreateSubscriptionViewModelFactory(

private val subscriptionID: UUID

): ViewModelProvider.Factory {

override fun <T: ViewModel> create(modelClass: Class<T>): T {

return CreateSubscriptionViewModel(subscriptionID) as T

}

}

class UpdateSubscriptionViewModel(subscriptionID: UUID): ViewModel() {

private val repository = SubscriptionRepository.get()

private var _subscription: MutableStateFlow<Subscription?> = MutableStateFlow(null)

val subscription: StateFlow<Subscription?> = _subscription.asStateFlow()

init {

viewModelScope.launch {

_subscription.value = repository.getSubscription(subscriptionID)

}

}

override fun onCleared() {

super.onCleared()

subscription.value?.let { subscription ->

repository.updateSubscription(subscription)

}

}

fun updateSubscription(onUpdate: (Subscription) -> Subscription) {

_subscription.update { oldSub ->

oldSub?.let { onUpdate(it) }

}

}

suspend fun deleteSubscription(id: UUID) = repository.deleteSubscription(id)

}

class UpdateSubscriptionViewModelFactory(

private val subscriptionID: UUID

): ViewModelProvider.Factory {

override fun <T: ViewModel> create(modelClass: Class<T>): T {

return UpdateSubscriptionViewModel(subscriptionID) as T

}

}

class SubscriptionListViewModel: ViewModel() {

private val repository = SubscriptionRepository.get()

private val _subscriptions: MutableStateFlow<List<Subscription>> = MutableStateFlow(emptyList())

val subscriptions: StateFlow<List<Subscription>>

get() = _subscriptions.asStateFlow()

init {

viewModelScope.launch {

repository.getSubscriptions().collect {

_subscriptions.value = it

}

}

}

suspend fun addSubscription(subscription: Subscription) {

repository.addSubscription(subscription)

}

override fun onCleared() {

super.onCleared()

Log.d(SUBSCRIPTION_LIST_VIEWMODEL, "ON CLEARED")

}

}

class ProfileFragmentViewModel: ViewModel() {

private val repository = SubscriptionRepository.get()

private val _uiState: MutableStateFlow<ProfileFragmentUiState> = MutableStateFlow(

ProfileFragmentUiState()

)

val uiState: StateFlow<ProfileFragmentUiState> = _uiState.asStateFlow()

init {

viewModelScope.launch {

repository.amountPrice().combine(repository.profilePreferencesFlow) { amountPrice, profilePreferences ->

ProfileFragmentUiState(

amountPrice = amountPrice,

profit = profilePreferences.profit,

fullName = profilePreferences.fullName)

}.collect { oldProfileFragmentUiState ->

_uiState.update { newProfileFragmentUiState ->

newProfileFragmentUiState.copy(

amountPrice = oldProfileFragmentUiState.amountPrice,

profit = oldProfileFragmentUiState.profit,

fullName = oldProfileFragmentUiState.fullName )}}}}

override fun onCleared() {

super.onCleared()

Log.d(PROFILE_FRAGMENT_VIEWMODEL, "ON CLEARED")

}

fun updateFullName(fullName: String) {

viewModelScope.launch {

repository.updateFullName(fullName)

}

}

fun updateProfit(profit: Int) {

viewModelScope.launch {

repository.updateProfit(profit)

}

}

fun spendOnSubscriptions(state: ProfileFragmentUiState): Int {

val profit = state.profit

val amount = state.amountPrice

return if (profit != 0) {

((amount / profit.toDouble()) * 100).toInt()

} else { 0 } } }

data class ProfileFragmentUiState(

val amountPrice: Int = 0,

val profit: Int = 0,

val fullName: String = "",)

class SubscriptionsListFragment: Fragment() {

private var _binding: SubscriptionListFragmentBinding? = null

private val binding

get() = checkNotNull(_binding){

"Cannot access binding because it is null."

}

private val subscriptionViewModel: SubscriptionListViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

Log.d("SIZE", "Size = ${subscriptionViewModel.subscriptions.value.size}")

}

override fun onCreateView(

inflater: LayoutInflater,

container: ViewGroup?,

savedInstanceState: Bundle?

): View {

_binding = SubscriptionListFragmentBinding.inflate(inflater, container, false)

binding.subscriptionRecyclerView.layoutManager = GridLayoutManager(context, 2)

binding.subscriptionRecyclerView.addItemDecoration(SubscriptionMargin())

return binding.root

}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

super.onViewCreated(view, savedInstanceState)

viewLifecycleOwner.lifecycleScope.launch {

viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {

subscriptionViewModel.subscriptions.collect{ subscriptions ->

binding.subscriptionRecyclerView.adapter = SubscriptionsAdapter(subscriptions) { subscriptionID ->

findNavController().navigate(

SubscriptionsListFragmentDirections.showUpdateSubscription(subscriptionID) ) } } } } }

override fun onDestroyView() {

super.onDestroyView()

_binding = null

}

}

class CreateSubscriptionFragment: Fragment() {

private var _binding: CreateSubscriptionFragmentBinding? = null

private val binding

get() = checkNotNull(_binding){

"Cannot access binding because it is null."

}

private val viewModelForAddSubscription: SubscriptionListViewModel by viewModels()

private val subscription = Subscription(

id = UUID.randomUUID(),

title = "",

price = 0

)

private val createSubscriptionViewModel: CreateSubscriptionViewModel by viewModels {

CreateSubscriptionViewModelFactory(subscriptionID = subscription.id)

}

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

lifecycleScope.launch {

viewModelForAddSubscription.addSubscription(subscription = subscription)

}

}

override fun onAttach(context: Context) {

super.onAttach(context)

val callback = object: OnBackPressedCallback(true){

override fun handleOnBackPressed() {

binding.apply {

if( (editName.text.isNotBlank()) || (editPrice.text.isNotBlank())) {

editName.setText("")

editPrice.setText("")

}

else {

findNavController().popBackStack(R.id.createSubscriptionFragment, true) } } } }

requireActivity().onBackPressedDispatcher.addCallback(callback)

}

override fun onCreateView(

inflater: LayoutInflater,

container: ViewGroup?,

savedInstanceState: Bundle?

): View {

_binding = CreateSubscriptionFragmentBinding.inflate(inflater, container, false)

return binding.root

}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

super.onViewCreated(view, savedInstanceState)

bottomReconfiguration()

binding.apply {

editName.doOnTextChanged { text, _, _, _ ->

createSubscriptionViewModel.updateSubscription { oldSubscription ->

oldSubscription.copy(title = text.toString())

}

}

editPrice.doOnTextChanged { text, _, _, _ ->

if (text.toString().isNotBlank()) {

createSubscriptionViewModel.updateSubscription { oldSubscription ->

oldSubscription.copy(price = text.toString().toInt())

}

}

}

addButton.setOnClickListener {

if ((editPrice.text.isBlank()) || (editPrice.text.toString() == "0") || (editName.text.isBlank())) {

Toast.makeText(context, "Check the data you entered", Toast.LENGTH_SHORT).show()

} else {

val navController = findNavController()

addButtonNavigation(navController)

}

}

}

viewLifecycleOwner.lifecycleScope.launch {

viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {

createSubscriptionViewModel.subscription.collect { currentSubscription ->

currentSubscription?.let {

updateUi(it)

}

}

}

}

}

override fun onStop() {

super.onStop()

if (

(createSubscriptionViewModel.subscription.value?.price == 0) ||

(createSubscriptionViewModel.subscription.value?.title?.isBlank()!!)

) {

Log.d(CREATE_SUBSCRIPTION_FRAGMENT, "THIS SUBSCRIPTION WILL BE DELETED - ${createSubscriptionViewModel.subscription.value}")

defaultDeleteSubscription(createSubscriptionViewModel.subscription.value?.id!!)

Log.d(CREATE_SUBSCRIPTION_FRAGMENT, "THIS SUBSCRIPTION WAS DELETED - ${createSubscriptionViewModel.subscription.value}")

}

if (subscription.id != createSubscriptionViewModel.subscription.value?.id) {

Log.d(CREATE_SUBSCRIPTION_FRAGMENT, "THIS SUBSCRIPTION WILL BE DELETED - ${subscription.id}")

defaultDeleteSubscription(subscription.id)

Log.d(CREATE_SUBSCRIPTION_FRAGMENT, "THIS SUBSCRIPTION WAS DELETED - $${subscription.id}")

}

}

override fun onDestroyView() {

super.onDestroyView()

Log.d("ON_DESTROY | $CREATE_SUBSCRIPTION_FRAGMENT", "DELETE CURRENT SUBSCRIPTION - ${subscription.id}")

_binding = null

}

private fun updateUi(subscription: Subscription) {

Log.d("$CREATE_SUBSCRIPTION_FRAGMENT | COLLECT SUBSCRIPTION UUID", "UUID - ${subscription.id}")

Log.d("$CREATE_SUBSCRIPTION_FRAGMENT | COLLECT SUBSCRIPTION", "TITLE - ${subscription.title} | PRICE - ${subscription.price}")

}

private fun addButtonNavigation(navController: NavController) {

val startDestination = navController.graph.startDestinationId

val previousDestination = navController.previousBackStackEntry?.destination?.id

if (previousDestination != startDestination) {

navController.navigate(

R.id.subscriptionsListFragment, null,

NavOptions.Builder()

.setPopUpTo(R.id.profileFragment, true)

.build()

)

}

else {

navController.popBackStack(R.id.createSubscriptionFragment, true)

}

}

private fun defaultDeleteSubscription(id: UUID){

viewLifecycleOwner.lifecycleScope.launch {

createSubscriptionViewModel.deleteSubscription(id)

}

}

private fun bottomReconfiguration() {

val bottomNavigationView = activity?.findViewById<BottomNavigationView>(R.id.bottomNavigation)

bottomNavigationView?.setOnItemSelectedListener { menuItem ->

when(menuItem.itemId) {

R.id.subscriptionsListFragment -> {

findNavController().popBackStack(R.id.createSubscriptionFragment, true)

true

}

R.id.createSubscriptionFragment -> {

findNavController().navigate(R.id.createSubscriptionFragment)

true

}

R.id.profileFragment -> {

findNavController().navigate(R.id.profileFragment, null,

NavOptions.Builder()

.setPopUpTo(R.id.subscriptionsListFragment, true)

.build()

)

true

}

else -> false

}

}

}

}

class ProfileFragment: Fragment() {

private var _binding: FragmentProfileBinding? = null

private val binding: FragmentProfileBinding

get() = checkNotNull(_binding) {

"Cannot access binding because it is null."

}

private val profileFragmentViewModel: ProfileFragmentViewModel by viewModels()

override fun onCreateView(

inflater: LayoutInflater,

container: ViewGroup?,

savedInstanceState: Bundle?

): View {

_binding = FragmentProfileBinding.inflate(inflater, container, false)

return binding.root

}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

super.onViewCreated(view, savedInstanceState)

binding.apply {

userProfit.doOnTextChanged { text, _, _, _ ->

if (text.toString().isNotBlank())

profileFragmentViewModel.updateProfit(text.toString().toInt())

}

viewLifecycleOwner.lifecycleScope.launch {

viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {

profileFragmentViewModel.uiState.collect { state ->

uiState(state)

}

}

}

}

}

override fun onDestroyView() {

super.onDestroyView()

_binding = null

Log.d(PROFILE_FRAGMENT, "ON DESTROY")

}

private fun uiState(state: ProfileFragmentUiState){

val profit = state.profit

val fullName = state.fullName

val amount = state.amountPrice

val percentage = profileFragmentViewModel.spendOnSubscriptions(state)

binding.apply {

if ((profit != 0) && (userProfit.text.isBlank()) && (userProfit.text.toString() != profit.toString())) {

userProfit.setText(profit.toString())

}

if (fullName.isNotBlank()) testAvatar.text = fullName.first().toString()

Log.d(PROFILE_FRAGMENT, "Percentage - $percentage / Amount - $amount / Profit - $profit")

percentageText.text = String.format(getString(R.string.percentage_symbol), percentage)

profileInfo.text = fullName

subscriptionPercentage.progress = percentage

amountSubscriptions.text = amount.toString()

}

}

}

class UpdateSubscriptionFragment: Fragment() {

private var _binding: UpdateSubscriptionFragmentBinding? = null

private val binding: UpdateSubscriptionFragmentBinding

get() = checkNotNull(_binding){

"Cannot access binding because it is null."

}

private val args: UpdateSubscriptionFragmentArgs by navArgs()

private val updateSubscriptionViewModel: UpdateSubscriptionViewModel by viewModels {

UpdateSubscriptionViewModelFactory(args.subscriptionID)

}

override fun onAttach(context: Context) {

super.onAttach(context)

val callback = object: OnBackPressedCallback(true){

override fun handleOnBackPressed() {

binding.apply {

if ((editPrice.text.isBlank()) || (editPrice.text.toString() == "0") || (editName.text.isBlank())) {

Toast.makeText(context, "Проверьте введенные вами данные", Toast.LENGTH_SHORT).show()

} else {

findNavController().popBackStack(R.id.updateSubscriptionFragment, true)

}

}

}

}

requireActivity().onBackPressedDispatcher.addCallback(callback)

}

override fun onCreateView(

inflater: LayoutInflater,

container: ViewGroup?,

savedInstanceState: Bundle?

): View {

_binding = UpdateSubscriptionFragmentBinding.inflate(inflater, container, false)

return binding.root

}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

super.onViewCreated(view, savedInstanceState)

binding.apply {

editName.doOnTextChanged { text, _, _, _ ->

updateSubscriptionViewModel.updateSubscription { subscription ->

subscription.copy(title = text.toString())

}

}

editPrice.doOnTextChanged { text, _, _, _ ->

if (text.toString().isNotBlank()) {

updateSubscriptionViewModel.updateSubscription { subscription ->

subscription.copy(price = text.toString().toInt())

}

}

}

addButton.setOnClickListener {

if ((editPrice.text.isBlank()) || (editPrice.text.toString() == "0") || (editName.text.isBlank())) {

Toast.makeText(context, "Check the data you entered", Toast.LENGTH_SHORT).show()

} else {

findNavController().popBackStack(R.id.updateSubscriptionFragment, true)

}

}

deleteButton.setOnClickListener {

deleteSubscription(args.subscriptionID)

findNavController().popBackStack(R.id.updateSubscriptionFragment, true)

}

}

viewLifecycleOwner.lifecycleScope.launch {

viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED){

updateSubscriptionViewModel.subscription.collect{ subscription ->

subscription?.let {

updateUi(it)

}

}

}

}

}

override fun onDestroyView() {

super.onDestroyView()

_binding = null

}

private fun updateUi(subscription: Subscription){

binding.apply {

if(editName.text.toString() != subscription.title){

editName.setText(subscription.title)

}

if ((editPrice.text.isBlank())

&& (editPrice.text.toString() != subscription.price.toString())

&& (subscription.price != 0)) {

editPrice.setText(subscription.price.toString())

}

Log.d(UPDATE_SUBSCRIPTION, "VALUES: TITLE = ${subscription.title} ; PRICE = ${subscription.price} ; UUID = ${subscription.id}")

}

}

private fun deleteSubscription(id: UUID) {

viewLifecycleOwner.lifecycleScope.launch {

updateSubscriptionViewModel.deleteSubscription(id)

}

findNavController().popBackStack(R.id.createSubscriptionFragment, true)

}

}

class SubscriptionsAdapter(

private val subscriptions: List<Subscription>,

private val onSubscriptionClicked: (id: UUID) -> Unit

): RecyclerView.Adapter<SubscriptionsAdapter.SubscriptionViewHolder>() {

class SubscriptionViewHolder(

private val binding: SubscriptionItemListBinding

): RecyclerView.ViewHolder(binding.root) {

fun bind(subscription: Subscription, onSubscriptionClicked: (id: UUID) -> Unit){

binding.apply {

subscriptionTitle.text = subscription.title

subscriptionPrice.text = subscription.price.toString() + "$"

if (subscription.title.isNotBlank()) firsLetter.text = subscription.title[0].toString()

root.setOnClickListener {

onSubscriptionClicked(subscription.id)

}

}

}

}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {

val inflater = LayoutInflater.from(parent.context)

val binding = SubscriptionItemListBinding.inflate(inflater, parent, false)

return SubscriptionViewHolder(binding)

}

override fun onBindViewHolder(holder: SubscriptionViewHolder, position: Int) {

val subscription = subscriptions[position]

holder.bind(subscription, onSubscriptionClicked)

}

override fun getItemCount() = subscriptions.size

}

class SubscriptionMargin: RecyclerView.ItemDecoration() {

override fun getItemOffsets(

outRect: Rect,

view: View,

parent: RecyclerView,

state: RecyclerView.State

) {

if (parent.getChildLayoutPosition(view) % 2 == 0) {

outRect.top = 50

outRect.left = 20

outRect.right = 10

} else {

outRect.top = 50

outRect.right = 20

outRect.left = 10

}

}

}

Размещено на Allbest.ru

...

Подобные документы

  • Разработка приложения для проверки использования времен глаголов в английском языке. Создание базы данных. Анализ используемых средств для реализации автоматического разбора текста. Проектирование мобильного приложения с помощью диаграмм деятельности.

    дипломная работа [2,6 M], добавлен 13.09.2017

  • Анализ российского рынка мобильных приложений. Мобильное приложение как новый канал коммуникации с целевой аудиторией. Этапы создания мобильного приложения. План продвижения мобильного приложения в сети Интернет. Бесплатные инструменты продвижения.

    дипломная работа [1,6 M], добавлен 23.06.2016

  • Анализ предметной области "Конкурс поэтов" на основе объектно-ориентированного подхода. Разработка оконного приложения и описание информационной модели предметной области. Описание разработанных процедур С++ и результатов тестирования приложения.

    курсовая работа [355,9 K], добавлен 18.06.2013

  • Современное состояние рынка мобильных приложений. Основные подходы к разработке мобильных приложений. Обоснование выбора целевой группы потребителей приложения. Этапы проектирования и разработки мобильного приложения для операционной системы Android.

    курсовая работа [987,1 K], добавлен 27.06.2019

  • Изучение языков программирования PHP, SQL, C++, HTML. Рассмотрение правил запуска и использования локального сервера Denwer. Составление технического задания по разработке программного продукта. Описание создаваемого мобильного и веб-приложения.

    курсовая работа [212,4 K], добавлен 07.04.2015

  • Разработка программного решения по созданию мобильного приложения. Изучение технологий для разработки приложений. Анализ работы торговых агентов. Обоснование выбора языка программирования. Проектирование интерфейса структуры и верстка, листинг программы.

    дипломная работа [2,2 M], добавлен 08.06.2017

  • Создание, изучение и разработка приложение на Android. Среда разработки приложения DelphiXE5. Установка и настройка среды программирования. Этапы разработки приложения. Инструменты для упрощения конструирования графического интерфейса пользователя.

    курсовая работа [1,6 M], добавлен 19.04.2017

  • Обзор подходов к разработке музейных приложений с элементами дополненной реальности, формирование требований к ним. Выбор методов разработки приложения, разработка пользовательского интерфейса. Принципы тестирования. Реализация раздела "Распознавание".

    дипломная работа [2,8 M], добавлен 03.07.2017

  • Вид деятельности, для автоматизации которой предназначен модуль. Определение границ проекта "создание мобильного приложения системы КБНТИ для отображения изменений в системе и управления модулем подписок". Построение диаграммы состояний уведомления.

    отчет по практике [386,9 K], добавлен 11.04.2016

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

    дипломная работа [1,0 M], добавлен 10.07.2017

  • Проектирование удобного приложения для комфортной навигации по файлам облачного хранилища в одном файловом менеджере. Выбор интегрированной среды разработки. Выбор инструментов для визуализации приложения. Выбор средств отслеживания HTTPзапросов.

    курсовая работа [3,6 M], добавлен 16.07.2016

  • Разработка приложений для смартфонов на ОС Android для сети аптек "Фармация". Архитектура операционной системы Android. Архитектура и реализация приложения. Его функциональность. Описание работы мобильного приложения. Расчет затрат на создание продукта.

    дипломная работа [1,6 M], добавлен 17.06.2017

  • Назначение и возможности разработанного приложения для контроля активности сетевых и периферийных устройств предприятия. Язык программирования Java. Распределенные многоуровневые приложения. Структура базы данных, интерфейс разработанного приложения.

    курсовая работа [1,0 M], добавлен 16.12.2012

  • Обзор мобильной операционной системы ios: Архитектура ОС iOS; уровень библиотек; среды разработки приложения (Xcode, Xamarin). Доступ к информации колледжа "Угреша". Требования к мобильному приложению. Подготовка среды разработки. Тестирование приложения.

    дипломная работа [5,6 M], добавлен 10.07.2014

  • Разработка приложения "Калькулятор" для подсчитывания количества символов или букв в арабском тексте. Проектирование программной системы, определение функциональных требований к приложению. Алгоритм разработки модульной структуры мобильного приложения.

    презентация [853,9 K], добавлен 08.04.2019

  • Разработка программы для рисования различных правильных многоугольников с помощью объектно-ориентированного языка программирования. Использование для разработки среды C++ Builder 6 и библиотеки VCL. Разработка интерфейса приложения и алгоритма его работы.

    курсовая работа [616,4 K], добавлен 18.10.2010

  • Разработка приложения, которое будет выполнять функции показа точного времени и точной даты. Определение дополнительных функций разработанного приложения. Рассмотрение основных этапов создания программного продукта. Результаты тестирования приложения.

    курсовая работа [2,2 M], добавлен 14.04.2019

  • Разработка и формализация эффективного подхода к оценке качества каналов трафика мобильного приложения. Преимущества работы с социальными сетями. Тестирование возможных типов каналов по привлечению установок приложения. Расчёт средней стоимости лидов.

    дипломная работа [2,6 M], добавлен 09.02.2017

  • Изучение существующих подходов к использованию компьютерных игр в образовательном процессе. Особенности использования мобильного обучения. Методика и этапы закрепления полученных ранее знаний с использованием игрового приложения на мобильной платформе.

    дипломная работа [813,0 K], добавлен 27.10.2017

  • Технология создания многопоточных приложений в современных системах программирования с использованием языка C# в Visual Studio.NET. Разработка алгоритма и структуры программы. Описание и особенности тестирования приложения с разным количеством потоков.

    курсовая работа [773,0 K], добавлен 14.03.2013

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.