You are on page 1of 222

НАЦІОНАЛЬНИЙ ТЕХНІЧНИЙ УНІВЕРСИТЕТ УКРАЇНИ

«КИЇВСЬКИЙ ПОЛІТЕХНІЧНИЙ ІНСТИТУТ


імені ІГОРЯ СІКОРСЬКОГО»
Факультет інформатики та обчислювальної техніки
__________________________ _________________________________________________

(повне найменування інституту, факультету)


Автоматизованих систем обробки інформації і управління
________________ ___________________________________________________________

(повна назва кафедри)

«До захисту допущено»


В.о. завідувача кафедри
__________ Олександр ПАВЛОВ
________________
(підпис) (ініціали, прізвище)

“ ” 2021 р.

Дипломний проєкт
на здобуття ступеня бакалавра
за освітньо-професійною програмою «Програмне забезпечення
комп’ютеризованих систем»
спеціальності «121 Інженерія програмного забезпечення»

на тему Мобільне застосування для контролю особистого часу

Виконав: студент IV курсу, ІП-72 Нестеренко Єгор


групи Олександрович
(прізвище, ім’я, по батькові ) (підпис)

Керівник ст. викладач Вітковська І.І.


посада,науковий ступінь,вчене звання,прізвище,і ім’я, по батькові (підпис)

Консультант
з графічної
документації ст. викладач Вітковська І.І.
посада,науковий ступінь,вчене звання,прізвище,і ім’я, по батькові (підпис)

Рецензент:
проф., д.т.н., проф.Писарчук О.О.
посада,науковий ступінь,вчене звання,прізвище,ім’я, по батькові (підпис)

Засвідчую, що у цьому дипломному проєкті


немає запозичень з праць інших авторів без
відповідних посилань.
Студент _____________
(підпис)

Київ – 2021 року


Національний технічний університет України
“Київський політехнічний інститут імені Ігоря Сікорського”
Інформатики та обчислювальної техніки
Факультет (інститут) ______________________________________________
(повна назва)

автоматизованих систем обробки інформації і управління


Кафедра __________________________________________________________
(повна назва)

Рівень вищої освіти – перший (бакалаврський)


Спеціальність – 121 Інженерія програмного забезпечення
Освітньо-професійна програма – Програмне забезпечення
комп’ютеризованих систем

ЗАТВЕРДЖУЮ
В.о. завідувача кафедри
__________ Олександр ПАВЛОВ
(підпис)

“___”_____________________2021 р.

ЗАВДАННЯ
НА ДИПЛОМНИЙ ПРОЄКТ СТУДЕНТУ
Нестеренко Єгору Олександровичу
(прізвище, ім’я, по батькові)

1. Тема проєкту «Мобільне застосування для контролю


особистого часу»
керівник проєкту Вітковська Ірина Іванівна, ст. викладач я
( прізвище, ім’я, по батькові, науковий ступінь, вчене звання)
затверджені наказом по університету від “11” травня 2021 р. №1139-с
2. Термін подання студентом проєкту «08» червня 2021 року
3. Вихідні дані до проєкту
Технічне завдання

4. Зміст пояснювальної записки


1) Аналіз вимог до програмного забезпечення: основні визначення та терміни,
опис предметного середовища, огляд існуючих технічних рішень та відомих
програмних продуктів, розробка функціональних та нефункціональних вимог
2) Моделювання та конструювання програмного забезпечення: моделювання та
аналіз програмного забезпечення, засоби розробки, технічні рішення, архітектура
програмного забезпечення
3) Розгортання та впровадження програмного забезпечення
4) Керівництво користувача, методика випробувань програмного продукту
5. Перелік графічного матеріалу
1) Схема структурна діяльності
2) Схема бази даних
3) Креслення вигляду екранних форм

6. Консультанти розділів проєкту


Підпис, дата
Прізвище, ініціали та посада
Розділ завдання завдання
консультанта
видав прийняв

7. Дата видачі завдання «14» березня 2021 року


а

КАЛЕНДАРНИЙ ПЛАН
№ Назва етапів виконання дипломного Термін виконання
з/п
Примітка
проєкту етапів проєкту
1. Вивчення рекомендованої літератури 18.03.2021
2. Аналіз існуючих методів розв’язання 23.03.2021
задачі
3. Постановка та формалізація задачі 28.03.2021
4. Аналіз вимог до програмного 04.04.2021
забезпечення
5. Алгоритмізація задачі 10.04.2021
6. Моделювання програмного 20.04.2021
забезпечення
7. Обґрунтування використовуваних 29.04.2021
технічних засобів
8. Розробка архітектури програмного 01.05.2021
забезпечення
9. Розробка програмного забезпечення 07.05.2021
10. Налагодження програми 11.05.2021
11. Виконання графічних документів 13.05.2021
12. Оформлення пояснювальної записки 15.05.2021
13. Подання ДП на попередній захист 17.05.2021
14. Подання ДП рецензенту 01.06.2021
15. Подання ДП на основний захист 08.06.2021

Студент ____________________________ Єгор НЕСТЕРЕНКО


(підпис)

Керівник ___________________________________ Ірина ВІТКОВСЬКА


(підпис)
Кількість
Формат

листів
№ з/п
Позначення Найменування Примітка

1 А4 Завдання на дипломний проєкт 2

2 А4 КПІ.ІП-7219.045490.02.81 Пояснювальна записка 65


Технічне завдання
3 А4 КПІ.ІП-7219.045490.03.91 11

4 А4 КПІ.ІП-7219.045490.04.51 Програма та методика тестування 5

5 А4 КПІ.ІП-7219.045490.05.34 Керівництво користувача 11

6 А4 КПІ.ІП-7219.045490.06.13 Опис програми 125

7 А3 КПІ.ІП-7219.045490.07.99.СС Схема структурна діяльності 1

8 А3 КПІ.ІП-7219.045490.07.99.СБД Схема бази даних системи 1

9 А3 КПІ.ІП-7219.045490.07.99.КЕ Креслення вигляду екранних форм 1

КПІ.ІП-7219.045490.01.90
Зм. Арк. ПІБ Підп. Дата

Розробн. Нестеренко Є.О. Літ. Лист Листів


Керівн. Вітковська І.І.
Відомість 1 1
Консульт
. дипломного проєкту КПІ ім.Ігоря Сікорського
Н/контр. Вітковська І.І.
кафедра АСОІУ гр. ІП-72
В.о.зав.каф. Павлов О.А.
АНОТАЦІЯ

Структура та обсяг роботи. Пояснювальна записка дипломного проєкту


складається з чотирьох розділів, містить 15 рисунків, 32 таблиці, 11 джерел.
Дипломний проєкт призначений для керування особистим часом, а також
планування задач відповідно до своєї поточної завантаженості. Саме тому в
рамках дипломного проєктування було створено мобільний застосунок для
системи iOS, який надає можливість вирішувати задачі керування особистим
часом.
У першому розділі було виконано опис та аналіз предметної області
програмного продукту, який розробляється. Під час цього було проаналізовано
ринок наявних застосувань, що є схожими за тематикою та функціональністю.
Було проаналізовано переваги та недоліки цих застосувань та на основі цієї
інформації визначено цілі програмного продукту в рамках дипломного проєкту.
Також було описано функціональні та нефункціональні вимоги до програмного
забезпечення.
У другому розділі було виконано опис процесів за допомогою діаграм
UML, визначення архітектури мобільного застосунку, проаналізовано та обрано
найбільш підходящі для цього технології, за допомогою яких буде
розроблятися програмний продукт.
Третій розділ дипломного проєкту охоплює якість програмного продукту.
Для цього було описано процеси тестування, тест-кейси та наведено приклади
тестування.
У четвертому розділі було виконано опис процесу впровадження та
супроводу застосунку, зокрема розгортання програмного забезпечення та
створено інструкцію користувача та адміністратора.
КЕРУВАННЯ ЗАДАЧАМИ, ПЛАНУВАЛЬНИК ЗАДАЧ, МОБІЛЬНЕ
ЗАСТОСУВАННЯ, ТАСК-МЕНЕДЖЕР

Арк.

КПІ.ІП-7219.045490.02.81 5
Змн. Арк. № докум. Підпис Дата
ABSTRACT

Structure and scope of work. The explanatory note of the diploma project
consists of six sections, contains 15 figures, 32 tables, 11 sources.
The diploma project is designed to manage personal time, as well as to plan
tasks according to their current workload. That is why as part of the diploma design
was created a mobile application for iOS, which provides the ability to solve
problems of personal time management.
In the first section, a description and analysis of the subject area of the future
software product being developed was performed. During this time, the market of
available applications that are similar in theme and functionality was analyzed. These
applications were analyzed and based on it information the goals of the software
product within the diploma project were determined. Functional and non-functional
software requirements were described also.
The second section describes the business processes using UML diagrams,
defines the architecture of the mobile application, analyzed and selected the most
appropriate technologies for which will be developed software product waiting for
the thesis project.
The third section of the thesis project covers the quality of the software
product. For this purpose, testing processes, test cases and examples of testing were
described.
The fourth section describes the process of implementing and maintaining the
application, including software deployment, and creates user and administrator
instructions.
TASK MANAGEMENT, TASK PLANNER, MOBILE APPLICATION,
TASK MANAGER

Арк.

КПІ.ІП-7219.045490.02.81 6
Змн. Арк. № докум. Підпис Дата
Пояснювальна записка
до дипломного проєкту

на тему: Мобільне застосування для контролю особистого часу

Київ – 2021 року


ЗМІСТ

ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ, СИМВОЛІВ, ОДИНИЦЬ, СКОРОЧЕНЬ І


ТЕРМІНІВ ......................................................................................................................................... 9

ВСТУП .................................................................................................................................. 10

1 АНАЛІЗ ВИМОГ ДО ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ .............................. 12

1.1 ЗАГАЛЬНІ ПОЛОЖЕННЯ ...........................................................................................12


1.2 ЗМІСТОВНИЙ ОПИС І АНАЛІЗ ПРЕДМЕТНОЇ ОБЛАСТІ ...............................................12
1.3 АНАЛІЗ УСПІШНИХ IT-ПРОЄКТІВ............................................................................14
1.4 АНАЛІЗ ВИМОГ ДО ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ .................................................21
1.4.1 Розроблення функціональних вимог ................................................................29
1.4.2 Розроблення нефункціональних вимог ............................................................33
1.4.3 Постановка комплексу завдань модулю ........................................................34
1.5 ВИСНОВКИ ПО РОЗДІЛУ ..........................................................................................35

2 МОДЕЛЮВАННЯ ТА КОНСТРУЮВАННЯ ПРОГРАМНОГО


ЗАБЕЗПЕЧЕННЯ ........................................................................................................................... 36

2.1 МОДЕЛЮВАННЯ ТА АНАЛІЗ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ ...................................36


2.2 АРХІТЕКТУРА ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ ........................................................40
2.3 КОНСТРУЮВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ .................................................43
2.4 ОПИС БАЗИ ДАНИХ .................................................................................................49
2.5 ВИСНОВКИ ПО РОЗДІЛУ ..........................................................................................54

3 АНАЛІЗ ЯКОСТІ ТА ТЕСТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ 55

3.1 АНАЛІЗ ЯКОСТІ ПЗ..................................................................................................55


3.2 ОПИС ПРОЦЕСІВ ТЕСТУВАННЯ ................................................................................56
3.3 ОПИС КОНТРОЛЬНОГО ПРИКЛАДУ ..........................................................................57
3.4 ВИСНОВКИ ДО РОЗДІЛУ ..........................................................................................61

4 ВПРОВАДЖЕННЯ ТА СУПРОВІД ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ .... 62

4.1 РОЗГОРТАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ........................................................62


4.2 РОБОТА З ПРОГРАМНИМ ЗАБЕЗПЕЧЕННЯМ ..............................................................63
4.3 ВИСНОВКИ ДО РОЗДІЛУ ..........................................................................................63

ВИСНОВКИ ........................................................................................................................ 64

ПЕРЕЛІК ПОСИЛАНЬ ..................................................................................................... 65


Арк.

КПІ.ІП-7219.045490.02.81 8
Змн. Арк. № докум. Підпис Дата
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ, СИМВОЛІВ, ОДИНИЦЬ,
СКОРОЧЕНЬ І ТЕРМІНІВ

БД – База даних.
HTTP (HyperText Transfer Protocol) – протокол для передачі
гіпертектовних даних та документів в формату «HTML» прикладного рівня.
SQL – декларативна мова програмування для керування базами даних та
використовується, щоби формувати запити і оновлювати дані з баз даних,
створювати схеми та модифікувати їх.
UI – інтерфейс користувача для взаємодії користувача з мобільним
застосунком.
Токен – згенерована випадковим чином послідовність символів, щоби
підтвердити дій користувача.
API (Application Programming Interface) – спеціальний набір правил,
програмам та протоколів для взаємодії всередині програмного забезпечення, які
описані у вигляді методів.

Арк.

КПІ.ІП-7219.045490.02.81 9
Змн. Арк. № докум. Підпис Дата
ВСТУП

Час – найцінніший ресурс, правильне керування яким є не простою, але


важливою задачею для кожного, хто хоче встигати робити більше справ. Кожен
з нас, особливо в молодому віці, хоче витрачати кожну хвилину свого життя з
користю та максимальною ефективністю, плануючи день більш правильно,
відповідно до технік планування часу та розставляючи пріоритети. Від
паперових календарів та нотаток на холодильнику людство перейшло до
календарів у мобільних телефонах та додатків, що допомагають керувати
особистим часом, спрощуючи цей процес.
Проте, особливістю багатьох програм є те, що ви записуєте власні справи,
а програма просто нагадує вам про них. Програми не пропонують вам
спланувати день ефективним чином, базуючись на завданнях, що вже були
сплановані вами завчасно та технік планування для керування особистим
часом.
Список мобільних застосувань для контролю особистого часу, що вже
представлені на ринку, досить широкий, але вони мають свої недоліки. В
дипломній роботі було досліджено це питання та розроблено мобільне
застосування, згідно з потреб користувачів.
Проаналізувавши програми для особистого контролю та керування часом,
було зроблено висновок, що їх досить важко адаптувати під навчання студента.
Багато з них мають однакову функціональність, а основними їх недоліками є те,
що:
− застосунок просто дозволяє вносити задачі, а не пропонує спланувати їх
відповідно до вашого поточного розкладу та методик для керування
задачами;
− безоплатна версія застосунків не має широкого спектра функціональних
можливостей для досягання максимальної ефективності планування;

Арк.

КПІ.ІП-7219.045490.02.81 10
Змн. Арк. № докум. Підпис Дата
− застосунок доступний лише в англомовній версії, немає української
версії;
− не завжди є синхронізація з календарями та вбудованими утилітами
телефону.
Отже, тема дипломної роботи є актуальною, оскільки направлена на
полегшення процесу планування задач та особистого часу, особливо для
студента, оскільки мобільне застосування, що розроблено, пропонує
користувачеві обирати часові проміжки, в які він може виконати заплановану
задачу. Мобільне застосування для контролю особистого часу використовує
декілька відомих технік та методик планування одночасно, щоби при
плануванні робочих та особистих задач, програма могла запропонувати
користувачеві найкращий час для виконання задачі, мала зручний
локалізований інтерфейс українською мовою, що робить застосування
доступним для україномовних студентів, а також пропонуватиме спланувати
розклад задач на день більш оптимізовано, базуючись на дослідженнях та
відомих техніках планування, які будуть описані в наступних розділах
дипломного проєкту.

Арк.

КПІ.ІП-7219.045490.02.81 11
Змн. Арк. № докум. Підпис Дата
1 АНАЛІЗ ВИМОГ ДО ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ

1.1 Загальні положення

Багато хто з нас має щільний графік роботи, навчання, і у багатьох людей
не вистачає часу на організацію повсякденного життя. Люди стикаються з
труднощами при керуванні своїм розпорядком дня, складанні особистого
розкладу і зосередженні уваги на проєктах та завданнях.
Метою дипломного проєкту є розробка мобільного застосунку, який
допоможе користувачеві з плануванням часу та керуванням особистими
задачами. Основною ціллю є розробка такого застосунку, щоби користувач міг
вносити туди власні задачі для того, щоби наочно їх бачити та контролювати їх.
Особливістю застосунку є можливість розмежування особистих та навчальних
задач, а також автоматичне планування задач, які не потребують конкретного
часу виконання.
Мобільне застосування, що розробляється, розділене на клієнтську та
серверну частину, що дозволяє зберігати дані при втраті доступу до профілю та
здійснювати авторизований вхід з будь-якого iOS-сумісного пристрою.

1.2 Змістовний опис і аналіз предметної області

Для виконання планування особистих задач рекомендують


використовувати методики, що базуються на психологічних принципах
людини, а також на усталених та перевірених техніках планування. У цій
частині дипломного проєкту було проаналізовано деякі з таких технік.

1.2.1 Матриця Ейзенхауера

Техніка керування особистими задачами за допомогою матриці


Ейзенхауера полягає в упорядкуванні завдань в одному з чотирьох умовних
квадрантів:

Арк.

КПІ.ІП-7219.045490.02.81 12
Змн. Арк. № докум. Підпис Дата
− терміново і важливо (що потрібно зробити в першу чергу);
− не терміново, але важливо (те, що слід відкласти, але це потрібно
зробити);
− терміново і не важливо (має сенс делегувати це завдання комусь);
− не важливо і не терміново (це завдання можна виключити через
неактуальність).
Ця техніка дає змогу наочно бачити важливість та терміновість кожної.
задачі, що була внесена до цієї матриці.

1.2.2 Принцип дев'яти справ або правило 1-3-5

Принцип техніки планування «Дев’яти», або правила «1-3-5» полягає в


тому, що для більшої ефективності протягом дня необхідно виконувати щодня
лише одне велике завдання, три середніх та п’ять маленьких. До малих
належать, наприклад, прості домашні справи. Також краще спланувати їх так,
щоб вони не накопичувались. Ідея полягає в тому, що така кількість і
співвідношення завдань допоможуть вам провести день продуктивніше і
водночас не виснажитися.
Ця техніка допомагає користувачеві побудувати чітку ієрархію задач, а
також є можливість легко підбивати підсумки щодо виконаної роботи
наприкінці дня.
Також було проведено аналіз спеціалістами галузі, що простіше
виконувати кілька невеликих, але чітко визначених завдань, ніж великих. Тому
базуючись на даній техніці планування особистими задачами, необхідно
розбивати всі великі задачі на невеликі підзадачі, щоби відстежувати прогрес
користувача, щоб він не застряг на великих складних завданнях.

Арк.

КПІ.ІП-7219.045490.02.81 13
Змн. Арк. № докум. Підпис Дата
1.2.3 Техніка часових блоків

Багато людей пишуть список завдань на день, але все одно не встигають
виконати все, що задумали. Часто це пов’язано з тим, що простий перелік
завдань не враховує двох речей: по-перше, скільки часу займає кожна справа, а
по-друге, коли саме їх потрібно виконати. Метод часового блоку враховує
обидва.
Суть полягає в тому, щоб заздалегідь розподілити час для кожного
випадку. А в цей час не виконувати задач, крім запланованої. Ця техніка
планування підходить для дисциплінованих людей, а саме тих, які чітко
планують робочий та особистий час, дотримуючись графіку. Важливо, щоб
щось можна було зробити за відведений час.

1.3 Аналіз успішних IT-проєктів

На ринку постійно з’являється все більша кількість мобільних та


браузерних застосунків для тайм-менеджменту та керування особистими
задачами. Користувачі завантажують їх, щоби полегшити процес керування
задачами, не забути що необхідно виконати та не випускати їх з виду – оскільки
застосунок, у який ви додаєте свої задачі, зможе проінформувати та нагадати,
про необхідність виконання певної задачі.
Використання застосунків для керування особистими задачами стало
особливо активним та актуальним з початком карантину, коли всі почали
працювати віддалено та повинні були контролювати задачі та особистий та
робочий час самостійно.
Різні застосування використовують різні техніки для контролю часу та
контролю задачами та мають досить зручний інтерфейс. Користувачі можуть
обрати такий застосунок, який до вподоби саме їм – оскільки на ринку
представлена велика кількість різних застосунків.

Арк.

КПІ.ІП-7219.045490.02.81 14
Змн. Арк. № докум. Підпис Дата
1.1.1 Trello

Рисунок 1.1 – Головний екран мобільного застосування для керування задачами


Trello
Trello – це інструмент для керування задачами для роботи та життя, який
організовує всі проєкти у вигляді дошок. З даним застосуванням є можливість
наочно бачити над чим працює ваш колега, в якому статусі проєкт та
попереджати про настання дедлайнів.
Trello використовує методику Kanban дошки, що є достатньо обмеженим
за функціональністю. Kanban дошка – це можливість керування особистими
задачами та робочими процесами. Цей метод використовує спецілальну
аналогову або цифрову дошку, на якій розміщено стовпці та картки. Типова
дошка Kanban складається з однієї або декількох смуг та декількох стовпців,
щоби зобразити робочий процес.
Методика Kanban дошки часто використовується при розробці
програмного забезпечення, оскільки є можливість спостерігати за прогресом
виконання задач. Розглядаючи саме застосунок Trello, можна побачити що він
не не має української версії, а ця техніка є достатньо обмеженою – оскільки
користувач має сам додавати задачу та стежити за статусом її виконання.

Арк.

КПІ.ІП-7219.045490.02.81 15
Змн. Арк. № докум. Підпис Дата
Можливості застосунку Trello:
− кросплатформленість;
− інтерфейс, що направлений на керування задачами в рамках
великих проєктів;
− інтеграція з іншими застосунками та сервісами;
− додавання супровідного матеріалу на карту завдання: файли,
зображення, посилання тощо.

1.1.2 Any.do

На ринку представлено дуже багато мобільних застосувань, у яких


реалізовано техніку планування «to-do list», але такі застосунки мають дуже
обмежений функціонал. Any.do (рис.1.2) не виключення – це звичайний to-do
list задач, які необхідно зробити. Такий спосіб керування задачами та
особистим часом є досить обмеженим.
Особливість цього мобільного застосування є в тому, що користувач
просто записує особисті задачі на день, декілька днів, або тижнів у вигляді
звичайного маркованого списку та відмічає їх, як виконані, шляхом відмічення
цього завдання галочкою. Всі завдання, що були додані до мобільного
застосунку можна також побачити у вигляді календаря, при умові, що задача
має початкову або кінцеву дату виконання. Також є можливість встановлювати
пріоритети задач, створювати підзадачі.

Арк.

КПІ.ІП-7219.045490.02.81 16
Змн. Арк. № докум. Підпис Дата
Рисунок 1.2 – Головний екран мобільного застосування для керування
задачами Any.do

Користувачі цього мобільного застосування мають можливість


ділитися списками спланованих задач, призначати завдання, стежити за ходом
їх виконання і спілкуватися з іншими в рамках проєктів та завдань.
Можливості застосунку Any.Do:
− керування задачами та створення to-do списків задач;
− користувацький інтерфейс, з яким легко працювати;
− використання застосунку на декількох платформах;
− розпізнавання голосу та інтерпретація його в задачі;
− спільний доступ до списків задач;
− інформування про дедлайни запланованих задач;

Арк.

КПІ.ІП-7219.045490.02.81 17
Змн. Арк. № докум. Підпис Дата
1.1.3 Google календар

Google календар (рис. 1.3) – це сервіс для управління часом та


плануванням задач у вигляді календаря, що був розроблений компанією Google.
Сервіс працює в різних браузерах та на різних платформах.
Google календар дозволяє своїм користувачам швидко планувати задачі,
створювати майбутні події, щоби застосунок повідомляв про їх початок та
нагадував про всі майбутні задачі. Працювати з календарем можна в режимі
спільного доступу, а це означає що ви можете поділитися своїм розкладом та
графіком з групою людей, які навчаються та/або працюють з вами. Також є
можливість створювати відразу декілька календарів – для особистого та
спільного доступу.
Користувач може додавати до завдання місце події, запрошувати інших
користувачів, а також написати нотатку. Користувачі можуть увімкнути або
вимкнути видимість спеціальних календарів, що пропонуються сервісом
Google: це календар днів народження, інформацію про які система бере з
контактів Google, свята, що відповідають календарю конкретної країн тощо.
Календар Google постійно оновлює свою функціональність, оскільки над
ним працює велика команда розробників. З останнього оновлення, на яке
вплинув карантин через пандемію коронавірусу – це можливість додання
посилання на відеозуструч Zoom чи Google Meets.

Арк.

КПІ.ІП-7219.045490.02.81 18
Змн. Арк. № докум. Підпис Дата
Рисунок 1.3 – Головний екран мобільного застосунку для керування задачами
Google календар

Можливості Google календаря:


− створення та редагування подій та задач: картка події може містити
інформацію про місце та час заходу, а також додавати опціональні
додаткові параметри;
− використання застосунку на декількох платформах;
− спільний доступ до календарів та запланованих задач: ви можете
назначати до своєї картки завдання різних осіб;
− перегляд подій у різному вигляді: на день, тиждень, місяць, або твій
постійний розклад;
− інформування про дедлайни запланованих задач через вбудовані
нотифікації на пристрої, або електронну пошту;
− стандартні вбудовані календарі дні народжень та свят, які можна
відключити за потреби.

1.1.4 TickTick

TickTick (рис. 1.4) – це онлайн-сервіс для створення безлічі списків


завдань. Списки можна використовувати навіть як проєкти. Їх може бути
скільки завгодно. Архіви завдань і списків зберігаються в хмарі.
Арк.

КПІ.ІП-7219.045490.02.81 19
Змн. Арк. № докум. Підпис Дата
Застосунок TickTick можна використовувати незалежно від мети
планування: це може бути планування, пов’язане з роботою, чи особистим
життям. У користувача є можливість встановити нагадування про необхідність
виконання задач, щоби не турбуватися про пропущені терміни.
За допомогою п’яти різних представлень списку задач, що потребують
вашої реакції: від календаря до діаграм – користувачі можуть перевіряти
розклад більш зручно та брати інформацію про попередні події до уваги.

Рисунок 1.4 – Головний екран мобільного застосунку для керування задачами


TickTIck
Можливості TickTick:
− створення списків задач та керування ними;
− простий та зручний інтерфейс;
− використання додатку на декількох платформах;
− встановлення додаткової функціональності при створенні задач:
залежність завдання від локації;
− запис голосу для інтерпретації його в текстову задачу;
− можливість спільної роботи та поділитися календарем з друзями чи
колегами;
− інформування дедлайнів задач.

Арк.

КПІ.ІП-7219.045490.02.81 20
Змн. Арк. № докум. Підпис Дата
Проаналізувавши наявні мобільні застосування для планування часу, які
представлені на ринку та порівнявши їх – можна прийти до висновку, що їх
досить велика кількість та користувач може покористуватися декількома з них
та обрати найоптимальніший саме для нього.
Проте, розробляючи дипломний проєкт основний акцент було зроблено
на тому, щоби застосунок був актуальний для студента – а отже він міг додати
розклад на весь семестр, оскільки він повторюється кожного тижня. Також,
застосунки, що представлені на ринку не пропонують користувачеві
спланувати розклад відповідно до вже запланованих завдань. В мобільному
застосунку, що представляється, було додано таку можливість, і якщо
користувачеві немає необхідності виконувати певне завдання в конкретний час
– застосунок повинен запропонувати підходящий час, що буде вільним від
інших задач.

1.4 Аналіз вимог до програмного забезпечення

Дослідивши та проаналізувавши вимоги до програмного забезпечення, а


також вивчивши ринок схожих мобільних застосунків для керування особистим
часом, було сформовано основну функціональність, яка необхідна для розробки
дипломного проєкту.
На рис. 1.5 представлена діаграма варіантів використання, на якій описані
основні можливості користувача.

Арк.

КПІ.ІП-7219.045490.02.81 21
Змн. Арк. № докум. Підпис Дата
Рисунок 1.5 – Схема структурна варіантів використань

Основні функціональні вимоги до мобільного застосунку:


− вхід в мобільний додаток;
− додавання нової задачі;
− додавання задач з фіксованим часом;
− додавання задачі з нефіксованим часом (система пропонує
користувачеві оптимальний час для задачі);
− додавання розкладу з університету;
− встановлення зайнятих годин;
− встановлення кольору для задачі;
− імпортування календаря;

Арк.

КПІ.ІП-7219.045490.02.81 22
Змн. Арк. № докум. Підпис Дата
− очищення календаря;
− вихід з мобільного додатку.

Таблиця 1.1 – Опис варіанти використання UC-1


Назва Вхід в мобільний додаток
Опис Користувач входить до мобільне застосування з наявним
Google профілем
Актор Користувач
Передумови У користувача є профіль Google за допомогою якого він
авторизується в системі та вже був зареєстрований в
додатку до цього
Постумови Користувач потрапляє на основну сторінку та отримує
доступ до функціональності додатку
Основний 1) Но сторінці входу в застосування користувач
сценарій бачить кнопку увійти
2) Він переходить на сторінку Google та підтверджує
свої дані
3) Користувач авторизується в систему;
Розширений 3.1. Користувач ввів некоректні дані
сценарій

Таблиця 1.2 – Опис варіанти використання UC-2


Назва Додавання нової задачі
Опис Користувач може додавати нові задачі в систему
Актор Користувач
Передумови У користувача є зареєстрований профіль в систем,
користувач авторизований в систему
Постумови Користувач додав нову задачу в систему

Арк.

КПІ.ІП-7219.045490.02.81 23
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 1.2
Основний 1) Користувач додав нову задачу
сценарій a) Користувач додав задачу в рамках розкладу
b) Користувач додав задачу з фіксованим часом
виконання
c) Користувач додав задачу з нефіксованим
часом виконання, система пропонує варіанти
Розширений
сценарій

Таблиця 1.3 – Опис варіанти використання UC-3


Назва Додання нової задачі з фіксованим часом виконання
Опис Користувач може додати нову задачу з фіксованим часом
виконання в список особистих задач
Актор Користувач
Передумови Користувач має зареєстрований профіль та він
авторизований в системі
Постумови Користувач додав нову задачу до списку своїх задач
Основний 1) Користувач бачить на головному меню кнопку
сценарій «Додати нову задачу» та натискає її
2) Користувач заповнює поля для створення нової
задачі: назва задачі, дедлайн
3) Користувач натискає «Зберегти задачу», щоби
створити задачу
4) Задача додається до списку запланованих задач
користувача
Розширений
сценарій

Арк.

КПІ.ІП-7219.045490.02.81 24
Змн. Арк. № докум. Підпис Дата
Таблиця 1.4 – Опис варіанти використання UC-4
Назва Додання нової задачі з нефіксованим часом виконання
Опис Користувач може додати нову задачу з нефіксованим
часом виконання в список свої задач
Актор Користувач
Передумови Користувач має зареєстрований профіль та він
авторизований в системі
Постумови Користувач додав нову задачу до списку своїх задач
Основний 1) Користувач бачить на головному меню кнопку
сценарій «Додати нову задачу» та натискає її
2) Користувач заповнює поля для створення нової
задачі: назва задачі, як часто ця задача
повторюється та чи можна розподілити її
виконання протягом певного проміжку часу
3) Система пропонує користувачеві варіанти, на коли
ця задача може бути запланована
4) Користувач обирає найбільш підходящий варіант
5) Задача додається до списку запланованих задач
користувача
Розширений
сценарій

Таблиця 1.5 – Опис варіанти використання UC-5


Назва Додання нової задачі з розкладу задач
Опис Користувач може додати нову задачу з розкладу свої
занять до списку запланованих подій
Актор Користувач
Передумови Користувач має зареєстрований профіль та він
авторизований в системі

Арк.

КПІ.ІП-7219.045490.02.81 25
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 1.5
Постумови Користувач додав нову задачу предмет з
університетського розкладу на семестр, який
повторюватиметься в календарі щотижня.
Основний 1) Користувач бачить на головному меню кнопку
сценарій «Додати предмет з розкладу» та натискає її
2) Користувач заповнює поля для створення нової
задачі: назва задачі, час виконання та тривалість
семестру, протягом якого цей предмет
повторюватиметься
3) Користувач натискає «Зберегти», щоби створити
задачу
4) Задача додається до списку запланованих задач
користувача
Розширений
сценарій

Таблиця 1.6 – Опис варіанти використання UC-6


Назва Встановлення зайнятих годин
Опис Користувач може встановити свої особливі години, які
він витрачає на щоденні задачі: сон, їжу тощо, щоби при
автоматичному плануванні розкладу цей час не
враховувався.
Актор Користувач
Передумови Користувач має зареєстрований профіль та він
авторизований в системі
Постумови Користувач встановив особливі умови та часові рамки, в
які він зайнятий та не може виконувати заплановані
задачі.

Арк.

КПІ.ІП-7219.045490.02.81 26
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 1.6
Основний 1) Користувач переходить на сторінки
сценарій «Налаштування»
2) Користувач натискає на кнопку «Встановити
зайняті години»
3) Користувач встановлює часові обмеження, коли він
не може виконувати свої заплановані задачі:
встановлює обмеження на сон, приймання їжі.
4) Користувач натискає кнопку зберегти та
підтверджує ці зміни.
Розширений
сценарій

Таблиця 1.7 – Опис варіанти використання UC-7


Назва Очищення календаря
Опис Користувач може очистити календар від усі задач
Актор Користувач
Передумови Користувач має зареєстрований профіль та він
авторизований в системі
Постумови Користувач очистив календар від усіх запланованих
задач.
Основний 1) Користувач переходить на сторінки
сценарій «Налаштування»
2) Користувач натискає на кнопку «Очистити
календар»
3) Користувач підтверджує свої дії
4) Система очищає календар від запланованих задач
Розширений
сценарій

Арк.

КПІ.ІП-7219.045490.02.81 27
Змн. Арк. № докум. Підпис Дата
Таблиця 1.8 – Опис варіанти використання UC-8
Назва Імпортування календаря
Опис Користувач може імпортувати календар в мобільне
застосування з акаунту Google
Актор Користувач1
Передумови Користувач має зареєстрований профіль та він
авторизований в системі
Постумови Користувач імпортував календар, що знаходиться в
Google акаунті та він є доступним для подальшого
керування задачами.
Основний 1) Користувач переходить на налаштування
сценарій 2) Користувач обирає варіант «Імпортувати календар»
3) Користувач обирає календар з доступних йому для
імпортування
Розширений
сценарій

Таблиця 1.9 – Опис варіанти використання UC-9


Назва Вихід з мобільного застосування
Опис Користувач виходить з мобільного застосування
Актор Користувач
Передумови Користувач має авторизований профіль у застосунку
Постумови Користувач виходить з мобільного застосування та
переходить на сторінку авторизації
Основний 1) На сторінці налаштування користувач бачить
сценарій кнопку «Вийти з акаунту»
2) Користувач підтверджує свої дії
3) Користувач виходить з системи

Арк.

КПІ.ІП-7219.045490.02.81 28
Змн. Арк. № докум. Підпис Дата
Продовждення таблиці 1.9
Розширений
сценарій

Таблиця 1.10 – Опис варіанти використання UC-10


Назва Встановлення кольору для задач
Опис Користувач встановлює колір для задач різного типу
Актор Користувач
Передумови Користувач має авторизований профіль у додатку та
авторизований в системі
Постумови Користувач встановив колір для задач різного типу
Основний 1) На сторінці «Налаштування» користувач обирає
сценарій «Обрати колір»
2) Користувач обирає колір для різного типу задач,
що буде відображатися при створенні цих задач
3) Користувач натискає «Готово»
Розширений
сценарій

1.4.1 Розроблення функціональних вимог

Базуючись на складених варіантах використання в пункті 1.4 дипломної


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

Таблиця 1.11 – Опис функціональної вимоги RQ-1


Унікальний ідентифікатор RQ-1
Назва Вхід в мобільний додаток

Арк.

КПІ.ІП-7219.045490.02.81 29
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 1.11
Опис Система надає можливість користувачеві,
який ще не авторизувався в мобільному
додатку, увійти до системи натиснувши
кнопку «Увійти» та виконати вказівки
авторизації до Google акаунту

Таблиця 1.12 – Опис функціональної вимоги RQ-2


Унікальний ідентифікатор RQ-2
Назва Додавання нової задачі
Опис Система надає користувачеві, який вже є
авторизованим в мобільному додатку,
можливість додавати нову задачу.

Таблиця 1.13 – Опис функціональної вимоги RQ-3


Унікальний ідентифікатор RQ-3
Назва Додання нової задачі з фіксованим часом
виконання
Опис Система надає користувачеві, який вже є
авторизованим в мобільному додатку,
можливість додавати нову задачу та
можливість встановлення для цієї задачі
фіксованого часу виконання.

Таблиця 1.14 – Опис функціональної вимоги RQ-4


Унікальний ідентифікатор RQ-4
Назва Додання нової задачі з нефіксованим
часом виконання

Арк.

КПІ.ІП-7219.045490.02.81 30
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 1.14
Опис Система надає користувачеві, який вже є
авторизованим в мобільному додатку,
можливість створювати задачу, для якої
неважливий конкретний час виконання та
запропоновувати варіанти, коли цю
задачу може виконати користувач.

Таблиця 1.15 – Опис функціональної вимоги RQ-5


Унікальний ідентифікатор RQ-5
Назва Додання нової задачі з розкладу задач
Опис Система надає можливість користувачеві,
який вже є авторизованим в мобільному
додатку, додавати до списку запланованих
задач предмет із розкладу, який
встановлюватиметься автоматично
протягом семестру.

Таблиця 1.16 – Опис функціональної вимоги RQ-6


Унікальний ідентифікатор RQ-6
Назва Встановлення зайнятих годин
Опис Система надає можливість користувачеві,
який вже є авторизованим в мобільному
додатку, встановлювати спеціальні
години, коли користувач зайнятий та не
буде ставити заплановані задачі на цей
час.

Арк.

КПІ.ІП-7219.045490.02.81 31
Змн. Арк. № докум. Підпис Дата
Таблиця 1.17 – Опис функціональної вимоги RQ-7
Унікальний ідентифікатор RQ-7
Назва Очищення календаря
Опис Система надає можливість користувачеві,
який вже є авторизованим в мобільному
додатку, очистити календар запланованих
задач.

Таблиця 1.18 – Опис функціональної вимоги RQ-8


Унікальний ідентифікатор RQ-8
Назва Імпортування календаря з Google акаунту
Опис Система надає можливість користувачеві
імпортувати в мобільне застосування
задачі, що вже були додані в інші
календарі акаунту Google

Таблиця 1.19 – Опис функціональної вимоги RQ-9


Унікальний ідентифікатор RQ-9
Назва Вихід з мобільного додатку
Опис Система надає можливість користувачеві,
який вже є авторизованим в мобільному
додатку, вийти з додатку, щоби потрапити
на сторінку авторизації – наприклад, для
авторизації з іншими даними.

Таблиця 1.20 – Опис функціональної вимоги RQ-10


Унікальний ідентифікатор RQ-10
Назва Встановлення кольору для задач різного
типу

Арк.

КПІ.ІП-7219.045490.02.81 32
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 1.20
Опис Система надає можливість користувачеві,
який авторизованим в мобільному
застосунку, обрати різний колір для задач
різного типу, для зручності при виборі
оптимального розкладу.

Порівнявши варіанти використання та функціональні вимоги було


встановлено певні залежності між ними, які можна зобразити у вигляді матриці
трасування (рис. 1.6).

Рисунок 1.6 – Матриця трасування між варіантами використання та


функціональними вимогами до системи

1.4.2 Розроблення нефункціональних вимог

Мобільний застосунок, що було розроблено в рамках дипломного


проєкту є клієнт-серверним мобільним застосунком для керування процесом
особистого часу. Застосунок розроблений відповідно до наступних
нефункціональних вимог:
− інтерфейс має містити українську версію, як основну, або
додаткову, а також англійську локалізацію,
Арк.

КПІ.ІП-7219.045490.02.81 33
Змн. Арк. № докум. Підпис Дата
− підтримка операційної системи IOS версії вищу за 9,
− інтуїтивно зрозумілий інтерфейс користувача,
− мобільне застосування не має бути переповнений надлишковою
інформацією,
− дотримання балансу кольорів та не бути перенасиченим кольоровим
наповненням,
− єдиний стиль та шаблон сторінок при реалізації інтерфейсу
мобільного застосунку.

1.4.3 Постановка комплексу завдань модулю

Метою даної розробки є створення такого мобільного застосування, який


буде корисним користувачеві для керування особистими задачами. Для цього в
застосунку має бути можливість пропонування оптимального для користувача
часу виконання задач, які не потребують фіксованого часу виконання.
Проаналізувавши варіанти використання, функціональні та
нефункціональні вимоги, що було розроблено, було сформульовано наступні
задачі для розробки продукту:
− авторизація користувача;
− додавання задач з фіксованим часом;
− додавання задач з нефіксованим часом,
− додавання розкладу;
− встановлення зайнятих годин користувача;
− очищення календаря;
− імпортування календаря;
− перегляд задач.

Арк.

КПІ.ІП-7219.045490.02.81 34
Змн. Арк. № докум. Підпис Дата
1.5 Висновки по розділу

У першому розділі дипломного проєкту було проаналізовано наявні


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

Арк.

КПІ.ІП-7219.045490.02.81 35
Змн. Арк. № докум. Підпис Дата
2 МОДЕЛЮВАННЯ ТА КОНСТРУЮВАННЯ ПРОГРАМНОГО
ЗАБЕЗПЕЧЕННЯ

2.1 Моделювання та аналіз програмного забезпечення

Щоби реалізувати програмне забезпечення, необхідно детально


змоделювати основні процеси та визначати архітектуру мобільного застосунку,
який розробляється. Всі ці процеси зображені у вигляді діаграм діяльності,
тобто основних процесів, які виконуються в роботі застосування, а саме:
− авторизація користувача (рис. 2.1);
− додавання нової фіксованої задачі та/або розкладу (рис. 2.2);
− додавання нової не фіксованої задачі (рис. 2.3);
− встановлення зайнятих годин (рис. 2.4);
− встановлення інформації про навчальний семестр (рис.2.5);

Рисунок 2.1 – схема структурна діяльності процесу авторизації користувача за


допомогою Google акаунту

Арк.

КПІ.ІП-7219.045490.02.81 36
Змн. Арк. № докум. Підпис Дата
Послідовний опис процесу авторизації користувача з Google календарем:
− користувачу відображається головна сторінка;
− користувач натискає кнопку «Login»;
− відкривається сторінка в Google;
− користувач надає доступ до своїх даних;
− якщо користувач успішно авторизувався, то йому надається доступ до
застосунку.

Рисунок 2.2 – схема структурна діяльності процесу додавання користувачем


задачі з фіксованим часом / розкладу

Послідовний опис процесу додавання користувачем задачі з фіксованим


часом / розклад:
− користувач переходить на сторінку та обирає варіант додати нову задачу з
фіксованим часом / додати розклад;
− користувач заповнює відповідні поля та відправляє їх;
− задача додається до списку запланованих.

Арк.

КПІ.ІП-7219.045490.02.81 37
Змн. Арк. № докум. Підпис Дата
Рисунок 2.3 – схема структурна діяльності процесу додавання користувачем
задачі з не фіксованим часом

Послідовний опис процесу додавання користувачем задачі з не фіксованим


часом:
− користувач переходить на сторінку та обирає варіант додати нову
задачу з не фіксованим часом
− користувач заповнює відповідні поля та відправляє їх;
− алгоритм аналізує, наскільки важливою є задача та пропонує
рекомендований час для її виконання;
− користувач обирає оптимальний для себе варіант;
− задача додається до списку запланованих.

Арк.

КПІ.ІП-7219.045490.02.81 38
Змн. Арк. № докум. Підпис Дата
Рисунок 2.4 – схема структурна діяльності процесу оновлення особистих годин
користувача

Послідовний опис процесу оновлення користувачем своїх особистих годин:


− користувач переходить на сторінку налаштування та обирає
налаштування особистих годин;
− користувач вводить та/або змінює відповідні поля;
− відправляє та зберігає змінені дані;

Рисунок 2.5 – схема структурна діяльності процесу очищення календаря

Послідовний опис очищення календаря:


− користувач переходить на сторінку налаштування
− користувач обирає функцію очищення календаря
Арк.

КПІ.ІП-7219.045490.02.81 39
Змн. Арк. № докум. Підпис Дата
− календар очищується

2.2 Архітектура програмного забезпечення

Для розробки мобільного застосунку для керування особистими


задачами, який використовує клієнт-серверну архітектуру були обрані наступні
технології та мови програмування: для клієнтської частини (frontend) був
обраний фреймворк React Native, а для серверної частини програмну
платформу Node.js., а також платформа для розробки мобільних застосунків
Firebase.

Рисунок 2.6 – Зображення структури мобільного застосування

Зображення структури мобільного додатку представлено на рис. 2.6. Вона


містить інформацію про те, що основна функціональна частина реалізована
саме на клієнтській частині додатку. Класи відповідних сторінок, що
реалізовані у додатку розширюють вже наявний клас React.PureComponents.
Тобто створюються нові класи на основі існуючого, і цей новий клас розширює
(extends) можливості старого.

Арк.

КПІ.ІП-7219.045490.02.81 40
Змн. Арк. № докум. Підпис Дата
Також є зв’язок з серверною частиною додатку, яка з’єднується з базою
даних SQLite, а також основні маршрути для передачі даних в додатку.
Для наочності взаємодії всіх компонентів системи, а саме клієнтської,
серверної частини, а також бази даних та Google систем, всі процеси, що
відбуваються в мобільному застосунку можна зобразити у вигляді діаграми
послідовності (рис. 2.7).

Рисунок 2.7 – Діаграма послідності процесів мобільного застосування

Арк.

КПІ.ІП-7219.045490.02.81 41
Змн. Арк. № докум. Підпис Дата
2.2.1 React Native

React Native – це відомий та популярний фреймворк для створення


мобільних додатків на базі мови програмування JavaScript. З React Native
можна створювати мобільні застосунки для різних платформ: iOS та Android.
Фреймворк дозволяє створювати програми для різних платформ з одної
сукупності коду.
При виборі технології для використання при розробці мобільного
застосування, особливу увагу було надано наступним аспектам:
− Можливість використання коду для декількох платформ з єдиною
кодовою базою
Хоча й в рамках дипломного проєкту було зроблено основний нахил на
розробку iOS застосунку, проте у майбутньому, проєкт може бути
масштабований на платформу Android без додаткових зусиль при переносі
проєкту.
− Можливості створення якісного користувацього інтерфейсу
Оскільки застосунок, що розроблявся, є клієнт-серверним, основний
нахил було зроблено саме користувацького інтерфейсу, оскільки на ринку
представлена велика кількість застосунків схожої тематики – необхідно
вирізнятися зручним та нативним користувацьким інтерфейсом.
− Простота та швидкість творення інтерфейсів
React Native надає досить велику кількість компонентів, як-от
компоненти для створення зображень, тексту, введення інформації з клавіатури,
створення списків, анімації, буфера обміну, посилань тощо. Дані елементи
прискорюють процес розробки застосувань. А також React Native має функцію
«Hot Reloading», яка дозволяє перезавантажити програму без необхідності
перекомпільовувати весь код. А бібліотеки React Native, такі як Redux (для
обробки стану вашого застосування) і Awesome React Native (список
компонентів і демонстрацій), також можуть допомогти швидше виконати
розробку мобільного застосування.
Арк.

КПІ.ІП-7219.045490.02.81 42
Змн. Арк. № докум. Підпис Дата
Проаналізувавши різні технології і фреймворки, особливу увагу було
надано саме React Native для використання при розробці програмного
забезпечення в рамках дипломного проєкту.

2.2.2 Node.js

Node.js – це середовище виконання коду, що написано на мові JavaScript


поза браузером. Програма, що була написана на мові програмування JavaScript,
може бути легко запущена в Node.js, оскільки середовище містить всі необхідні
інструменти для цього.
При розробці дипломного проєкту було використано фреймворк Express.
Цей веб-фреймворк є мінімалістичним у впровадженні та має гнучкі
можливості для додатків Node.js. Node.js дозволяє швидко і легко створити
надійний API, оскільки він має велику кількість спеціальних вбудовних методів
HTTP, а також можливості обробки даних.

2.2.3 Firebase

Firebase є спеціальною програмною платформою для розробки


застосувань, що надає велику функціональність при роботі з нею. Вона містить
серверну частину, базу даних, хостинг, автентифікацію в одній платформі.
Розробникам надається спеціальне Realtime Firabase API, яке виконує
синхронізацію даних і зберігає їх в хмарному сховищі.
Мобільне застосування під’єднується до бази даних через WebSocket,
який відповідає за синхронізацію даних протягом усього сеансу[10].

2.3 Конструювання програмного забезпечення

У цьому розділі наведено опис структур та методів основних класів, що


наявні у дипломному проєкті. Більшість процесів знаходиться на стороні веб-

Арк.

КПІ.ІП-7219.045490.02.81 43
Змн. Арк. № докум. Підпис Дата
клієнта, оскільки у програмі використовується Google API для з’єднання з
календарем Google.
Таблиця 2.1 – Опис класів системи
Клас Метод Опис
Home signIn() Авторизація
користувача за
допомогою
облікового запису
Google
FixedEvent dateTimeStateVerification() Перевірка, чи
однакові дати після
зміни дати, і
встановлює час, якщо
потрібно.
FixedEvent dateTimeVerification() Перевірка діапазону
дат на коректність.
FixedEvent recurrenceOnClick() Відкриття меню дій
iOS, щоби встановити
повторення.
FixedEvent skip() Якщо користувач хоче
перейти до
наступного екрану, не
вводячи жодної
інформації.
FixedEvent fieldValidation() Перевірка поля назви
задачі, дати початку
та часу завершення.
FixedEvent nextScreen() Додавання задачі до
календаря

Арк.

КПІ.ІП-7219.045490.02.81 44
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 2.1
FixedEvent addAnotherEvent() Додавання події до
календаря та очищає
поля.
FixedEvent resetField() Очищення полів
форми
NonFixedEvent skip() Якщо користувач хоче
перейти до
наступного екрану, не
вводячи жодної
інформації.
NonFixedEvent fieldValidation() Перевірка полів Назва
задачі та тривалість
NonFixedEvent nextScreen() Додавання задачі до
календаря
NonFixedEvent addAnotherEvent() Додавання події до
календаря та
очищення полів.
NonFixedEvent resetField() Очищає поля форми.
Course dayOfWeekOnClick() Відкриття меню дій
iOS, щоби встановити
повторення.
Course fieldValidation() Перевірка поля Назва
дисципліни.
Course nextScreen() Додавання
дисципліни до
календаря.

Арк.

КПІ.ІП-7219.045490.02.81 45
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 2.1
Course addAnotherEvent() Додавання події до
календаря та
очищення полів.
Course resetField() Очищення полів
форми.
Dashboard navigateEditScreen() Перехід на
відповідний екран для
редагування.
Dashboard changeInfo() Редагування
інформації по
задачам.
Dashboard navigateEditScreen() Перехід на
відповідний екран для
редагування.
ReviewEvent deleteEvent() Видалення задачі з
попереднього списку.
ReviewEvent updateInformation() Оновлення задачі з
попереднього списку.
ReviewEvent navigateEditScreen() Перехід на
відповідний екран для
редагування.
ReviewEvent navigateCreationScreen() Перехід на
відповідний екран для
створення
ScheduleCreation navigateToSelection() Перехід на наступний
екран мобільного
застосунку.

Арк.

КПІ.ІП-7219.045490.02.81 46
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 2.1
ScheduleEvent setEventBlock() Розміщення блоків з
задачами.
Schedule createTimes() Створює часові
інтервали між двома
рядками відповідно до
подій, які є в
календарі.
Schedule createLines() Метод для
динамічного
створення ліній.
SchoolInformation fieldValidation() Перевірка полів, які
були заповнені.
SchoolInformation saveInformation() Зберігає інформацію у
redux та повертається
до попереднього
екрана, якщо дані
перевірено успішно
UnavailableHours next() Перехід до
наступного екрану.
ImportCalendar getCalendars() Отримує інформацію
про календарі
користувача та
зберігає їх в стані.
ImportCalendar _onPressItem() Зворотня функція,
коли елемент
CalendarItem
використовується.

Арк.

КПІ.ІП-7219.045490.02.81 47
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 2.1
ImportCalendar _renderItem() Функція візуалізації
для елементів у
flatList.
ImportCalendar getListIdSelected() Повертає список
ідентифікаторів
календаря, вибраних
із списку flatList.
ImportCalendar importEvents() Імпортує всі події до
календаря зі списку
вибраного календаря.

Діаграма класів системи представлена на рис. 2.8. Вона містить класи, які
створюють структуру застосування та наслідують компоненту клас
React.PureComponents, розширюючи її можливості та функціональність.

Рисунок 2.8 – Схема структурна класів програмного забезпечення

Арк.

КПІ.ІП-7219.045490.02.81 48
Змн. Арк. № докум. Підпис Дата
2.4 Опис бази даних

В таблицях 2.2–2.6 наведено опис таблиць бази даних, які


використовуються в проєкті. ER-діаграма представлення бази даних наведена в
додатку КПІ.ІП-7219.045490.07.99.СБД у вигляді графічного матеріалу.

Таблиця 2.2 – User


Опис таблиці
Поле Опис Тип даних Ключ
ID Ідентифікатор TEXT PRIMARY
користувача KEY
EMAIL Електронна TEXT
адреса
користувача
FULLNAME Повне ім’я TEXT
користувача
SERVERAUTHCODE Спеціальний TEXT
код
авторизації
CALENDARID Ідентифікатор TEXT
календаря
FIREBASEID Токен TEXT
ідентифікатора
Firebase
ACCESSTOKEN Токен TEXT
ідентифікатора
REFRESHTOKEN Токен TEXT
ідентифікатора

Арк.

КПІ.ІП-7219.045490.02.81 49
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 2.2
PHOTOURL Посилання на TEXT
фотографію
користувача
ACTIVE Активність BOOLEAN
сесії
користувача
CREATED Дата datetime
створення
акаунту
UPDATED Дата datetime
останньої
зміни

Таблиця 2.3 – EventType


Опис таблиці
Поле Опис Тип даних Ключ
ID Ідентифікатор INTEGER PRIMARY
типу задачі KEY
CATEGORY Категорія INTEGER
задачі

Таблиця 2.4 – UserEvent


Опис таблиці
Поле Опис Тип даних Ключ
ID Ідентифікатор INTEGER PRIMARY
заходу KEY
користувача

Арк.

КПІ.ІП-7219.045490.02.81 50
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 2.4
USERID Ідентифікатор TEXT FOREIG
користувача N KEY
CATEGORY Категорія TEXT FOREIG
задачі N KEY
START Час та дата DATE
початку задачі
ALLDAY Чи триває BOOLEAN
задача весь
день?
END Час та дата DATE
закінчення
задачі
(дедлайн)
SUMMARY Назва задачі TEXT
RECURRENCE Чи TEXT
повторюється
задача. Якщо
так, то з яким
періодом
LOCATION Локація TEXT
виконання
задачі
DESCRIPTION Опис задачі TEXT
ACTIVE Чи активна BOOLEAN
задача?
CREATED Дата та час DATE
створення
задачі

Арк.

КПІ.ІП-7219.045490.02.81 51
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 2.4
UPDATED Дата та час DATE
оновлення
задачі

Таблиця 2.5 – UnavailableHours


Опис таблиці
Поле Опис Тип даних Ключ
ID Ідентифікатор INTEGER FOREIGN KEY
зайнятих
годин
користувача
USERID Ідентифікатор TEXT COMPOSITE
користувача PRIMARY
KEY
WEEK Тиждень для BOOLEAN COMPOSITE
зайнятих PRIMARY
годин KEY
CATEGORY Категорія TEXT COMPOSITE
зайнятих PRIMARY
годин KEY
START Час початку DATE
зайнятих
годин
END Час кінця DATE
зайнятих
годин

Арк.

КПІ.ІП-7219.045490.02.81 52
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 2.5
CREATED Дата та час datetime
створення
зайнятих
годин
UPDATED Дата та час datetime
оновлення
зайнятих
годин

Таблиця 2.6 – GeneratedCalendars


Опис таблиці
Поле Опис Тип даних Ключ
ID Ідентифікатор INTEGER PRIMARY
календаря KEY
USERID Ідентифікатор TEXT FOREIGN KEY
користувача
START Час початку DATE
задачі
END Час кінця DATE
задачі
SUMMARY Опис TEXT
SELECTED Чи обраний BOOLEAN
даний
календар
ALLDAY Чи BOOLEAN
виконується
задача весь
день

Арк.

КПІ.ІП-7219.045490.02.81 53
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 2.6
CREATED Дата та час datetime
оновлення
календаря
UPDATED Дата та час datetime
оновлення
календаря

2.5 Висновки по розділу

В цьому розділі було спроєктовано та проілюстровано основні процеси


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

Арк.

КПІ.ІП-7219.045490.02.81 54
Змн. Арк. № докум. Підпис Дата
3 АНАЛІЗ ЯКОСТІ ТА ТЕСТУВАННЯ ПРОГРАМНОГО
ЗАБЕЗПЕЧЕННЯ

3.1 Аналіз якості ПЗ

Для того, щоби проаналізувати якість та коректну роботу програмного


забезпечення, воно має бути детально протестованим. Цей етап є дуже
важливим у життєвому циклі розробки програмного забезпечення, оскільки він
дає можливість не лише контролювати якість готового продукту, але й
продуктивність системи, виконання всіх вимог, які були поставлені до
програмного забезпечення та додаткові особливості, які необхідно було
впровадити. Під час проведення процесу тестування програмного забезпечення
було проаналізовано різні частини програми, виходячи з наявного часу та
людських ресурсів, й визначити важливі проблеми, що потребують вирішення,
до процесу впровадження.
Щоби підтвердити, що мобільне застосування було виконано якісно,
використовують такі процеси, як верифікація та валідація.
Процес верифікація програмного забезпечення є статичною перевіркою
архітектури, документів та коду, також перевіркою того, чи було розроблено
програмне забезпечення відповідно до усіх вимог що ставилися до нього на
різних етапах проєктування. Верифікація відбувається без запуску програмного
коду.
Процес валідації програмного забезпечення є підтвердженням того, що
очікування та вимоги клієнта відповідають оцінці кінцевого продукту. Це
перевірка того, що програмне забезпечення було розроблено якісно. Також
валідація є наступним етапом тестування, після виконання верифікації
продукту.
При тестуванні особливу увагу необхідно зосередити на наступних
елементах:
− правильне зображення елементів інтерфейсу;
Арк.

КПІ.ІП-7219.045490.02.81 55
Змн. Арк. № докум. Підпис Дата
− правильна побудова елементів після виконання користувачем певних
дій;
− адаптивність програми;
− швидкість виконання програмного коду.

Часто при здійсненні тестування використовують спеціальний стандарт


ISO-9126, який визначає основні характеристики програмного забезпечення. Це
такі характеристики, як:
− функціональність;
− надійність;
− зручність;
− ефективність;
− супровід;
− портативність.

3.2 Опис процесів тестування

Під час тестування програмного забезпечення будуть проводитись


наступні функціональні типи тестів:
− тестування функціональності збереження результатів у базу даних;
− тестування функціональності з’єднання з Google календарем

Також під час тестування проводитиметься перевірка нефункціональних


типи тестів:
− тестування роботи інтерфейсу користувача;
− навантажувальне тестування сервера по обробці даних.

Арк.

КПІ.ІП-7219.045490.02.81 56
Змн. Арк. № докум. Підпис Дата
3.3 Опис контрольного прикладу

При тестуванні мобільного застосування, що було розроблено в рамках


дипломного проєктування, було перевірено всі функціональні вимоги, що були
описані в пункті 1.4. В таблицях 3.1-3.6 наведено опис контрольних прикладів
тестування.

Таблиця 3.1 – Тестування авторизації до мобільного додатку


Мета тесту Перевірка можливості
аутентифікації у застосунку.
Початковий стан Відкритий додаток.
Вхідні дані Google акаунт користувача.
Схема проведення тесту Відкрити додаток. На початковій
сторінці додатку натиснути на
кнопку «Login»; У відкритому вікні
ввести дані для аутентифікації з
Google акаунтом:
Користувач вводить власні дані
Google кабінету та натискає кнопку
«Підтвердити».
Очікуваний результат Мобільне застосування успішно
дає змогу авторизуватися
користувачеві та переадресує
головну сторінку мобільного
додатку
Стан програмного продукту після Користувач авторизований
проведення тестування

Арк.

КПІ.ІП-7219.045490.02.81 57
Змн. Арк. № докум. Підпис Дата
Таблиця 3.2 – Тестування додання задачі з не фіксованим часом
Мета тесту Перевірка можливості додання
задачі з не фіксованим часом.
Початковий стан Відкритий додаток, користувач
авторизований
Вхідні дані Відкрита сторінка «Додати задачу з
не фіксованим часом»
Схема проведення тесту Заповнюємо поля для додання
задачі з нефіксованим часом:
«Назва задачі», «Тривалість»,
«Дати», «Розділити на тиждень»,
«Кількість разів на тиждень», «Чи
кожен тиждень», «Пріоритет»,
«Локація», «Опис»; Користувач
натискає кнопку «Додати»;
Очікуваний результат Система запропонує користувачеві
декілька варіантів, куди можна
додати нову задачу. Користувач
обирає найбільш підходящий
варіант для нього.
Стан програмного продукту після Нова задача додається до
проведення тестування календаря.

Таблиця 3.3 – Тестування додання задачі з фіксованим часом


Мета тесту Перевірка можливості додання
задачі з фіксованим часом.
Початковий стан Відкритий додаток, користувач
авторизований

Арк.

КПІ.ІП-7219.045490.02.81 58
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 3.3
Вхідні дані Відкрита сторінка «Додати задачу з
фіксованим часом»
Схема проведення тесту Заповнюємо поля для додання
задачі з нефіксованим часом:
«Назва задачі», «Чи цілий день»,
«Час початку задачі», «Час кінця
задачі, «Локація», «Опис»
Очікуваний результат Система додає задачу до
календаря.
Стан програмного продукту після Нова задача додається до
проведення тестування календаря.

Таблиця 3.4 – Тестування додання задачі з розкладу занять


Мета тесту Перевірка можливості додання
задачі з розкладу занять.
Початковий стан Відкритий додаток, користувач
авторизований
Вхідні дані Відкрита сторінка «Додати розклад
занять»
Схема проведення тесту Заповнюємо поля для додання
задачі з нефіксованим часом:
«Назва дисципліни», « «Час
початку задачі», «Час кінця задачі,
«Локація», «Опис», «Тривалість
семестру»

Арк.

КПІ.ІП-7219.045490.02.81 59
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 3.4
Очікуваний результат Система додає задачу з розкладу
занять до календаря.
Стан програмного продукту після Нова задача додається до
проведення тестування календаря.

Таблиця 3.5 – Тестування очищення календаря


Мета тесту Перевірка очищення календаря.
Початковий стан Відкритий додаток, користувач
авторизований
Вхідні дані Відкрита сторінка «Налаштування»
Схема проведення тесту Натискаємо на кнопку «Очистити
календар»
Очікуваний результат Календар очищується.
Стан програмного продукту після Календар очищено.
проведення тестування

Таблиця 3.6 – Тестування встановлення зайнятих годин


Мета тесту Перевірка встановлення зайнятих
годин для подальшого
використання при плануванні задач
з не фіксованим часом.
Початковий стан Відкритий додаток, користувач
авторизований.
Вхідні дані Відкрита сторінка «Налаштування»

Арк.

КПІ.ІП-7219.045490.02.81 60
Змн. Арк. № докум. Підпис Дата
Продовження таблиці 3.6
Схема проведення тесту Заповнюємо поля, де вказуємо
коли користувач зайнятий та не
може виконувати задачі з не
фіксованим часом; Переходимо на
головну сторінку; Додаємо задачу з
не фіксованим часом.
Очікуваний результат Користувач успішно встановив
години, коли він зайнятий від задач
– щоби застосунок не міг
запропонувати йому поставити
туди задачу з не фіксованим часом;
Користувач додав задачу з не
фіксованим часом виконання, та
години, які він встановив як
зайняті не були йому
запропоновані.
Стан програмного продукту після Користувач додав задачу з не
проведення тестування фіксованим часом виконання, яка
урахувала його часи зайнятості.

3.4 Висновки до розділу

В цьому розділі було проаналізовано та протестовано якість розробки


мобільного застосунку, що було розроблено. При цьому було описано цілі,
сценарії та механізми тестування, що використовувалися для цього. Були
описані контрольні приклади тестування та процедура проведення тестів, а
також проаналізована коректність роботи застосунку при діях користувача.

Арк.

КПІ.ІП-7219.045490.02.81 61
Змн. Арк. № докум. Підпис Дата
4 ВПРОВАДЖЕННЯ ТА СУПРОВІД ПРОГРАМНОГО
ЗАБЕЗПЕЧЕННЯ

4.1 Розгортання програмного забезпечення

Мобільний застосунок, що було розроблено, має бути розгорнутим на


сервері, на якому попередньо встановлено таке програмне забезпечення, як:
Node.js, менеджер пакетів NPM, React.js.
Для розгортання мобільного застосунку необхідно виконати
завантаження всіх додаткових програмних модулів, а також додатково
завантажити модулі для запуску мобільного застосунку на iOS сумісних
пристроях. Оскільки серверна частина застосунку розгортається разом із
клієнтською, то не потрібно виконувати додаткових зусиль з його розгортання.
Діаграма розгортання системи представлена на рис. 4.1

Рисунок 4.1 – Діаграма розгортання мобільного застосування для керуваня


особистим часом

Арк.

КПІ.ІП-7219.045490.02.81 62
Змн. Арк. № докум. Підпис Дата
4.2 Робота з програмним забезпеченням

Робота з мобільним застосунком може бути з будь-яким iOS сумісним


мобільним пристроєм версії операційної системи не нижчу за iOS 9.0
Повну інструкцію роботи із клієнтською частиною програмного
забезпечення наведено у документі «Керівництво користувача».

4.3 Висновки до розділу

У цьому розділі описано процес впровадження та розгортання мобільного


застосунку з перерахуванням усіх вимоги до системи для успішного
розгортання проєкту та описані засоби для роботи з мобільним застосунком.

Арк.

КПІ.ІП-7219.045490.02.81 63
Змн. Арк. № докум. Підпис Дата
ВИСНОВКИ

При розробці дипломного проєкту було проведено аналіз теми контролю


особистого часу. Сьогодні, особистий та робочий час контролює більшість
людей, через перехід в онлайн та початок пандемії, оскільки мають поєднувати
та планувати багато справ, в тому числі й навчання, одночасно. Для більш
ефективного планування особистого часу використовуються спеціальні техніки
та методики.
В рамках виконання дипломного проєкту було розроблено мобільне
застосування для керування особистим часом. Це застосування дозволяє
користувачеві вносити керувати своїми задачами: створювати їх, з можливістю
пропонування оптимального часу виконання тих задач, які можуть бути
виконані в нефіксований час, в зручні для користувача часові проміжки.
Алгоритм знаходження вільного проміжку часу аналізує поточний розклад
користувача та пропонує слоти для виконання задачі, які базуються на
зайнятості користувача.
Мобільне застосування працює з Google акаунтом та синхронізується з
наявними календарями користувача.
При розробці мобільного застосунку було використано актуальні
технології, такі як: Node.js, React.js, Firebase, а в якості бази даних було
використано SQLite. Мобільний застосунок може працювати з усіма iOS
сумісними смартфонами.
Для перевірки коректності роботи мобільного застосунку та
підтвердження стандартів якості, було ретельно проаналізовано та
протестовано його якість та функціональність з виконанням низки тестів. В
результаті, всі заплановані тести було пройдено, а отже роботопридатність
застосунку перевірено.

Арк.

КПІ.ІП-7219.045490.02.81 64
Змн. Арк. № докум. Підпис Дата
ПЕРЕЛІК ПОСИЛАНЬ

1) 15 популярних технік тайм-менеджменту [Електронний ресурс] –


Режим доступу до ресурсу:
https://skillbox.ru/media/growth/15_populyarnykh_tekhnik_taym_menedzh
menta/
2) 5 essential time management techniques [Електронний ресурс] – Режим
доступу до ресурсу: https://memory.ai/timely-blog/time-management-
techniques
3) Метод Помидора. Управление временем, вдохновением и
концентрацией – Бомбора, 2018. – 224 с.
4) Bullet Journal метод. Переосмысли прошлое, упорядочи настоящее,
спроектируй будущее – Форс, 2019. – 336 с.
5) Принцип 80/20. Секрет досягнення більшого за менших витрат – КМ-
БУКС, 2019. – 400 с.
6) React.js. Быстрый старт – Питер Пресс, 2017. – 304 с.
7) React [Електронний ресурс] – Режим доступу:
https://reactjs.org/docs/getting-started.html
8) Express [Електронний ресурс] – Режим доступу:
https://metanit.com/web/nodejs/4.1.php
9) Firebase [Електронний ресурс] – Режим доступу:
https://firebase.google.com/
10) Що таке Firebase? [Електронний ресурс] – Режим доступу:
https://avada-media.ua/ua/services/firebase/
11) React и Redux: функциональная веб-разработка – Питер Пресс,
2018. – 336 с.

Арк.

КПІ.ІП-7219.045490.02.81 65
Змн. Арк. № докум. Підпис Дата
Факультет інформатики та обчислювальної техніки
Кафедра автоматизованих систем обробки інформації і управління

“ЗАТВЕРДЖЕНО”
В.о. завідувача кафедри
____________ Олександр ПАВЛОВ
“___” ___________________ 2021 р.

Мобільне застосування для контролю особистого часу


Технічне завдання
КПІ.ІП-7219.045490.03.91

“ПОГОДЖЕНО”
Керівник проєкту:
_______________ І.І. Вітковська

Нормоконтроль: Виконавець:
________________ І.І. Вітковська ________________Є.О. Нестеренко

Київ – 2021 року


ЗМІСТ

1 НАЙМЕНУВАННЯ ТА ГАЛУЗЬ ЗАСТОСУВАННЯ ........................................... 3

2 ПІДСТАВА ДЛЯ РОЗРОБКИ .................................................................................... 4

3 ПРИЗНАЧЕННЯ РОЗРОБКИ ................................................................................... 4

4 ВИМОГИ ДО ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ ............................................. 6

4.1 ВИМОГИ ДО ФУНКЦІОНАЛЬНИХ ХАРАКТЕРИСТИК/ ..................................................6


4.2 ВИМОГИ ДО НАДІЙНОСТІ ..........................................................................................6
4.3 УМОВИ ЕКСПЛУАТАЦІЇ .............................................................................................7
4.4 ВИМОГИ ДО СКЛАДУ І ПАРАМЕТРІВ ТЕХНІЧНИХ ЗАСОБІВ ........................................7
4.5 ВИМОГИ ДО ІНФОРМАЦІЙНОЇ ТА ПРОГРАМНОЇ СУМІСНОСТІ ....................................7
4.6 ВИМОГИ ДО МАРКУВАННЯ ТА ПАКУВАННЯ..............................................................8
4.7 ВИМОГИ ДО ТРАНСПОРТУВАННЯ ТА ЗБЕРІГАННЯ ....................................................8

4.8 СПЕЦІАЛЬНІ ВИМОГИ ................................................................................................8

5 ВИМОГИ ДО ПРОГРАМНОЇ ДОКУМЕНТАЦІЇ ................................................. 9

6 СТАДІЇ І ЕТАПИ РОЗРОБКИ................................................................................. 10

7 ВИМОГИ ДО ПРОГРАМНОЇ ДОКУМЕНТАЦІЇ ............................................... 11

Арк.

КПІ.ІП-7219.045490.03.91 2
Змн. Арк. № докум. Підпис Дата
1 НАЙМЕНУВАННЯ ТА ГАЛУЗЬ ЗАСТОСУВАННЯ

Назва розробки: Мобільне застосування для контролю особистого часу


Галузь застосування: Наведене технічне завдання поширюється на
розробку програмного забезпечення «Мобільне застосування для контролю
особистого часу» КПІ.ІП-7219.045490.03.91, котре використовується для
забезпечення процесу керування особистим часом задачами декількома
методиками. Застосування може використовуватися студентами, оскільки в
ньому буде можливість синхронізації з розкладом та керування особистим
часом, базуючись на зайнятість на навчання.

Арк.

КПІ.ІП-7219.045490.03.91 3
Змн. Арк. № докум. Підпис Дата
2 ПІДСТАВА ДЛЯ РОЗРОБКИ

Підставою для розробки «Мобільне застосування для контролю


особистого часу» є завдання на дипломне проектування, затверджене кафедрою
автоматизованих систем обробки інформації та управління Національного
технічного університету України «Київський політехнічний інститут» ім. Ігоря
Сікорського (КПІ ім. Ігоря Сікорського).

Арк.

КПІ.ІП-7219.045490.03.91 4
Змн. Арк. № докум. Підпис Дата
3 ПРИЗНАЧЕННЯ РОЗРОБКИ

Розробка призначена для будь-яких користувачів, які хочу планувати свій


особистий час, базуючись на задачах, що були сплановані попередньо.
Метою даної розробки є організація процесу керування задачами та
особистим часом, навантаженням, які отримують користувачі з переходом на
повністю, або частково віддалену роботу. Кількість задач, які необхідно
виконати зростоє, а тому користувачам все складніше стає спланувати свій час
так, щоби це було оптимально з боку інших задач. Мобільне застосування, що
розробляється повинно запропонувати користувачеві часові проміжки, в які він
може виконати задачу, яка постала перед ним.

Арк.

КПІ.ІП-7219.045490.03.91 5
Змн. Арк. № докум. Підпис Дата
4 ВИМОГИ ДО ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ

4.1 Вимоги до функціональних характеристик

4.1.1 Програмне забезпечення повинно забезпечувати виконання


наступних основних функцій:
4.1.1.1 Для користувача:

- авторизація в системі за допомогою профілю Google;

- додавання задач з фіксованим часом;

- додавання задач з нефіксованим часом,

- додавання розкладу;

- встановлення зайнятих годин користувача;

- очищення календаря;

- перегляд задач;

- імпортування календаря.
4.1.2 Розробка має підтримувати операційну систему iOS, версії не нижчу
за 9.0
4.1.3 Додаткові вимоги:
− забезпечити сталий зв’язок мобільного додатку із сервером

4.2 Вимоги до надійності

4.2.1 Передбачити контроль введення інформації.


4.2.2 Передбачити захист від некоректних дій користувача.
4.2.3 Забезпечити цілісність інформації в базі даних
4.2.4 Відсутність збереження особистих даних користувачів та відео/аудіо
контенту.

Арк.

КПІ.ІП-7219.045490.03.91 6
Змн. Арк. № докум. Підпис Дата
4.3 Умови експлуатації

4.3.1 Умови експлуатації згідно СанПін 2.2.2.542 – 96.


Не висуваються.
4.3.2 Обслуговування
Не висуваються.
4.3.3 Обслуговуючий персонал
Не висуваються.

4.4 Вимоги до складу і параметрів технічних засобів

4.4.1 Програмне забезпечення повинно функціонувати на мобільних


пристроях, які підтримують платформу IOS
4.4.2 Мінімальна конфігурація технічних засобів:
4.4.2 Мінімальна конфігурація технічних засобів:
4.4.2.1 Об‘єм ОЗП: 8 Гб

4.5 Вимоги до інформаційної та програмної сумісності

4.5.1 Програмне забезпечення повинно працювати під управлінням


операційних систем сімейства iOS
4.5.2 Вхідні дані повинні бути представлені в наступному форматі: HTTP-
запити на сервер з форматом даних типу JSON і методами GET, POST, PUT,
DELETE, а також ввід користувача.
4.5.3 Результати повинні бути представлені в наступному форматі:
відповіді на HTTP-запити у вигляді JSON, візуальний інтерфейс мобільного
додатку.
4.5.4 Програмне забезпечення повинно бути реалізовано з використанням
фреймворку React Native та NodeJS. БД – SQLite.

Арк.

КПІ.ІП-7219.045490.03.91 7
Змн. Арк. № докум. Підпис Дата
4.6 Вимоги до маркування та пакування

Вимоги до маркування та пакування не пред‘являються.

4.7 Вимоги до транспортування та зберігання

Вимоги до транспортування та зберігання не пред'являються.

4.8 Спеціальні вимоги

Згенерувати установчу версію консольної утиліти програмного


забезпечення.

Арк.

КПІ.ІП-7219.045490.03.91 8
Змн. Арк. № докум. Підпис Дата
5 ВИМОГИ ДО ПРОГРАМНОЇ ДОКУМЕНТАЦІЇ

5.1 Програмні модулі, котрі розробляються, повинні бути


задокументовані, тобто тексти програм повинні містити всі необхідні
коментарі.

5.2 Програмне забезпечення повинно мати довідникову систему.

5.3 У склад супроводжувальної документації повинні входити наступні


документи:

5.3.1 Пояснювальна записка не менше ніж на 50 аркушах формату А4


(без додатків 5.3.2 - 5.3.4).
5.3.2 Технічне завдання.
5.3.3 Керівництво користувача.
5.3.4 Програма та методика тестування.

5.4 Графічна частина повинна бути виконана у формату А3, котрі


включаються у якості додатків до пояснювальної записки:

5.4.1 Схема бази даних


5.4.2 Схема структурна діяльності
5.4.3 Креслення вигляду екранних форм
5.4.3 Схема структурна класів програмного забезпечення

Арк.

КПІ.ІП-7219.045490.03.91 9
Змн. Арк. № докум. Підпис Дата
6 СТАДІЇ І ЕТАПИ РОЗРОБКИ

Стадії та єтапи розробки наведені у таблиці 6.1.


Таблиця 6.1 - Стадії та єтапи розробки
№ Назва етапу Строк Звітність
1 Вивчення рекомендованої 18.03.2021
літератури
2 Розробка технічного завдання 23.03.2021 Технічне завдання
3 Аналіз вимог та уточнення 28.03.2021 Схема структурна
специфікацій послідовностей та схема
структурна компонентів
4 Проектування структури 20.04.2021 Схема структурна
програмного забезпечення, програмного забезпечення
проектування компонентів та специфікація
компонентів (діаграма
класів, схема алгоритму
…)
5 Програмна реалізація 07.05.2021 Тексти програмного
програмного забезпечення забезпечення
6 Тестування програмного 12.05.2021 Тести, результати
забезпечення тестування
7 Розробка матеріалів текстової 15.05.2021 Пояснювальна записка.
частини проекту
8 Розробка матеріалів 18.05.2021 Графічний матеріал
графічної частини проекту проекту
9 Розробка програмного 20.05.2021 Технічна документація
забезпечення

Арк.

КПІ.ІП-7219.045490.03.91 10
Змн. Арк. № докум. Підпис Дата
7 ВИМОГИ ДО ПРОГРАМНОЇ ДОКУМЕНТАЦІЇ

7.1. Види випробувань

Тестування розробленого програмного продукту виконується відповідно


до “Програми та методики тестування”.

Арк.

КПІ.ІП-7219.045490.03.91 11
Змн. Арк. № докум. Підпис Дата
Факультет інформатики та обчислювальної техніки
Кафедра автоматизованих систем обробки інформації і управління

“ЗАТВЕРДЖЕНО”
В.о. завідувача кафедри
____________ Олександр ПАВЛОВ
“___” ___________________ 2021 р.

Мобільне застосування для контролю особистого часу


Опис програми
КПІ.ІП-7219.045490.06.13

“ПОГОДЖЕНО”
Керівник проєкту:
_______________ І.І. Вітковська

Виконавець:
Нормоконтроль:
________________Є.О. Нестеренко
________________ І.І. Вітковська

Київ – 2021 року


Тексти програмного коду
Мобільне застосування для контролю особистого часу
мережі
(Найменування програми (документа))

DVD-R
(Вид носія даних)

125 арк, 244 Кб


(Обсяг програми (документа) , арк.,) Кб)

Київ - 2021

Арк.

КПІ.ІП-719.045490.06.13 87
Змн. Арк. № докум. Підпис Дата
Тексти програмного коду
Мобільне застосування для контролю особистого часу
(Найменування програми (документа))
мережі

DVD-R
(Вид носія даних)

125 арк, 244 Кб


(Обсяг програми (документа) , арк.,) Кб)

Київ - 2021

Арк.

КПІ.ІП-7219.045490.06.13 2
Змн. Арк. № докум. Підпис Дата
const queryPerson = require('../informationbase/queries/queryPerson');
const queryUni = require('../informationbase/queries/queryUni');
const queryTask= require('../informationbase/queries/Tasksqueries');
const queryNonAvalHour= require('../informationbase/queries/nonAvalHour');
const helperCalen = require('../libraries/permissions');
const tokenGenerator = require('../services/googleTokenGeneration');
const kalendQueries = require('../informationbase/queries/kalendqueries');

const express = require('express');


const router = express.Router();
const request = require('request');

router.post('/api/lgPerson', async (req, res) = function() {


const { authCodeServer, accessToken} = req.body;
let {id, email, name, photo} = req.body.person;

queryPerson.getPerson(id).then(async (row) function () {


if (row) {
let columns = [fullname, email, photo];
let values = [name, email, photo, id];

queryPerson.updPerson(columns, values)
.then( () = function() {
req.session.personID = id;
res.send(true);
}).catch(err = function() {
res.send(false);
});
} else {
let tokenInformation = await
tokenGenerator.serverAuthentication(authCodeServer);
let { refresh_token, access_token } = tokenInformation;
let person = { id, name, email, photo, authCodeServer, accessToken:
access_token, refreshToken: refresh_token };

queryPerson.PutPerson(person)
.then(id = function() {
req.session.personID = id;
res.send(true);
})
.catch(err = function() {
console.log('err', err);
res.send(false);
});
}
})
.catch((err) = function() {
console.log(err);
res.send(false);
});
});

router.post('/api/updPerson', (req, res) = function() {


const { values, columns } = req.body;
const personID = req.session.personID;

queryPerson.updPerson(columns, [...values, personID])


.then( () = function() {
res.send(true);
}).catch(err = function() {
Арк.

КПІ.ІП-7219.045490.06.13 3
Змн. Арк. № докум. Підпис Дата
res.send(false);
});
});

router.post('/api/saveUniValue', (req, res) = function() {


const { startDate, endDate, value } = req.body;
const id = req.session.personID;
const uniID = value;
console.log('req.sessions.uni', req.session.uniValue)
let uniValue = {id, startDate, endDate, uniID};

queryUni.getUniValue(id, uniID)
.then((rows) = function() {
if (rows.length > 0) {
queryUni.updUniValue(uniValue)
.then(information = function() {
req.session.uniValue = information;
res.send(true);
return;
})
.catch(err = function() {
console.log('err', err);
res.send(false);
return err;
});
} else {
queryUni.PutUniValue(uniValue)
.then(information = function() {
req.session.uniValue = information;
res.send(true);
return;
})
.catch(err = function() {
console.log('err', err);
res.send(false);
return err;
});
}

}).catch((err) = function() {
res.send(false);
return err;
})
});

router.post('/api/savePutedKalends', async (req,res) => {

let promises = [];


req.body.forEach(Task = function() {
Task.personID = req.session.personID;
promises.push(TaskQueries.upsertTask(Task));
});

Promise.all(promises).then(() = function() {
res.send(true);
})
.catch(err = function() {
console.log('err storing Puted Kalends', err);
res.send(false);
Арк.

КПІ.ІП-7219.045490.06.13 4
Змн. Арк. № докум. Підпис Дата
});

});

router.post('/api/saveGeneratedKalends’, async (req,res) => {


console.log('length', req.body.length);
let promises = [];
req.body.forEach(kalend = function() {
kalend.forEach(Task = function() {
Task.selected = (kalend.selected) ? kalend.selected: false
kalendQueries.PutTask(Task, req.session.personID);

})

promises.push(TaskQueries.upsertTask(Task));
});
});

router.post('/api/savePersonHour’, (req, res) = function() {


if (req.session.personID) {
let promises = [];

if(req.body.length > 0) {
req.body.forEach(hour = function() {
promises.push(nonAvalHourQueries.upsertNonAvalHourValue(hour,
req.session.personID))
});
}

Promise.all(promises).then(() = function() {
res.send(true);
})
.catch(err = function() {
console.log('err', err);
res.send(false);
});
} else {
res.send(false);
}

});

router.get('/api/getTasks', (req,res) => {


if (req.session.personID) {

TaskQueries.getTasks(req.session.personID)
.then(Tasks = function() {
res.send(Tasks);
});
}
});

router.get('/api/getPersonValue', (req,res) => {


if (req.session.personID) {
queryPerson.getPerson(req.session.personID)
.then(Value = function() {
res.send(Value);
});
}
});
Арк.

КПІ.ІП-7219.045490.06.13 5
Змн. Арк. № докум. Підпис Дата
router.post('/api/getPersonValues', (req,res) => {
if (req.session.personID) {
if (req.body) {
const { columns } = req.body;
queryPerson.getPersonValue(columns, req.session.personID)
.then(Value = function() {
res.send(Value);
});
}
}
});

router.post('/api/logOut', (req,res) => {


if (!req.session.personID) res.send(false)
else {
let id = req.session.personID;
req.session.personID = null;
res.send(id);
}

});

router.post('/api/getValueAboutPersonByColumns', (req,res) => {


let information = req.body;
queryPerson.getPersonValueSpecial(information.columns, information.where)
.then(Value = function() {
if(Value) res.send(Value);
else res.send(false);
});
});

router.post('/api/setKalendAccess', async (req,res) => {


if (!req.session.personID) {
res.send(false);
return;
}

let columns = ['KALENDID', 'ACCESSTOKEN'];


let information = req.body;
let promises = []

let requesterInformation = {
field:'EMAIL',
value: information.requester.email
}

let accepterInformation = {
field:'EMAIL',
value: information.accepter.email
}

let requester = await queryPerson.getPersonValueSpecial(columns, requesterInformation);


let accepter = await queryPerson.getPersonValueSpecial(columns, accepterInformation);

if (!requester || !accepter) {
res.send(false);
return;
}
Арк.

КПІ.ІП-7219.045490.06.13 6
Змн. Арк. № докум. Підпис Дата
let accepterPermission = await helperCalen.addPermissionPerson(information.accepter.email,
requester.KALENDID, requester.ACCESSTOKEN);
let requesterPermission = await helperCalen.addPermissionPerson(information.requester.email,
accepter.KALENDID, accepter.ACCESSTOKEN);

if (accepterPermission.etag && accepterPermission.etag) res.send(true);


else res.send(false);
});
module.exports = router;
const request = require('request');
let HelpApiCall = (URL, method, information) = function() {
let fetch = request.get;
let fetchInformation = {
url: URL,
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer ' + information.ACCESSTOKEN,
'Content-Type': 'application/json',
}
};

if (method == 'POST') {
fetchInformation.body = JSON.stringify(information.Value);
fetch = request.post
}

return new Promise((resolve) = function() {


fetch(fetchInformation, (error, response, body) = function() {
resolve(JSON.parse(body));
});
});
};

let listTasks = (kalendId, information, query) = function() {


return HelpApiCall('https://www.googleapis.com/kalend/v3/kalends/' + kalendId + '/Tasks', 'GET',
information, query);
};

let PutAccessRule = (kalendId, information, query) = function() {


return HelpApiCall('https://www.googleapis.com/kalend/v3/kalends/' + kalendId + '/acl', 'POST',
information, query);
};

module.exports = {
listTasks,
PutAccessRule
};
const request = require("request");
const client_secret = require("../config/client-secret");

let serverAuthentication = (code) = function() {


let information = {
code,
client_secret: client_secret.CLIENT_SECRET,
client_id: client_secret.CLIENT_ID,
grant_type: "authorization_code",
redirect_uri: "http://localhost",
Арк.

КПІ.ІП-7219.045490.06.13 7
Змн. Арк. № докум. Підпис Дата
access_type: 'offline'
};
return makeRequest(information);
}

let getNewAccessToken = (refresh_token) = function() {


let information = {
client_secret: client_secret.CLIENT_SECRET,
client_id: client_secret.CLIENT_ID,
refresh_token,
grant_type: "refresh_token"
};

return makeRequest(information);
}

let makeRequest = (information) = function() {


return new Promise(function(resolve, reject){
request({
url: "https://www.googleapis.com/oauth2/v4/token" + getQueryParam(information),
method: "POST"
},
function(error, response, body) {
if (error || response.statusCode !== 200) reject(error);

if (body) resolve(JSON.parse(body));
}
);
});
}

let getQueryParam = (query) = function() {


let text = "?";
let keys = Object.keys(query);

for (let i = 0; i < keys.length; i++) {


text += keys[i] + "=" + query[keys[i]] + "&";
}

return text.substr(0, text.length - 1);


}

module.exports = {
getNewAccessToken,
serverAuthentication
};

const theme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
primary: greenColor,
accent: orangeColor,
}
};

const LoginNavig = makeStackNavigator(


Арк.

КПІ.ІП-7219.045490.06.13 8
Змн. Арк. № докум. Підпис Дата
{
Home
},
{
headerMode: 'none',
initialRouteName: 'Home'
}
);

const InnerPageOptionsDashboard= {
headerTitleStyle: { fontFamily: 'Raleway-Regular' },
headerStyle: {
BgColor: green,
marginTop: Platform.OS === 'ios' ? 0 : StatusBar.currentHeightSize
},
headerBackTitle: null
};

const NavigatorOfDashboard= makeMaterialBottomTabNavigator(


{
Dashboard: {
Page: makeStackNavigator({
Dashboard: {
Page: Dashboard,
NavOptions: {
...dashboardInnerPageOptions
}
}
}),
NavOptions: ({Nav}) = function() {
return {
title: save.getState().BottomNavReducer.dashboardTitle,
tabBarIcon: ({ focused, tintColor }) = function() {
const iconName = `home-variant${focused ? '' : '-outline'}`;
return <MaterialCommunityIcons name={iconName} size={25}
color={tintColor} />;
},
};
}
},
Setting: {
Page: makeStackNavigator({
Setting: {
Page: Setting,
NavOptions: {
...dashboardInnerPageOptions
}
}
}),
NavOptions: ({Nav}) => ({
title: save.getState().BottomNavReducer.SettingTitle,
tabBarIcon: ({ focused, tintColor }) = function() {
const iconName = `Setting${focused ? '' : '-outline'}`;
return <MaterialCommunityIcons name={iconName} size={25}
color={tintColor} />;
},
})
}
},
{
Арк.

КПІ.ІП-7219.045490.06.13 9
Змн. Арк. № докум. Підпис Дата
initialRouteName: 'Dashboard',
barStyle: { BgColor: dark_green },
}
);

DashboardNavigator.NavOptions = {
header: null,
headerBackTitle: null,
};

const NavigatorSettingForDashboardOptions = {
headerTintColor: dark_green,
headerTitleStyle: {
fontFamily: 'Raleway-Regular'
},
headerStyle: {
marginTop: StatusBar.currentHeightSize
},
headerBackTitle: null,
};

const NavigatorSettingForDashboard = makeStackNavigator(


{
DashboardNavigator,
ScheduleOfUni: { Page: ScheduleOfUni, NavOptions: NavigatorSettingForDashboardOptions },
AddCourses: { Page: Courses, NavOptions: NavigatorSettingForDashboardOptions },
ScheduleOfUniSelectPicture: {
Page: ScheduleOfUniSelectPicture,
NavOptions: {
...NavigatorSettingForDashboardOptions,
headerTintColor: white,
headerStyle: {
...NavigatorSettingForDashboardOptions.headerStyle,
BgColor: 'rgba(0, 0, 0, 0.3)',
}
},
},
ScheduleOfUniTakePicture: {
Page: ScheduleOfUniTakePicture,
NavOptions: {
...NavigatorSettingForDashboardOptions,
headerTintColor: white,
headerStyle: {
...NavigatorSettingForDashboardOptions.headerStyle,
BgColor: 'rgba(0, 0, 0, 0.3)',
}
},
},
ScheduleOfUniCreation: {
Page: ScheduleOfUniCreation,
NavOptions: {
...NavigatorSettingForDashboardOptions,
headerTransparent: true,
title: '',
headerStyle: {
...NavigatorSettingForDashboardOptions.headerStyle,
BgColor: 'rgba(0, 0, 0, 0.3)',
}
}
},
Арк.

КПІ.ІП-7219.045490.06.13 10
Змн. Арк. № докум. Підпис Дата
DiktdTask: { Page: DiktdTask, NavOptions: NavigatorSettingForDashboardOptions },
NonDiktdTask: { Page: NonDiktdTask, NavOptions: NavigatorSettingForDashboardOptions },

ReviewTask: { Page: ReviewTask, NavOptions: NavigatorSettingForDashboardOptions },


EditCourses: { Page: Courses, NavOptions: NavigatorSettingForDashboardOptions },
EditDiktdTask: { Page: DiktdTask, NavOptions: NavigatorSettingForDashboardOptions },
EditNonDiktdTask: { Page: NonDiktdTask, NavOptions: NavigatorSettingForDashboardOptions },
CreationOfSchedule: {
Page: CreationOfSchedule,
NavOptions: {
...NavigatorSettingForDashboardOptions,
headerTransparent: true,
title: '',
headerStyle: {
...NavigatorSettingForDashboardOptions.headerStyle,
BgColor: 'rgba(0, 0, 0, 0.3)',
}
}
},
ScheduleSelection: {
Page: ScheduleSelection,
NavOptions: {
...NavigatorSettingForDashboardOptions,
headerTransparent: true,
headerTintColor: white,
headerStyle: {
...NavigatorSettingForDashboardOptions.headerStyle,
BgColor: 'rgba(0, 0, 0, 0.3)',
}
}
},
ScheduleSelectionValue: { Page: ScheduleSelectionValue, NavOptions:
NavigatorSettingForDashboardOptions },

CleanReducers: {
Page: CleanReducers,
NavOptions: {
...dashboardInnerPageOptions
},
},
UniValuermation: { Page: UniValuermation, NavOptions: NavigatorSettingForDashboardOptions },

NonAvalHour: { Page: NonAvalHour, NavOptions: NavigatorSettingForDashboardOptions },


DiktdUnavailable: { Page: DiktdTask, NavOptions: NavigatorSettingForDashboardOptions },
PermissionOfKalend: { Page: PermissionOfKalend, NavOptions:
NavigatorSettingForDashboardOptions }
},
{
initialRouteName: 'DashboardNavigator',
}
);

const MainNavigator = makeSwitchNavigator(


{
WelcomePage,
LoadPage,
NavigatorSettingForDashboard,
LoginNavig
},
Арк.

КПІ.ІП-7219.045490.06.13 11
Змн. Арк. № докум. Підпис Дата
{
initialRouteName: 'LoadPage'
}
);

const defaultGetStateForAction = NavigatorSettingForDashboard.router.getStateForAction;


NavigatorSettingForDashboard.router.getStateForAction = (action, state) = function() {

let routes = [
state.routes[0],
{key: '2',
routeName: 'ReviewTask',
params:{title: getStrings().ReviewTask.title}}];

return {
...state,
routes,
index: 1,
};
}

return defaultGetStateForAction(action, state);


};

const AppCont = makeAppCont(MainNavigator);

export default function Main() {


return (
<Provider save={save}>
<PersistGate Load={null}
persistor={persistor}>
<PaperProvider theme={theme}>
<AppCont />
</PaperProvider>
</PersistGate>
</Provider>
);
}

AppRegistry.registerComponent(appName, () => Main);

const moment = require('moment');

export const device_width = Dimensions.get('window')._width;


export const deviceHeightSize = Platform.OS === 'ios' ? Dimensions.get('window').HeightSize :
ExtraDimensions.getRealWindowHeightSize();

export const convertToDictionary = (information) = function() {


let dict = {};
information.forEach(item = function() {
if(dict[item.position.x] != undefined) {
dict[item.position.x].push(item);
} else {
dict[item.position.x] = [item];
}
});
return dict;
};

Арк.

КПІ.ІП-7219.045490.06.13 12
Змн. Арк. № докум. Підпис Дата
export const convertTasksToDictionary = async (information) = function() {
let kalendID = save.getState().KalendReducer.id;
let dict = {};

if (information == undefined) return;

information.forEach(async (Task) = function() {


if (Task.recurrence) {
await getTasksInstances(kalendID, Task.id).then(instances = function() {
instances.items.forEach(TaskRec = function() {
let item = {};
item.name = TaskRec.summary;
let keyDate = TaskRec.start.dateTime.divide('T')[0];
item.date = keyDate;
item.actualTime = TaskRec.start.dateTime;

item.time = `${convertLTSToSimple(TaskRec.start.dateTime)} -
${convertLTSToSimple(Task.end.dateTime)}`;
(dict[keyDate] != undefined) ? dict[keyDate].push(item) : dict[keyDate] =
[item];
});
});
} else {
let item = {};
let keyDate = Task.start.dateTime.divide('T')[0];
item.name = Task.summary;
item.date = keyDate;
item.actualTime = Task.start.dateTime;
item.time = `${convertLTSToSimple(Task.start.dateTime)} -
${convertLTSToSimple(Task.end.dateTime)}`;
(dict[keyDate] != undefined) ? dict[keyDate].push(item) : dict[keyDate] = [item];
}
});
return dict;
};

export const selectionSort = (arr) = function() {


let minIdx, temp, len = arr.length;

for (let i = 0; i < len; i++) {


minIdx = i;
for(let j = i+1; j<len; j++) {
let firstDate = new Date(arr[j].actualTime);
let secondDate = new Date(arr[minIdx].actualTime);

if(firstDate.getTime() < secondDate.getTime()) {


minIdx = j;
}
}
temp = arr[i];
arr[i] = arr[minIdx];
arr[minIdx] = temp;
}
return arr;
};

const convertLTSToSimple = (tempDate) = function() {


return new moment(tempDate).format('h:mm A');
Арк.

КПІ.ІП-7219.045490.06.13 13
Змн. Арк. № докум. Підпис Дата
};

export const formatInformation = (information) = function() {


let dict = convertToDictionary(information);
let Tasks = [];
for (const [key,value] of Object.entries(dict)) {
if(value.length > 1) {
let Task = {
Coursess: []
};
value.forEach(item = function() {
if((item.CoursesValue.length <= 2)) {
Task.day = item.CoursesValue[0];
} else {
if(item.CoursesValue.length > 4) {
let brokenInformation = item.CoursesValue[1];
item.CoursesValue[0] += brokenInformation;
item.CoursesValue.splice(1, 1);
}
let obj = {
name: item.CoursesValue[0],
time: item.CoursesValue[2],
location: item.CoursesValue[3]
};
Task.Coursess.push(obj);
}
});
Tasks.push(Task);
}
}
return new Promise( function(resolve, reject) {
if(Tasks.length == 0) {
reject(getStrings().ServicesError.formatDate);
} else {
resolve(Tasks);
}
});
};

export const getStartDate = (date, tempDay) = function() {


let StudyStart = new Date(date.getTime());
StudyStart.setTime(StudyStart.getTime()+StudyStart.getTimezoneOffset()*60*1000);
let startDay = StudyStart.getDay();

if(day < startDay) {


let diff = startDay - day;
StudyStart.setDate(StudyStart.getDate() + (7 - diff));
} else if (day > startDay) {
let diff = day - startDay;
StudyStart.setDate(StudyStart.getDate() + diff);
}

let newDate = new Date(StudyStart.getTime());


return newDate;
};

export function containsDateTime(dates, checkDate) {


let checkStartDate = checkDate.startDate;
let checkEndDate = checkDate.endDate;
Арк.

КПІ.ІП-7219.045490.06.13 14
Змн. Арк. № докум. Підпис Дата
let contains = false;
dates.forEach(date = function() {
let startDate = new Date(date.startDate);
let endDate = new Date(date.endDate);

if((checkStartDate >= startDate && checkStartDate <= endDate ) || (checkEndDate >= startDate &&
checkEndDate <= endDate)) {
contains = true;
return contains;
}
});
return contains;
}

export function getRndInteger(__min, __max) {


return Math.round(Math.rand() * (__max - __min) ) + __min;
}

export function getRandDate(start, end) {


let newDate = new Date(start + Math.rand() * (end - start));
return newDate;
}

export function divideDurartion(__Hour, __min, __occurence) {


let totalDurartion = (__Hour * 60) + __min;
let dividedDurartion = totalDurartion / __occurence;
let { Hour, min } = convertMinToHour(dividedDurartion);

return {Hour, min};


}

function convertMinToHour(__Durartion) {
const Hour = Math.floor(__Durartion / 60);
const min = __Durartion % 60;

return {Hour, min};


}

export const clearEveryReducer = () = function() {


const reducersDeleteOperations = [
clearCourses,
clearKalendID,
clearDiktdTasks,
clearNonDiktdTasks,
clearGeneratedNonDiktdTasks,
clearGeneratedKalends,
clearNav,
clearSchedule,
uniValuermationClear,
clearNonAvalHour,
logoffPerson
];

reducersDeleteOperations.map(action => save.dispatch(action()));


};

export const getStrings = () = function() {


const { lang } = save.getState().SettingReducer;
Арк.

КПІ.ІП-7219.045490.06.13 15
Змн. Арк. № докум. Підпис Дата
let lang = lang ? lang : 'en';

return strings[lang];
};

export const timeValidation = (startTime, endTime, time) = function() {


if (moment(time, 'h:mm A').isBefore(moment(startTime, 'h:mm A'))) {
return startTime;
} else if (moment(time, 'h:mm A').isAfter(moment(endTime, 'h:mm A'))) {
return endTime;
} else {
return time;
}
};

export const dateValidation = (startDate, endDate, date) = function() {


if (moment(date, 'ddd., MMM DD, YYYY').isBefore(moment(startDate, 'ddd., MMM DD, YYYY'))) {
return startDate;
} else if(moment(date, 'ddd., MMM DD, YYYY').isAfter(moment(endDate, 'ddd., MMM DD, YYYY'))) {
return endDate;

} else {
return date;
}
};

const strings = getStrings().ServicesError;

let serverUrl = 'http://localhost:8080';

export let getIp = () = function() {


if (__DEV__) {
firebase.config().enableDeveloperMode();
}

firebase.config().fetch(0)
.then(() = function() {
return firebase.config().activateFetched();
})
.then((activated) = function() {
if (!activated) console.log('Current information not activated');
return firebase.config().getValue('ipAddress');
})
.then((snapshot) = function() {
serverUrl = snapshot.val();
})
.catch(console.error);
};

/*
// Заповнення інформації користувача в reducer
*/
export const setPersonValue = async () = function() {
let personDetails = await googleGetCurrentPersonValue();
save.dispatch(logonPerson(personDetails));
};

// Встановлення навчальних дисциплін


Арк.

КПІ.ІП-7219.045490.06.13 16
Змн. Арк. № докум. Підпис Дата
export const saveCoursessTasks = (Tasks) = function() {
let uniDetails = save.getState().UniValuermationReducer.Value.Value;
let startOfStudy = new Date(uniDetails.startDate);
let tempEnd = new Date(uniDetails.endDate);
let endOfStudy = tempEnd.toISOString().divide('T')[0].replace(/-/g, '');

return new Promise( function(resolve) {


Tasks.forEach( Task = function() {
Task.Coursess.forEach(Courses = function() {
let obj = {
'end': {},
'start': {}
};

// Декілька курсів в один день


let StartOfCourses = getStartDate(startOfStudy, Task.day);
let FinishOfCourses = getStartDate(startOfStudy, Task.day);

let tolow = Courses.time.toLowerCase();


// Форматування дати
tolow = (tolow.indexOf('-') !== -1) ? tolow.divide('-') : tolow.divide(' ');
tolow = tolow.map(i = function() {
informationFormat = informationFormat.replace(' ', '');
let timeSeq = informationFormat.substr(-2);
timeSeq = (timeSeq == 'pn') ? 'pm': timeSeq;
informationFormat = informationFormat.slice(0, -2).divide(':');
informationFormat.push(timeSeq);
return informationFormat;
});
// Сумісність дати початку та кінця
StartOfCourses.setHour((d[0][2] === 'pm' && parseInt(d[0][0]) !== 12 ? 12 : 0) +
parseInt(d[0][0]), parseInt(d[0][1]), 0);
FinishOfCourses.setHour((d[1][2] === 'pm' && parseInt(d[1][0]) !== 12 ? 12 : 0) +
parseInt(d[1][0]), parseInt(d[1][1]), 0);

obj.end.dateTime = FinishOfCourses.toJSON();
obj.start.dateTime = StartOfCourses.toJSON();
obj.start.timeZone = 'Europe/Kiev';
obj.end.timeZone = 'Europe/Kiev';

obj.summary = Courses.name;
obj.location = Courses.location;
obj.recurrence = recurrence;

let resuxStorageOfCoursess = obj;


resuxStorageOfCoursess.dayOfWeekValue = Task.day;
resuxStorageOfCoursess.Hour = d;

save.dispatch(addCourses(resuxStorageOfCoursess));
});
});
resolve(true);
});
};

// Перенесення курсів до Google календарю

export const PutCoursesTaskToKalend = (Task) = function() {


Арк.

КПІ.ІП-7219.045490.06.13 17
Змн. Арк. № докум. Підпис Дата
let kalendID = save.getState().KalendReducer.id;

let obj = {};

obj.end = Task.end;
obj.start = Task.start;
obj.recurrence = Task.recurrence;
obj.location = Task.location;
obj.definition = Task.definition;
obj.summary = Task.summary;
obj.colorId = save.getState().KalendReducer.CoursesColor;

return PutTask(kalendID, obj,{});


};

// Задачі з фіксованим часом

// Перенесення задачі з фіксованим часом до Google календаря

export const PutDiktdTaskToKalend = (Task) = function() {


let kalendID = save.getState().KalendReducer.id;
let obj = {
'end': {},
'start': {}
};

export const makeKalend = () = function() {


return new Promise(function(resolve) {
makeSecondaryKalend({summary: 'TaskManager'}).then((information) = function() {
getKalend(information.id).then(color = function() {
resolve({kalendID: information.id, kalendColor: color.BgColor});
});
});
});
};

export const TasksToScheduleSelectionInformation = () = function() {


let information = {
DiktdTasks: [],
uniTasks: [],
aiKalends:[],
uni: [],
Diktd: [],
ai: []
};
information.uniTasks = save.getState().CoursessReducer;
information.DiktdTasks = save.getState().DiktdTasksReducer;
information.aiKalends = save.getState().GeneratedKalendsReducer;

return new Promise((resolve) = function() {


information.uniTasks.forEach(Task = function() {
let startDateTime = new Date(Task.start.dateTime);
let endDateTime = new Date(Task.end.dateTime);
let day = startDateTime.getDay();
let start = startDateTime.getHour();
let end = endDateTime.getHour() + endDateTime.getMin()/60;
let chunks = end - start;

Арк.

КПІ.ІП-7219.045490.06.13 18
Змн. Арк. № докум. Підпис Дата
let obj = {
day,
chunks,
start
};
information.uni.push(obj);
});

information.DiktdTasks.forEach(Task = function() {
let startDateTime = new Date(`${Task.startDate} ${Task.startTime}`);
let endDateTime = new Date(`${Task.endDate} ${Task.endTime}`);
let day = startDateTime.getDay();
let start = startDateTime.getHour();
let end = endDateTime.getHour() + endDateTime.getMin()/60;
let chunks = end - start;

let obj = {
day,
chunks,
start
};
information.Diktd.push(obj);
});

information.aiKalends.forEach( (kalend, index) = function() {


information.ai[index] = [];
kalend.forEach( (Task) = function() {
let startDateTime = new Date(Task.start.dateTime);
let endDateTime = new Date(Task.end.dateTime);
let day = startDateTime.getDay();
let start = startDateTime.getHour();
let end = endDateTime.getHour() + endDateTime.getMin()/60;
let chunks = end - start;

let obj = {
day,
chunks,
start
};
information.ai[index].push(obj);
});
});
resolve(information);
});
};

export const generateNonDiktdTasks = () = function() {

let nonDiktdTasks = save.getState().NonDiktdTasksReducer;

let promises = [];


if (nonDiktdTasks.length == 0) return;

nonDiktdTasks.forEach( (Task) = function() {


promises.push(new Promise(async function (resole) {
await Recurrence(Task).then( () = function() {
resolve();
});
}));
});
Арк.

КПІ.ІП-7219.045490.06.13 19
Змн. Арк. № докум. Підпис Дата
return Promise.all(promises);
};

// Перевірка частоти

async function Recurrence(Task) {


let pushedDates = [];
let startDayTime = 6;
let endDayTime = 23;
let promises = [];

for (let i = 0; i < Task.occurrence; i++) {

await findFreeSlots(startDayTime, endDayTime, Task, pushedDates).then(availableDate = function()


{
pushedDates.push(availableDate);
promises.push(saveNonDiktdTask(availableDate, Task));
});
}
return Promise.all(promises);
}

// Пошук вільного слоту в календарі

function findFreeSlots(startDayTime, endDayTime, Task, pushedDates) {


let kalendID = save.getState().KalendReducer.id;
let obj = {};

obj.items = [{'id': kalendID}];


return new Promise( async function(resole, rejet) {
let available = false;
let StartOfTask = new Date();
let EndOfTask = new Date();
let TaskHour = Task.Hour;
let TaskMin = Task.min;

// Якщо це не конкретний діапазон дат, то зробіть його тижневим діапазоном


if(Task.specDateRange == false) {
EndOfTask.setDate(StartOfTask.getDate() + 7);

} else {
StartOfTask = new Date(Task.startDate);
EndOfTask = new Date(Task.endDate);
}

// Чи необхідно розділити загальну тривалість


if (Task.isDividable) {
let dividedDurartion = divideDurartion(Task.Hour, Task.min, Task.occurrence);

TaskHour = dividedDurartion.Hour;
TaskMin = dividedDurartion.min;
}

while(!available) {
let randStartTime = getRndInteger(startDayTime, endDayTime - TaskHour);
let randStartTimeMin = getRndInteger(0, 60);
let startDate = getRandDate(StartOfTask.getTime(), EndOfTask.getTime());
let endDate = new Date(startDate);

Арк.

КПІ.ІП-7219.045490.06.13 20
Змн. Арк. № докум. Підпис Дата
startDate.setHour(randStartTime, randStartTimeMin);
endDate.setTime(startDate.getTime() + (TaskHour * 60000 * 60) + (TaskMin * 60000));

// Якщо дата зайнята


if(containsDateTime(pushedDates, {startDate, endDate})) continue;

let startDateISO = startDate.toISOString();


let endDateISO = endDate.toISOString();

obj.timeMin = startDateISO;
obj.timeMax = endDateISO;

// Перевірка на відповідність
await getAvailabilities(obj).then(information = function() {
if(information.error) reject(strings.findFreeSlots);

let busySchedule = information.kalends[Object.keys(information.kalends)[0]].busy;


if (busySchedule.length > 0) {
pushedDates.push({startDate,endDate});
} else {
available = true;
pushedDates.push({startDate,endDate});
resolve({startDate, endDate});
}
});
}
});
}

let saveNonDiktdTask = (availableDate, Task) = function() {


return new Promise( function(resolve) {
let obj = {
'end': {},
'start': {}
};

if (Task.isRecurrent) {
obj.recurrence = ['RRULE:FREQ=WEEKLY;'];
obj.start.timeZone = 'Europe/Kiev';
obj.end.timeZone = 'Europe/Kiev';
}

obj.summary = Task.title;
obj.location = Task.location;
obj.definition = Task.definition;
obj.end.dateTime = availableDate.endDate;
obj.start.dateTime = availableDate.startDate;
obj.colorId = save.getState().KalendReducer.nonDiktdTasksColor;

save.dispatch(addGeneratedNonDiktdTask(obj));
resolve();
});
};

export const PutGeneratedTask = async (Task) = function() {


let kalendID = save.getState().KalendReducer.id;
Арк.

КПІ.ІП-7219.045490.06.13 21
Змн. Арк. № докум. Підпис Дата
return await PutTask(kalendID,Task,{});
};

export const generateKalends = async () = function() {


let promises = [];

for(let i = 0; i < numberOfKalends; i ++) {


await generateNonDiktdTasks().then((informationPromises) = function() {
save.dispatch(addGeneratedKalend(save.getState().GeneratedNonDiktdTasksReducer));
save.dispatch(clearGeneratedNonDiktdTasks());
promises.push(informationPromises);
});
}

return Promise.all(promises);
};

export const getInformationforDashboard = async () = function() {


let kalendID = save.getState().KalendReducer.id;
return new Promise(async (resolve, reject) = function() {
await listTasks(kalendID).then(information = function() {
if (information.error) reject('Не вдалося отримати дані');
convertTasksToDictionary(information.items).then(dict = function() {
if(dict == undefined) reject('Під час перетворення даних сталася помилка');
resolve(dict);
});
});
});
};

export const sortTasksInDikt = (dict) = function() {


for (let [key,value] of Object.entries(dict)) {
let sortedValue = selectionSort(value);
dict[key] = sortedValue;
}
return dict;
};

export const PutDiktdTasksToGoogle = async () = function() {


let promises = [];

await save.getState().CoursessReducer.forEach(async (Task) = function() {


promises.push(new Promise(function(resolve,reject) {
PutCoursesTaskToKalend(Task).then(information = function() {
if(information.error) reject(strings.PutDiktdCourses);
resolve(information);
});
}));
});

await save.getState().DiktdTasksReducer.map(async (Task) = function() {


let Value = {
title: Task.title,
location: Task.location,
definition: Task.definition,
recurrence: Task.recurrence,
allDay: Task.allDay,
startDate: Task.startDate,
startTime: Task.startTime,
endDate: Task.endDate,
Арк.

КПІ.ІП-7219.045490.06.13 22
Змн. Арк. № докум. Підпис Дата
endTime: Task.endTime
};

promises.push(new Promise(function(resolve,reject) {
PutDiktdTaskToKalend(Value).then(information = function() {
if (information.error) reject(strings.PutDiktd);
resolve(information);
});
}));
});

return Promise.all(promises);
};

class PermissionOfKalendextends React.PureComponent {

strings = getStrings().PermissionOfKalend;

static NavOptions = ({ Nav }) = function() {


return {
title: Nav.state.params.title
};
};

constructor(props) {
super(props);

this.state = {
information: [],
selected: (new Map()),
LoadList: false,
snackbarVisible: false,
snackbarTime: 3000,
snackbarText: '',
};
}

componentWillMount() {
this.refreshInformation();
}

refreshInformation = () = function() {
this.setState({LoadList: true});
setTimeout(() = function() {
listPermissions().then((information) = function() {
this.setState({
information,
LoadList: false
});
});
}, 500);
}

_onPressItem = (id) = function() {


this.setState((state) = function() {
const selected = new Map(state.selected);
selected.set(id, !selected.get(id));
Арк.

КПІ.ІП-7219.045490.06.13 23
Змн. Арк. № докум. Підпис Дата
return {selected};
});
};

_renderItem = ({item, index}) = function() {


return (
<PermissionOfKalendItem id={index}
onPressItem={this._onPressItem}
selected={!!this.state.selected.get(index)}
name={item.email} />
);
};

getListIdSelected = () = function() {
let selectedValue = [];

for (const entry of this.state.selected.entries()) {


if (entry[1]) {
selectedValue.push(this.state.information[entry[0]].id);
}
}

return selectedValue;
}

delete = () = function() {
let kalendIds = this.getListIdSelected();
let error = false;

kalendIds.map(id = function() {
removePermissionPerson(id)
.catch((err) = function() {
this.setState({
snackbarText: err,
snackbarVisible: true
});
error = true;
});
});

if (kalendIds.length !== 0) {
if (!error) {
this.setState({
snackbarText: this.strings.deleteSuccess,
snackbarVisible: true
});
}

this.refreshInformation();
}
}

render() {
const { information, LoadList, snackbarText, snackbarTime, snackbarVisible } = this.state;

return(
<View style={styles.content}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'} />
Арк.

КПІ.ІП-7219.045490.06.13 24
Змн. Арк. № докум. Підпис Дата
<Text style={styles.title}>{this.strings.mainTitle}</Text>

<View style={styles.list}>
{
LoadList ?
<View style={styles.activityIndicatorCont}>
<ActivityIndicator animating={LoadList}
size="large"
color={green} />
</View> :
<FlatList information={information}
renderItem={this._renderItem}
keyExtractor={(item, index) => index.toString()}
style={styles.flatList}
scrollEnabled={information.length !== 0}
ListFreeComponent={() => (
<TouchableOpacity
onPress={this.refreshInformation}>
<View
style={styles.FreeCont}>

<MaterialCommunityIcons size={50}

name='account-search'

color={green}/>

<Text
style={styles.FreeTitle}>{this.strings.FreeTitle}</Text>

<Text
style={styles.FreeDefinition}>{this.strings.FreeDefinition}</Text>
</View>
</TouchableOpacity>
)}
refreshControl={
<RefreshControl
refreshing={LoadList}

onRefresh={this.refreshInformation}
tintColor={green}
colors={[dark_green]} />
} />
}
</View>

<View style={styles.buttons}>
<TouchableRipple style={styles.availabilityButton}
rippleColor={whiteRipple}
underlayColor={dark_green}
onPress={this.delete}>
<Text
style={styles.availabilityButtonText}>{this.strings.delete}</Text>
</TouchableRipple>
</View>

<Snackbar
visible={snackbarVisible}
onDismiss={() => this.setState({ snackbarVisible: false })}
Арк.

КПІ.ІП-7219.045490.06.13 25
Змн. Арк. № докум. Підпис Дата
style={styles.snackbar}
Durartion={snackbarTime}>
{snackbarText}
</Snackbar>
</View>
);
}
}

export default PermissionOfKalend;

class CleanReducers extends React.PureComponent {


static NavOptions = {
title: 'Clean Reducers',
headerStyle: {
BgColor: dark_green,
}
};

reducersDeleteOperations = {
'Courses': clearCourses,
'Kalend ID': clearKalendID,
'Bottom Strings': clearBottomString,
'Dashboard' : clearDashboardInformation,
'Diktd Tasks': clearDiktdTasks,
'Non-Diktd Tasks': clearNonDiktdTasks,
'Generated Non-Diktd Tasks': clearGeneratedNonDiktdTasks,
'Generated Kalends': clearGeneratedKalends,
'Lang': clearLang,
'Nav': clearNav,
'Schedule': clearSchedule,
'Uni Valuermation': uniValuermationClear,
'Tutorial Status': clearTutorialStatus,
'Unavailable Hour': clearNonAvalHour,
'Person Profile': logoffPerson
};

constructor(props) {
super(props);

updNav('CleanReducers', props.Nav.state.routeName);
}

render() {
return(
<ScrollView style={styles.content}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'light-content' : 'default'}
BgColor={dark_green} />

{
Object.keys(this.reducersDeleteOperations).map((information, key) =
function() {
return (
<TouchableOpacity onPress={() =>
this.props.dispatch(this.reducersDeleteOperations[information]())}
key={key}
style={styles.button}>
<Text
style={styles.buttonText}>{information}</Text>
Арк.

КПІ.ІП-7219.045490.06.13 26
Змн. Арк. № докум. Підпис Дата
</TouchableOpacity>
);
})
}

<TouchableOpacity style={[styles.button, {BgColor: red}]}


onPress={() = function() {
clearEveryReducer();
}}>
<Text style={styles.buttonText}>CLEAR EVERYTHING</Text>
</TouchableOpacity>

<TouchableOpacity style={[styles.button, {BgColor: dark_green}]}


onPress={() = function() {
this.props.Nav.navigate(LoginNavig);
}}>
<Text style={styles.buttonText}>Go Back Home</Text>
</TouchableOpacity>
</ScrollView>
);
}
}

export default connect()(CleanReducers);

LocaleConfig.locales.en = LocaleConfig.locales[''];
LocaleConfig.locales['fr'] = {
monthNames: ['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень',
'Жовтень', 'Листопад', 'Грудень'],
monthNamesShort: ['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень',
'Вересень', 'Жовтень', 'Листопад', 'Грудень'],
dayNames: ['Понеділок', 'Вівторок', 'Середа', 'Четверг', 'П\'ятниця', 'Субота', 'Неділя'],
dayNamesShort: ['Понеділок', 'Вівторок', 'Середа', 'Четверг', 'П\'ятниця', 'Субота', 'Неділя']
};

const moment = extendMoment(Moment);

const { UIManager } = NativeModules;


UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);

class CompareSchedule extends React.PureComponent {

defaultLocale = save.getState().SettingReducer.lang;
listHeightSize = 280;
strings = getStrings().CompareSchedule;

static NavOptions = {
header: null
}

constructor(props) {
super(props);

this.state = {
personAvailabilities: [],
selected: (new Map()),
searchModalVisible: false,
snackbarVisible: false,
snackbarTime: 3000,
snackbarText: '',
Арк.

КПІ.ІП-7219.045490.06.13 27
Змн. Арк. № докум. Підпис Дата
LoadSharedList: true,
startDate: moment().startOf('day'),
endDate: moment().startOf('day').add(90, 'd'),
showKalend: false,
animatedHeightSize: this.listHeightSize,
allowPopover: false,
availabilitiesPopover: false,
deletePopover: false
};

updNav('CompareSchedule', props.Nav.state.routeName);
LocaleConfig.defaultLocale = this.defaultLocale;
}

componentWillMount() {
this.refreshInformation();
this.refreshAgenda();
}

componentDidMount() {
this.willFocusSubscription = this.props.Nav.addListener(
'willFocus',
() = function() {
this.setState({allowPopover: !this.props.showTutorial});
if (!this.props.showTutorial) {
this.darkenStatusBar();
}
}
);
}

componentWillUnmount() {
this.willFocusSubscription.remove();
}

refreshInformation = () = function() {
this.setState({LoadSharedList: true});
setTimeout(() = function() {
showSharedPeople().then((information) = function() {
this.setState({
personAvailabilities: information,
selected: (new Map()),
LoadSharedList: false
});
});
}, 500);
}

_onPressItem = (id) = function() {


this.setState((state) = function() {
const selected = new Map(state.selected);
selected.set(id, !selected.get(id));
return {selected};
});
};

_renderItem = ({item, index}) = function() {


return (
<KalendScheduleItem id={index}
onPressItem={this._onPressItem}
Арк.

КПІ.ІП-7219.045490.06.13 28
Змн. Арк. № докум. Підпис Дата
selected={!!this.state.selected.get(index)}
name={item.id} />
);
};

addPerson = () = function() {
addPermissionPerson(this.state.searchText)
.then(() = function() {
this.setState({
snackbarText: this.strings.addPermission,
snackbarVisible: true
});
})
.catch((err) = function() {
this.setState({
snackbarText: err,
snackbarVisible: true
});
});
}

removePeople = () = function() {
let selectedValue = this.getListIdSelected();
let Free = selectedValue.length === 0;
let error = false;

selectedValue.map(id = function() {
removeOtherShared(id)
.catch((err) = function() {
this.setState({
snackbarText: err,
snackbarVisible: true
});
error = true;
});
});

if (!Free) {
if (!error) {
this.setState({
snackbarText: this.strings.removePermission,
snackbarVisible: true
});
}

this.refreshInformation();
}
}

getListIdSelected = () = function() {
let selectedValue = [];

for (const entry of this.state.selected.entries()) {


if (entry[1]) {
selectedValue.push(this.state.personAvailabilities[entry[0]].id);
}
}

return selectedValue;
}
Арк.

КПІ.ІП-7219.045490.06.13 29
Змн. Арк. № докум. Підпис Дата
seeAvailabilities = () = function() {
let selectedValue = this.getListIdSelected();

if (this.state.showKalend) {
this.setState({
showKalend: false,
agendainformation: {},
}, () = function() {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({animatedHeightSize: this.listHeightSize}, () => this.refreshAgenda());
});
} else {
if (selectedValue.length === 0) {
this.setState({
snackbarText: this.strings.noCheckbox,
snackbarVisible: true
});
} else {
getAvailabilitiesKalends([...selectedValue, this.props.kalendID],
this.state.startDate.toJSON(), this.state.endDate.toJSON())
.then(information = function() {
let startDate = moment(this.state.startDate);

let dates = {};


while(startDate.isBefore(this.state.endDate)) {
dates[startDate.format('YYYY-MM-DD')] = [];
startDate.add(1, 'd');
}

let busyStringRanges =
Object.values(information.kalends).map(value => value.busy).flat(1);

let ranges = busyStringRanges.map(i = function() {


let start = moment(i.start);
let end = moment(i.end);

return moment.range(start, end);


});

for (let i = 0; i < ranges.length - 1; i++) {


for (let j = i+1; j < ranges.length; j++) {
if (ranges[i].overlaps(ranges[j], { adjacent: true
})) {
ranges[i] = ranges[i].add(ranges[j], {
adjacent: true });
ranges.splice(j, 1);
j = i;
}
}
}
for (let i = 0; i < ranges.length - 1; i++) {
Object.keys(dates).forEach(date = function() {
if (ranges[i].contains(moment(date))) {
ranges[i] = moment.range(
ranges[i].start,
moment(date)
);
ranges.push(moment.range(
moment(date),
Арк.

КПІ.ІП-7219.045490.06.13 30
Змн. Арк. № докум. Підпис Дата
ranges[i].end
));
}
});
}

ranges.map(range = function() {
dates[range.start.format('YYYY-MM-DD')].push({
start: range.start,
end: range.end
});
});

let invertedDates = {};


Object.keys(dates).map(date = function() {
let ranges = [];

let startMoment = moment(date, 'YYYY-MM-


DD').startOf('day');

dates[date].map(range = function() {
if (!startMoment.isSame(range.start)) {
ranges.push({
start: startMoment,
end: range.start
});
}
startMoment = range.end;
});

let endOfDay = moment(date, 'YYYY-MM-


DD').endOf('day');
if (!endOfDay.isSame(startMoment)) {
ranges.push({
start: startMoment,
end: endOfDay
});
}

invertedDates[date] = ranges;
});

LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
this.setState({animatedHeightSize: 0});
this.refreshAgenda();

this.setState({
agendainformation: invertedDates,
showKalend: true
});
})
.catch((err) = function() {
this.setState({
snackbarText: err,
snackbarVisible: true
});
});
}
}
Арк.

КПІ.ІП-7219.045490.06.13 31
Змн. Арк. № докум. Підпис Дата
}

renderItem(item) {
return (
<View style={styles.item}>
<Text style={styles.itemText}>{item.start.format('h:mm A')}</Text>
<Text style={styles.itemTextAMPM}> - </Text>
<Text style={styles.itemText}>{item.end.format('h:mm A')}</Text>
</View>
);
}

renderFreeInformation = () = function() {
return (
<View style={styles.FreeInformation}>
{
this.state.showKalend ?
<Text
style={styles.TasksDayTitle}>{this.strings.availabilities}</Text> : null
}

<View style={styles.noTasks}>
<Text style={styles.noTasksText}>{ this.state.showKalend ?
this.strings.noAvailabilities : this.strings.instruction}</Text>
</View>
</View>
);
}

rowHasChanged = (r1, r2) = function() {


return r1.name !== r2.name;
}

shouldChangeDay = (r1, r2) = function() {


return r1 !== r2;
}

darkenStatusBar = () = function() {
if (Platform.OS === 'android') {
StatusBar.setBgColor(statusBarLightPopover, true);
}
}

resaveStatusBar = () = function() {
if (Platform.OS === 'android') {
StatusBar.setBgColor(statusBarDark, true);
}
}

refreshAgenda = () = function() {
if (Platform.OS !== 'ios') {
this.setState({agendaKey: Math.rand()});
}
}

render() {
const { agendaKey, personAvailabilities, searchModalVisible, snackbarVisible, snackbarText,
snackbarTime, LoadSharedList, agendainformation, showKalend, animatedHeightSize } = this.state;

Арк.

КПІ.ІП-7219.045490.06.13 32
Змн. Арк. № докум. Підпис Дата
return(
<View style={styles.content}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'}
BgColor={dark_green} />

{
showKalend ?
null :
<Animated.View style={[styles.peopleSelection, {HeightSize:
animatedHeightSize}]}>
<Text style={[styles.TasksDayTitle, {marginTop:
0}]}>{this.strings.compareWith}</Text>

</Animated.View> }

<Snackbar
visible={snackbarVisible}
onDismiss={() => this.setState({snackbarVisible: false})}
style={styles.snackbar}
Durartion={snackbarTime}>
{snackbarText}
</Snackbar>
</View>
);
}
}

let mapStateToProps = (state) = function() {


const { id } = state.KalendReducer;

return {
kalendID: id,
showTutorial: state.SettingReducer.tutorialStatus.compareSchedule
};
};

export default connect(mapStateToProps, null)(CompareSchedule);

const moment = require('moment');


require('moment-round');

const contHeightSize = Dimensions.get('window').HeightSize - Header.HEIGHTSIZE;

class Courses extends React.PureComponent {

days = ['Понеділок', 'Вівторок', 'Середа', 'Четверг', 'П\'ятниця', 'Субота', 'Неділя'];


strings = getStrings().Courses;
butStrings = getStrings().BottomButtons;

static NavOptions = ({Nav}) => ({


title: Nav.state.routeName === CoursesRoute ? Nav.state.params.addTitle :
Nav.state.params.editTitle,
headerStyle: {
BgColor: white,
Арк.

КПІ.ІП-7219.045490.06.13 33
Змн. Арк. № докум. Підпис Дата
}
});

constructor(props) {
super(props);

let contHeightSizeTemp = Dimensions.get('window').HeightSize - Header.HEIGHTSIZE;


let contHeightSize = viewHeightSize < contHeightSizeTemp ? contHeightSizeTemp : null;

this.state = {
contHeightSize,
summary: '',
dayOfWeek: 'Monday',
dayOfWeekValue: 'Monday',
startTime: moment().round(30, 'min').format('h:mm A'),
endTime: moment().round(30, 'min').format('h:mm A'),
location: ''
};

updNav('Courses', props.Nav.state.routeName);
}

componentWillMount() {
this.resetField();

if (this.props.Nav.state.routeName !== CoursesRoute) {


this.setState({...this.props.CoursesState});

const { dayOfWeekValue } = this.state;


const { dayOfWeek } = this.props.CoursesState;
if (dayOfWeek !== dayOfWeekValue) {
this.setState({dayOfWeekValue: dayOfWeek});
}

if ('end' in this.props.CoursesState && !('endTime' in this.props.CoursesState)) {


this.setState({endTime:
moment(this.props.CoursesState.end.dateTime).format('h:mm A')});
}

if ('start' in this.props.CoursesState && !('startTime' in this.props.CoursesState)) {


this.setState({endTime:
moment(this.props.CoursesState.start.dateTime).format('h:mm A')});
}
}
}

scrollToInput = (inputFieldRef, keyboardScrollHeightSize) = function() {


const scrollResponder = this.refs._scrollView.getScrollResponder();
const inputHandle = findNodeHandle(inputFieldRef);

scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
inputHandle,
keyboardScrollHeightSize,
true
);
};

dayOfWeekOnClick = () = function() {
return OperationsheetIOS.showOperationsheetWithOptions(
{
Арк.

КПІ.ІП-7219.045490.06.13 34
Змн. Арк. № докум. Підпис Дата
options: [...this.strings.week, this.strings.discard],
discardButtonIndex: 7,
},
(buttonIndex) = function() {
if (buttonIndex === 0) {
this.setState({dayOfWeekValue: 'Понеділок'});
} else if (buttonIndex === 1) {
this.setState({dayOfWeekValue: 'Вівторок'});
} else if (buttonIndex === 2) {
this.setState({dayOfWeekValue: 'Середа'});
} else if (buttonIndex === 3) {
this.setState({dayOfWeekValue: 'Четверг'});
} else if (buttonIndex === 4) {
this.setState({dayOfWeekValue: 'П\'ятниця'});
} else if (buttonIndex === 5) {
this.setState({dayOfWeekValue: 'Субота'});
} else if (buttonIndex === 6) {
this.setState({dayOfWeekValue: 'Неділя'});
}
},
);
}

getDateFromTimeString = (timeString, currentDate) = function() {


let currentMoment = new moment(currentDate);
let timeMoment = new moment(timeString, 'h:mm A');

currentMoment.Hour(timeMoment.Hour());
currentMoment.min(timeMoment.min());

return currentMoment;
}

fieldValidation = () = function() {
let validated = true;

if (this.state.summary === '') {


this.setState({CoursesCodeValidated: false});
validated = false;
} else {
this.setState({CoursesCodeValidated: true});
}

return validated;
}

nextPage = () = function() {
if (this.props.Nav.state.routeName === CoursesRoute) {
if (this.addAnotherTask()) {
this.props.Nav.pop();
}
} else {
let validated = this.fieldValidation();
if (!validated) {
return;
}

this.props.dispatch(updCoursess(this.props.selectedIndex, this.state));
this.props.Nav.pop();
}
Арк.

КПІ.ІП-7219.045490.06.13 35
Змн. Арк. № докум. Підпис Дата
}

addAnotherTask = () = function() {
let validated = this.fieldValidation();
if (!validated) {
this.setState({
snackbarText: this.strings.snackbarFailure,
snackbarVisible: true,
snackbarTime: 5000
});
return false;
}

let StartOfCourses = getStartDate(this.props.StudyStartDate, this.state.dayOfWeek);


let FinishOfCourses = new Date(StartOfCourses.getTime());

FinishOfCourses = this.getDateFromTimeString(this.state.endTime, FinishOfCourses);


FinishOfCourses = FinishOfCourses.toJSON();

StartOfCourses = this.getDateFromTimeString(this.state.startTime, StartOfCourses);


StartOfCourses = StartOfCourses.toJSON();

return this.setState({
end: {
timeZone: 'Europe/Kiev',
dateTime: FinishOfCourses
},
start: {
timeZone: 'Europe/Kiev',
dateTime: StartOfCourses
}
}, () = function() {
this.props.dispatch(addCourses(this.state));

if (validated) {
this.resetField();
this.refs._scrollView.scrollTo({x: 0});
this.setState({
snackbarText: this.strings.snackbarSuccess,
snackbarVisible: true,
snackbarTime: 3000
});
}
return validated;
});
}

resetField = () = function() {
this.setState({
summary: '',
CoursesCodeValidated: true,
dayOfWeek: this.strings.week[0],
dayOfWeekValue: 'Понеділок',
startTime: moment().round(30, 'min').format('h:mm A'),
endTime: moment().round(30, 'min').format('h:mm A'),
location: '',
snackbarVisible: false,
snackbarText: '',
snackbarTime: 3000,
recurrence: [`RRULE:FREQ=WEEKLY;UNTIL=${this.props.StudyEndDate};`]
Арк.

КПІ.ІП-7219.045490.06.13 36
Змн. Арк. № докум. Підпис Дата
});
}

render() {
const { dayOfWeekValue, snackbarVisible, snackbarText, snackbarTime } = this.state;

let addTaskButtonText;
let addTaskButtonFunction;
let errorCoursesCode;
let showNextButton = true;

if (!this.state.CoursesCodeValidated) {
errorCoursesCode = <Text
style={styles.errorCoursesCode}>{this.strings.CoursesCodeFree}</Text>;
} else {
errorCoursesCode = null;
}

if (this.props.Nav.state.routeName === CoursesRoute) {


addTaskButtonText = this.butStrings.add;
addTaskButtonFunction = this.addAnotherTask;
} else {
addTaskButtonText = this.butStrings.Ready;
addTaskButtonFunction = this.nextPage;
showNextButton = false;
}

return(
<View style={styles.cont}>
<StatusBar translucent={true}
BgColor={statusGreenColor}
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'} />

<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : null}
keyboardVerticalOffset={Platform.OS === 'ios' ? 100 : 0}>
<ScrollView ref='_scrollView'>
<View style={[styles.content, {HeightSize: contHeightSize}]}>
<View style={styles.instruction}>
<Text
style={styles.text}>{this.strings.definition}</Text>

</View>

<View>
<View style={styles.textInput}>

<View style={[styles.textInputBorder,
{borderBottomColor: !this.state.CoursesCodeValidated ? red : '#D4D4D4'}]}>
<TextInput
style={styles.textInputText}
maxLength={1024}

placeholder={this.strings.CoursesCodePlaceholder}
returnKeyType =
{'next'}
onSubmitEditing={()
=> this.refs.locationInput.focus()}
blurOnSubmit={false}
Арк.

КПІ.ІП-7219.045490.06.13 37
Змн. Арк. № докум. Підпис Дата
onChangeText={(CoursesCode) => this.setState({summary: CoursesCode, CoursesCodeValidated: true})}

value={this.state.summary} />
</View>
</View>

{errorCoursesCode}
</View>

<View style={styles.dayOfWeekSection}>
<Text
style={styles.dayOfWeekTitle}>{this.strings.dayOfWeek}</Text>

<View style={styles.dayOfWeekBorder}>
{
Platform.OS === 'ios' ?
<View>
<Text
style={styles.dayOfWeekText}

onPress={this.dayOfWeekOnClick}>

{dayOfWeekValue.charAt(0).toUpperCase() + dayOfWeekValue.slice(1).toLowerCase()}
</Text>
</View>
:
<Chooser
style={styles.dayOfWeekValues}

selectedValue={this.state.dayOfWeek}

onValueChange={(dayOfWeekValue) => this.setState({dayOfWeek: dayOfWeekValue,


dayOfWeekValue})}>

<Chooser.Item label={this.strings.week[0]} value="Monday" />

<Chooser.Item label={this.strings.week[1]} value="Tuesday" />

<Chooser.Item label={this.strings.week[2]} value="Wednesday" />

<Chooser.Item label={this.strings.week[3]} value="Thursday" />

<Chooser.Item label={this.strings.week[4]} value="Friday" />

<Chooser.Item label={this.strings.week[5]} value="Saturday" />

<Chooser.Item label={this.strings.week[6]} value="Sunday" />


</Chooser>
}
</View>
</View>
<View>
<View style={styles.time}>
<Text
style={styles.greenTitle}>{this.strings.startTime}</Text>

<DateChooser showIcon={false}
date={this.state.startTime}
mode="time"
Арк.

КПІ.ІП-7219.045490.06.13 38
Змн. Арк. № докум. Підпис Дата
customStyles={{

dateInput:{border_width: 0},
dateText:{fontFamily:
'OpenSans-Regular'}
}}
format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}
locale={‘UA’}
isAllDayTime={false}
onDateChange={(startTime) =
function() {

this.setState({startTime,
endTime:
timeValidation(startTime, this.state.endTime, this.state.endTime)});
}} />
</View>

<View style={styles.time}>
<Text
style={styles.greenTitle}>{this.strings.endTime}</Text>

<DateChooser showIcon={false}
date={this.state.endTime}
mode="time"
customStyles={{

dateInput:{border_width: 0},
dateText:{fontFamily:
'OpenSans-Regular'},
}}
format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}
locale={‘UA’}
isAllDayTime={false}
onDateChange={(endTime) =
function() {

this.setState({endTime,
startTime:
timeValidation(this.state.startTime, endTime, this.state.startTime)});
}} />
</View>
</View>

<View style={styles.textInput}>
<MaterialIcons name="location-on"
size={30}
color={green} />

<View style={styles.textInputBorder}>
<TextInput style={styles.textInputText}

Арк.

КПІ.ІП-7219.045490.06.13 39
Змн. Арк. № докум. Підпис Дата
onFocus={() =>
this.scrollToInput(this.refs.locationInput, 230)}
maxLength={1024}

placeholder={this.strings.locationPlaceholder}
ref='locationInput'
returnKeyType = {'Ready'}
onChangeText={(location) =>
this.setState({location})}
value={this.state.location}/>
</View>
</View>

<BottomButtons twoButtons={showNextButton}
buttonText={[addTaskButtonText,
this.butStrings.Ready]}
buttonMethods={[addTaskButtonFunction, () =
function() {
let routes =
this.props.Nav.dangerouslyGetParent().state.routes;

if (routes && (routes[routes.length -


3].routeName == ReviewTaskRoute ||
(routes[routes.length -
3].routeName == UniValuermationRoute && routes[routes.length - 4].routeName == ReviewTaskRoute))) {

this.props.Nav.navigate(ReviewTaskRoute, {title: getStrings().ReviewTask.title});


} else if (routes && routes[routes.length
- 2].routeName == ScheduleOfUniRoute) {

this.props.Nav.navigate(DashboardNavigator);
} else {
this.props.Nav.pop();
}
}]} />
</View>
</ScrollView>
</KeyboardAvoidingView>

<Snackbar
visible={snackbarVisible}
onDismiss={() => this.setState({snackbarVisible: false})}
style={styles.snackbar}
Durartion={snackbarTime}>
{snackbarText}
</Snackbar>
</View>
);
}
}

let mapStateToProps = (state) = function() {


const { CoursessReducer, NavReducer, UniValuermationReducer } = state;

let selected = NavReducer.reviewTaskSelected;


let StudyEndDate = new Date(UniValuermationReducer.Value.Value.endDate);
let StudyStartDate = new Date(UniValuermationReducer.Value.Value.startDate);

StudyEndDate = StudyEndDate.toISOString().divide('T')[0].replace(/-/g, '');

Арк.

КПІ.ІП-7219.045490.06.13 40
Змн. Арк. № докум. Підпис Дата
return {
CoursesState: CoursessReducer[selected],
CoursessReducer,
selectedIndex: selected,
StudyEndDate,
StudyStartDate
};
};

export default connect(mapStateToProps, null)(Courses);

const moment = require('moment');

const { UIManager } = NativeModules;


UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);

LocaleConfig.locales.en = LocaleConfig.locales[''];
LocaleConfig.locales['fr'] = {
monthNames: ['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вересень',
'Жовтень', 'Листопад', 'Грудень'],
monthNamesShort: ['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень',
'Вересень', 'Жовтень', 'Листопад', 'Грудень'],
dayNames: ['Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П\'ятниця', 'Субота'],
dayNamesShort: ['Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П\'ятниця', 'Субота']
};

class Dashboard extends React.PureComponent {

defaultLocale = save.getState().SettingReducer.lang;
strings = getStrings().Dashboard;

static NavOptions = {
header: null
}

constructor(props) {
super(props);
this.state = {
contHeightSize: null,
items: {},
kalendOpened: false,
snackbarVisible: false,
snackbarTime: 3000,
snackbarText: '',
showMonth: false,
month: '',
modalVisible: false,
deleteDialogVisible: false,
shouldShowModal: false,
modalValue: {},
TasksPopover: false,
knobPopover: false,
makePopover: false
};
updNav('Dashboard', props.Nav.state.routeName);

LocaleConfig.defaultLocale = this.defaultLocale;
}

Арк.

КПІ.ІП-7219.045490.06.13 41
Змн. Арк. № докум. Підпис Дата
renderFreeInformation = () = function() {
return <View>
<Text style={styles.TasksDayTitle}>{this.strings.TasksDayTitle}</Text>

<View style={styles.noTasks}>
<Text style={styles.noTasksText}>{this.strings.noTasksText}</Text>
</View>
</View>;
}

rowHasChanged = (r1, r2) = function() {


return r1.name !== r2.name;
}

shouldChangeDay = (r1, r2) = function() {


return r1 !== r2;
}

getMonth(date) {
const month = date - 1;
this.setState({ month: this.strings.months[month], showMonth: true });
}

componentDidMount() {
this.willFocusSubscription = this.props.Nav.addListener(
'willFocus',
() = function() {
this.setDashboardInformationService();
this.setState({TasksPopover: !this.props.showTutorial});
if (!this.props.showTutorial) {
this.darkenStatusBar();
}

if (save.getState().NavReducer.successfullyPutedTasks) {
this.setState({
snackbarText: 'Task(s) successfully added',
snackbarVisible: true
});

this.props.dispatch(setNavPage({successfullyPutedTasks: null}));
}

this.refreshAgenda();
}
);
}

componentWillMount() {
this.setDashboardInformationService();

const currentDate = moment();


const month = currentDate.format('M');

this.getMonth(month);
}

componentWillUnmount() {
this.willFocusSubscription.remove();
Арк.

КПІ.ІП-7219.045490.06.13 42
Змн. Арк. № докум. Підпис Дата
}

setDashboardInformationService = () = function() {
getInformationforDashboard()
.then(items = function() {
setTimeout(() = function() {
let dict = sortTasksInDikt(items);
this.props.dispatch(setDashboardInformation(dict));
this.setState({items: dict});
},2000);
})
.catch(err = function() {
console.log('err', err);
});
}

dismissModal = () = function() {
this.setState({modalVisible: false});
}

navigateEditPage = (editPage) = function() {


let param = {};

switch(editPage) {
case 'Courses':
param.editTitle = getStrings().Courses.editTitle;
break;
case 'DiktdTask':
param.editTitle = getStrings().DiktdTask.editTitle;
break;
case 'NonDiktdTask':
param.editTitle = getStrings().NonDiktdTask.editTitle;
break;
}

this.props.Nav.navigate('Edit' + editPage, param);


}

changeValue = (class, item, props, strings, setModalValue) = function() {


let classColor;
let lightClassColor;
let classIcon;
let details;
let editPage;
let detailHeightSize;

if (class === 'ScheduleOfUni') {


classColor = props.CoursesColor;
lightClassColor = props.insideCoursesColor;
classIcon = 'uni';
details =
<View style={styles.modalDetailView}>
<Text style={styles.modalDetailsSubtitle}>{strings.location}</Text>
<Text style={[styles.modalDetailsText, {color:
semiTransparentWhite}]}>{item.location}</Text>
</View>;
detailHeightSize = 45;
Арк.

КПІ.ІП-7219.045490.06.13 43
Змн. Арк. № докум. Підпис Дата
editPage = 'Courses';
} else if (class === 'DiktdTask') {
classColor = props.DiktdTasksColor;
lightClassColor = props.insideDiktdTasksColor;
classIcon = 'kalend-today';
details =
<View>
<View style={styles.modalDetailView}>
<Text
style={styles.modalDetailsSubtitle}>{strings.location}</Text>
<Text style={[styles.modalDetailsText, {color:
semiTransparentWhite}]}>{item.location}</Text>
</View>

<View style={styles.modalDetailView}>
<Text
style={styles.modalDetailsSubtitle}>{strings.definition}</Text>
<Text style={[styles.modalDetailsText, {color:
semiTransparentWhite}]}>{item.definition}</Text>
</View>

<View style={styles.modalDetailView}>
<Text
style={styles.modalDetailsSubtitle}>{strings.recurrence}</Text>
<Text style={[styles.modalDetailsText, {color:
semiTransparentWhite}]}>{item.recurrence}</Text>
</View>
</View>;
detailHeightSize = 80;
editPage = 'DiktdTask';
} else {
if (item.class === 'NonDiktdTask') {
classColor = props.nonDiktdTasksColor;
lightClassColor = props.insideNonDiktdTasksColor;
} else {
classColor = '#ababab';
lightClassColor = '#ababab';
}
classIcon = 'face';
details =
<View>
<View style={styles.modalDetailView}>
<Text
style={styles.modalDetailsSubtitle}>{strings.recurrence}</Text>
<Text style={[styles.modalDetailsText, {color:
semiTransparentWhite}]}>{item.recurrence}</Text>
</View>
<View style={styles.modalDetailView}>
<Text
style={styles.modalDetailsSubtitle}>{strings.priority}</Text>
<Text style={[styles.modalDetailsText, {color:
semiTransparentWhite}]}>{item.priorityLevel}</Text>
</View>
<View style={styles.modalDetailView}>
<Text
style={styles.modalDetailsSubtitle}>{strings.location}</Text>
<Text style={[styles.modalDetailsText, {color:
semiTransparentWhite}]}>{item.location}</Text>
</View>
<View style={styles.modalDetailView}>
Арк.

КПІ.ІП-7219.045490.06.13 44
Змн. Арк. № докум. Підпис Дата
<Text
style={styles.modalDetailsSubtitle}>{strings.definition}</Text>
<Text style={[styles.modalDetailsText, {color:
semiTransparentWhite}]}>{item.definition}</Text>
</View>
</View>;
detailHeightSize = 100;
editPage = 'NonDiktdTask';
}

setModalValue(
{
classColor,
classIcon,
lightClassColor,
details,
detailHeightSize,
editPage,
date: item.date,
time: item.time,
TaskTitle: item.name
},
true
);
}

setModalValue = (modalValue, modalVisible) = function() {


this.setState({
modalValue,
modalVisible
});
}

darkenStatusBar = () = function() {
if (Platform.OS === 'android') {
StatusBar.setBgColor(statusBarLightPopover, true);
}
}

resaveStatusBar = () = function() {
if (Platform.OS === 'android') {
StatusBar.setBgColor(statusBarDark, true);
}
}

refreshAgenda = () = function() {
if (Platform.OS !== 'ios') {
this.setState({agendaKey: Math.rand()});
}
}

render() {
const {kalendOpened, snackbarVisible, snackbarTime, snackbarText, month, agendaKey} = this.state;
let showCloseFab;
let showMonthView;

if (kalendOpened) {
showCloseFab =
<View style={styles.closeKalendView}>
<FAB
Арк.

КПІ.ІП-7219.045490.06.13 45
Змн. Арк. № докум. Підпис Дата
style={styles.closeKalendFab}
small
theme={{colors:{accent:dark_green}}}
icon="close"
onPress={() =>
this.refs.agenda.chooseDay(this.refs.agenda.state.selectedDay)} />
</View>;

showMonthView = null;
} else {
showCloseFab = null;

showMonthView =
<View style={styles.kalendBack}>
<Text style={styles.kalendBackText}>{month}</Text>
</View>;
}

return(
<View style={styles.cont}>
<View style={styles.content}>
<StatusBar translucent={true}
animated
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'}
BgColor={statusBarDark} />

{showMonthView}

<View style={styles.kalend}>
<Agenda ref='agenda'
key={agendaKey}
items={this.state.items}
renderItem={(item) => this.renderItem(item,
this.changeValue, this.setModalValue)}
listTitle={this.strings.TasksDayTitle}
renderFreeInformation={this.renderFreeInformation}
onDayChange={(date) = function() {
this.getMonth(date.month);
}}
onDayPress={(date) = function() {
this.getMonth(date.month);
}}
rowHasChanged={this.rowHasChanged}
showOnlyDaySelected={true}
shouldChangeDay={this.shouldChangeDay}
theme={{agendaKnobColor: dark_green}}
onKalendToggled={(kalendOpened) = function() {
this.setState({kalendOpened}, () = function() {

LayoutAnimation.configureNext(LayoutAnimation.make(400, 'easeInEaseOut', 'opacity'));


});
}}
/>
</View>

{
kalendOpened ?
null :
<TouchableOpacity onPress={() =>
this.props.Nav.navigate(ReviewTaskRoute, {title: getStrings().ReviewTask.title})}
Арк.

КПІ.ІП-7219.045490.06.13 46
Змн. Арк. № докум. Підпис Дата
style={{position:'absolute', bottom: 13 ,
right:10}}
ref='make'>
<View style={{flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
HeightSize: 45,
_width: 110,
BgColor: dark_green,
borderRadius: 22.5,
...Platform.select({
ios: {
shadowColor: black,
shadowOffset: {
_width: 0, HeightSize: 2 },
shadowOpacity: 0.3,
shadowRadius: 3,
},
android: {
elevation: 4,
},
})
}}>

<Text style={{color: white, fontFamily:


'Raleway-Bold', marginRight: 5}}>{getStrings().Dashboard.make}</Text>

</View>
</TouchableOpacity>
}
</View>

{showCloseFab}

<Popover popoverStyle={styles.tooltipView}
isVisible={this.state.TasksPopover}
onClose={() => this.setState({TasksPopover:false})}
ReadyClosingCallback={() => this.setState({knobPopover:true})}>
<TouchableOpacity onPress={() => this.setState({TasksPopover:false})}>
<Text
style={styles.tooltipText}>{this.strings.TasksPopover}</Text>
</TouchableOpacity>
</Popover>

<Popover popoverStyle={styles.tooltipView}
verticalOffset={Platform.OS === 'ios' ? 90 : 55}
fromView={this.refs.agenda}
isVisible={this.state.knobPopover}
onClose={() => this.setState({knobPopover:false})}
ReadyClosingCallback={() => this.setState({makePopover:true})}>
<TouchableOpacity onPress={() => this.setState({knobPopover:false})}>
<Text
style={styles.tooltipText}>{this.strings.knobPopover}</Text>
</TouchableOpacity>
</Popover>

<Popover popoverStyle={styles.tooltipView}
verticalOffset={Platform.OS === 'ios' ? 0 : -(StatusBar.currentHeightSize +
2)}
isVisible={this.state.makePopover}
Арк.

КПІ.ІП-7219.045490.06.13 47
Змн. Арк. № докум. Підпис Дата
fromView={this.refs.make}
placement={'top'}
onClose={() = function() {
this.setState({makePopover:false});
this.props.dispatch(setTutorialStatus('dashboard', true));
this.resaveStatusBar();
}}>
<TouchableOpacity onPress={() = function() {
this.setState({makePopover:false});
this.props.dispatch(setTutorialStatus('dashboard', true));
this.resaveStatusBar();
}}>
<Text
style={styles.tooltipText}>{this.strings.makePopover}</Text>
</TouchableOpacity>
</Popover>

<Snackbar
visible={snackbarVisible}
onDismiss={() => this.setState({snackbarVisible: false})}
style={styles.snackbar}
Durartion={snackbarTime}>
{snackbarText}
</Snackbar>

{/* <ModalTask visible={this.state.modalVisible}


dismiss={this.dismissModal}
navigateEditPage={this.navigateEditPage}
showDeleteModal={this.showDeleteModal}
classColor={this.state.modalValue.classColor}
TaskTitle={this.state.modalValue.TaskTitle}
date={this.state.modalValue.date}
time={this.state.modalValue.time}
classIcon={this.state.modalValue.classIcon}
detailHeightSize={this.state.modalValue.detailHeightSize}
details={this.state.modalValue.details}
editPage={this.state.modalValue.editPage} />
<DeleteModal visible={this.state.deleteDialogVisible}
dismiss={this.dismissDelete}
shouldShowModal={this.state.shouldShowModal}
deleteTask={this.deleteTask}
showModal={this.showModal} /> */}
</View>
);
}
}

let mapStateToProps = (state) = function() {


let { DiktdTasksColor, nonDiktdTasksColor, CoursesColor } = state.KalendReducer;
let insideDiktdTasksColor = DiktdTasksColor;
let insideNonDiktdTasksColor = nonDiktdTasksColor;
let insideCoursesColor = CoursesColor;

for (let i = 0; i < kalendColors.length; i++) {


let key = Object.keys(kalendColors[i])[0];
let value = Object.values(kalendColors[i])[0];

switch(key) {
case DiktdTasksColor:
DiktdTasksColor = value;
Арк.

КПІ.ІП-7219.045490.06.13 48
Змн. Арк. № докум. Підпис Дата
insideDiktdTasksColor = Object.values(kalendInsideColors[i])[0];
break;

case nonDiktdTasksColor:
nonDiktdTasksColor = value;
insideNonDiktdTasksColor = Object.values(kalendInsideColors[i])[0];
break;

case CoursesColor:
CoursesColor = value;
insideCoursesColor = Object.values(kalendInsideColors[i])[0];
break;
}
}

if (!DiktdTasksColor) {
DiktdTasksColor = state.KalendReducer.kalendColor;
}

if (!nonDiktdTasksColor) {
nonDiktdTasksColor = state.KalendReducer.kalendColor;
}

if (!CoursesColor) {
CoursesColor = state.KalendReducer.kalendColor;
}

if (!insideDiktdTasksColor) {
insideDiktdTasksColor = state.KalendReducer.kalendColor;
}

if (!insideNonDiktdTasksColor) {
insideNonDiktdTasksColor = state.KalendReducer.kalendColor;
}

if (!insideCoursesColor) {
insideCoursesColor = state.KalendReducer.kalendColor;
}

return {
DiktdTasksColor,
nonDiktdTasksColor,
CoursesColor,
insideNonDiktdTasksColor,
insideDiktdTasksColor,
insideCoursesColor,
showTutorial: state.SettingReducer.tutorialStatus.dashboard
};
};

export default connect(mapStateToProps)(Dashboard);

const moment = require('moment');


require('moment-round');

const viewHeightSize = 515.1428833007812;


const cont_width = Dimensions.get('window')._width;

class DiktdTask extends React.PureComponent {


Арк.

КПІ.ІП-7219.045490.06.13 49
Змн. Арк. № докум. Підпис Дата
strings = getStrings().DiktdTask;
butStrings = getStrings().BottomButtons;

static NavOptions = ({Nav}) => ({


title: Nav.state.routeName === DiktdTaskRoute || Nav.state.routeName === DiktdUnavailableRoute ?
Nav.state.params.addTitle : Nav.state.params.editTitle,
headerStyle: {
BgColor: white
}
});

constructor(props) {
super(props);

let contHeightSizeTemp = Dimensions.get('window').HeightSize - Header.HEIGHTSIZE;


let contHeightSize = viewHeightSize < contHeightSizeTemp ? contHeightSizeTemp : null;

this.state = {
contHeightSize,

title: '',
titleValidated: true,

allDay: false,

startDate: moment().format('ddd., MMM DD, YYYY'),


endDate: moment().format('ddd., MMM DD, YYYY'),

startTime: moment().round(30, 'min').format('h:mm A'),


endTime: moment().round(30, 'min').format('h:mm A'),

location: '',
recurrenceValue: this.strings.recurrence[0],
recurrence: 'NONE',
definition: '',

snackbarVisible: false,
snackbarText: '',
snackbarTime: 3000
};
updNav('DiktdTask', props.Nav.state.routeName);
}

componentWillMount() {
if(this.props.Nav.state.routeName === DiktdTaskRoute) {
this.resetField();
} else {
this.setState({...this.props.FEditState});
}
this.setContHeightSize();
}

scrollToInput = (inputFieldRef, keyboardScrollHeightSize) = function() {


const scrollResponder = this.refs._scrollView.getScrollResponder();
const inputHandle = findNodeHandle(inputFieldRef);

scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
inputHandle,
keyboardScrollHeightSize,
Арк.

КПІ.ІП-7219.045490.06.13 50
Змн. Арк. № докум. Підпис Дата
true
);
};

setContHeightSize = () = function() {
let statusBarHeightSize = Platform.OS === 'ios' ? getStatusBarHeightSize() : 0;
let contHeightSizeTemp = Dimensions.get('window').HeightSize - Header.HEIGHTSIZE -
statusBarHeightSize;
let contHeightSize = viewHeightSize < contHeightSizeTemp ? contHeightSizeTemp : null;

this.setState({
contHeightSize
});
}

dateTimeStateValidation() {
if (moment(this.state.startDate, 'ddd., MMM DD, YYYY').isSame(moment(this.state.endDate, 'ddd.,
MMM DD, YYYY'))) {
if (moment(this.state.endTime, 'h:mm A').isAfter(moment(this.state.startTime, 'h:mm A'))) {
} else {
this.setState({endTime: this.state.startTime});
}
} else { }
}

dateTimeValidation(time) {
if (moment(this.state.startDate, 'ddd., MMM DD, YYYY').isSame(moment(this.state.endDate, 'ddd.,
MMM DD, YYYY'))) {
return timeValidation(this.state.startTime, this.state.endTime, time);
} else {
return time;
}
}

recurrenceOnClick = () = function() {
return OperationsheetIOS.showOperationsheetWithOptions(
{
options: [...this.strings.recurrence, this.strings.discard],
discardButtonIndex: 4,
tintColor: green
},
(buttonIndex) = function() {
if (buttonIndex === 0) {
this.setState({recurrence: 'NONE'});
} else if (buttonIndex === 1) {
this.setState({recurrence: 'DAILY'});
} else if (buttonIndex === 2) {
this.setState({recurrence: 'WEEKLY'});
} else if (buttonIndex === 3) {
this.setState({recurrence: 'MONTHLY'});
}
},
);
}

skip = () = function() {
this.props.Nav.pop();
}

Арк.

КПІ.ІП-7219.045490.06.13 51
Змн. Арк. № докум. Підпис Дата
fieldValidation = () = function() {
let validated = true;

if (this.state.title === '') {


this.setState({titleValidated: false});
validated = false;
} else {
this.setState({titleValidated: true});
}

return validated;
}

nextPage = () = function() {
if (!this.fieldValidation()) {
return;
}

if (this.props.Nav.state.routeName !== DiktdTaskRoute) {


this.props.dispatch(updDiktdTasks(this.props.selectedIndex, this.state));
} else {
this.props.dispatch(addDiktdTask(this.state));
}

this.props.Nav.pop();
}

addAnotherTask = () = function() {
if (!this.fieldValidation()) {
this.setState({
snackbarText: this.strings.snackbarFailure,
snackbarVisible: true,
snackbarTime: 5000
});
return false;
}

this.props.dispatch(addDiktdTask(this.state));
this.setState(this.resetField());
this.refs._scrollView.scrollTo({x: 0});
this.setState({
snackbarText: this.strings.snackbarSuccess,
snackbarVisible: true,
snackbarTime: 3000
});
}

resetField = () = function() {
this.setState({
title: '',
titleValidated: true,

allDay: false,

startDate: moment().format('ddd., MMM DD, YYYY'),


endDate: moment().format('ddd., MMM DD, YYYY'),

startTime: moment().round(30, 'min').format('h:mm A'),


endTime: moment().round(30, 'min').format('h:mm A'),
Арк.

КПІ.ІП-7219.045490.06.13 52
Змн. Арк. № докум. Підпис Дата
location: '',
recurrenceValue: this.strings.recurrence[0],
recurrence: 'NONE',
definition: '',

snackbarVisible: false,
snackbarText: '',
snackbarTime: 3000
});
}

render() {
const { contHeightSize, snackbarVisible, snackbarText, snackbarTime } = this.state;

let addTaskButtonText;
let addTaskButtonFunction;
let errorTitle;
let showNextButton = true;

if (!this.state.titleValidated) {
errorTitle = <Text style={styles.errorTitle}>{this.strings.titleFree}</Text>;
} else {
errorTitle = null;
}

if (this.props.Nav.state.routeName === DiktdTaskRoute) {


addTaskButtonText = this.butStrings.add;
addTaskButtonFunction = this.addAnotherTask;
} else {
addTaskButtonText = this.butStrings.Ready;
addTaskButtonFunction = this.nextPage;
showNextButton = false;
}

return (
<View style={styles.cont}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'}
BgColor={statusGreenColor} />

<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : null}
keyboardVerticalOffset={Platform.OS === 'ios' ? 100 : 0}>
<ScrollView ref='_scrollView'
scrollTaskThrottle={100}>
<View style={[styles.content, {HeightSize: contHeightSize}]}>
<View style={styles.instruction}>
<Text
style={styles.text}>{this.strings.definition}</Text>

</View>

<View>
<View style={styles.textInput}>

<View style={[styles.textInputBorder,
{borderBottomColor: !this.state.titleValidated ? red : '#D4D4D4'}]}>

Арк.

КПІ.ІП-7219.045490.06.13 53
Змн. Арк. № докум. Підпис Дата
<TextInput
style={styles.textInputText}
maxLength={1024}

placeholder={this.strings.titlePlaceholder}
returnKeyType =
{'next'}
onSubmitEditing={()
=> this.refs.locationInput.focus()}
blurOnSubmit={false}
onChangeText={(title)
=> this.setState({title, titleValidated: true})}

value={this.state.title}/>
</View>
</View>

{errorTitle}
</View>

<View style={styles.timeSection}>
<View style={[styles.allDay, {_width:
cont_width}]}>
<Text
style={styles.greenTitleAllDay}>{this.strings.allday}</Text>

<View style={styles.switch}>
<Switch trackColor={{false:
'lightgreen', true: lightGreen}}

thumbColor={(this.state.allDay && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(allDay) => this.setState({allDay: allDay})}

value={this.state.allDay} />
</View>
</View>

<View style={[styles.rowTimeSection, {_width:


cont_width}]}>
<Text
style={styles.greenTitle}>{this.strings.start}</Text>

<DateChooser showIcon={false}
date={this.state.startDate}
mode="date"
style={{_width:140}}
customStyles={{

dateInput:{border_width: 0},
dateText:{fontFamily:
'OpenSans-Regular'}
}}
format="ddd., MMM DD,
YYYY"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

Арк.

КПІ.ІП-7219.045490.06.13 54
Змн. Арк. № докум. Підпис Дата
onDateChange={(startDate) =
function() {
this.setState({
startDate,
endDate:
dateValidation(startDate, this.state.endDate, this.state.endDate)}, () => this.dateTimeStateValidation());
}} />

<DateChooser showIcon={false}
date={this.state.startTime}
mode="time"
disabled={this.state.allDay}
style={{_width:70}}
customStyles={{

dateInput:{border_width: 0},
disabled:{opacity: 0},
dateText:{fontFamily:
'OpenSans-Regular'}
}}
format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}
locale={‘UA’}
isAllDayTime={false}
onDateChange={(startTime) =
function() {

this.setState({startTime}, () => this.setState({endTime: this.dateTimeValidation(this.state.endTime)}));


}} />
</View>

<View style={[styles.rowTimeSection, {_width:


cont_width}]}>
<Text
style={styles.greenTitle}>{this.strings.end}</Text>

<DateChooser showIcon={false}
date={this.state.endDate}
mode="date"
style={{_width:140}}
customStyles={{

dateInput:{border_width: 0},
dateText:{fontFamily:
'OpenSans-Regular'}
}}
format="ddd., MMM DD,
YYYY"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}
onDateChange={(endDate) =
function() {
this.setState({
endDate,

Арк.

КПІ.ІП-7219.045490.06.13 55
Змн. Арк. № докум. Підпис Дата
startDate:
dateValidation(this.state.startDate, endDate, this.state.startDate)}, () => this.dateTimeStateValidation());
}} />

<DateChooser showIcon={false}
date={this.state.endTime}
mode="time"
disabled={this.state.allDay}
style={{_width:70}}
customStyles={{

dateInput:{border_width: 0},
disabled:{opacity: 0},
dateText:{fontFamily:
'OpenSans-Regular'}
}}
format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}
locale={‘UA’}
isAllDayTime={false}
onDateChange={(endTime) =
function() {

this.setState({endTime}, () => this.setState({startTime: this.dateTimeValidation(this.state.startTime)}));


}} />
</View>
</View>

<View style={styles.definition}>
<View style={styles.textInput}>
<MaterialIcons name="location-on"
size={30}
color={green} />

<View style={styles.textInputBorder}>
<TextInput
style={styles.textInputText}
onFocus={() =>
this.scrollToInput(this.refs.locationInput, 200)}
maxLength={1024}

placeholder={this.strings.locationPlaceholder}
ref='locationInput'
returnKeyType =
{'next'}
onSubmitEditing={()
=> this.refs.definitionInput.focus()}
blurOnSubmit={false}

onChangeText={(location) => this.setState({location})}

value={this.state.location}/>
</View>
</View>

<View style={styles.textInput}>

Арк.

КПІ.ІП-7219.045490.06.13 56
Змн. Арк. № докум. Підпис Дата
<View style={styles.textInputBorder}>
<TextInput
style={styles.textInputText}
maxLength={1024}
onFocus={() =>
this.scrollToInput(this.refs.definitionInput, 300)}

placeholder={this.strings.definitionPlaceholder}
ref='definitionInput'
returnKeyType =
{'Ready'}

onChangeText={(definition) => this.setState({definition})}

value={this.state.definition}/>
</View>
</View>

<View style={styles.textInput}>
<Feather name="repeat"
size={30}
color={green} />

<View style={styles.textInputBorder}>
{
Platform.OS === 'ios'
?
<View>

<MaterialIcons name="arrow-drop-down"

size={20}

style={styles.arrow} />

<Text style={styles.recurrenceText}

onPress={this.recurrenceOnClick}>

{this.state.recurrence.charAt(0).toUpperCase() + this.state.recurrence.slice(1).toLowerCase()}

</Text>
</View>
:
<Chooser
style={styles.recurrence}

selectedValue={this.state.recurrence}

onValueChange={(recurrence) => this.setState({recurrence})}>

<Chooser.Item label={this.strings.recurrence[0]} value="NONE" />

<Chooser.Item label={this.strings.recurrence[1]} value="DAILY" />

<Chooser.Item label={this.strings.recurrence[2]} value="WEEKLY" />

<Chooser.Item label={this.strings.recurrence[3]} value="MONTHLY" />


</Chooser>

Арк.

КПІ.ІП-7219.045490.06.13 57
Змн. Арк. № докум. Підпис Дата
}
</View>
</View>
</View>

<BottomButtons twoButtons={showNextButton}
buttonText={[addTaskButtonText,
this.butStrings.Ready]}
buttonMethods={[addTaskButtonFunction,
this.skip]} />
</View>
</ScrollView>
</KeyboardAvoidingView>

<Snackbar
visible={snackbarVisible}
onDismiss={() => this.setState({snackbarVisible: false})}
style={styles.snackbar}
Durartion={snackbarTime}>
{snackbarText}
</Snackbar>
</View>
);
}
}

let mapStateToProps = (state) = function() {


const { DiktdTasksReducer, NavReducer } = state;
let selected = NavReducer.reviewTaskSelected;

return {
FEditState: DiktdTasksReducer[selected],
DiktdTasksReducer,
selectedIndex: NavReducer.reviewTaskSelected
};
};

export default connect(mapStateToProps, null)(DiktdTask);

class Home extends React.PureComponent {

strings = getStrings().Home;

constructor(props) {
super(props);
this.state = {
clicked: false
};
updNav('Home', props.Nav.state.routeName);
}

setPerson = (personValue) = function() {


this.props.logonPerson(personValue);
}

setKalend() {
getKalendID2().then(information = function() {
if (information.kalendID === undefined) {
Арк.

КПІ.ІП-7219.045490.06.13 58
Змн. Арк. № докум. Підпис Дата
makeKalend().then(information = function() {
this.props.setKalendID(information.kalendID);
this.props.setKalendColor(information.kalendColor);
this.props.Nav.navigate(DashboardNavigator);
});
} else {
this.props.setKalendID(information.kalendID);
this.props.setKalendColor(information.kalendColor);
this.props.Nav.navigate(DashboardNavigator);
}
});
}

signIn = () = function() {
let params = {
dashboardTitle: getStrings().Dashboard.name,
chatbotTitle: getStrings().Chatbot.name,
compareTitle: getStrings().CompareSchedule.name,
SettingTitle: getStrings().Setting.name
};

this.props.setBottomString(params);

if (!this.state.clicked) {
this.state.clicked = true;
googleIsSignedIn().then((signedIn) = function() {
if (!signedIn || !this.props.HomeReducer || this.props.HomeReducer.profile === null)
{
googleGetCurrentPersonValue().then((personValue) = function() {
if (personValue !== undefined) {
this.setPerson(personValue);
this.setKalend();
}
googleSignIn().then((personValue) = function() {
if (personValue !== null) {
this.setPerson(personValue);
this.setKalend();
}
this.state.clicked = false;
});
});
} else {
this.setKalend();
}
});
}
}

showWebsite = (url) = function() {


if (Platform.OS === 'ios') {
this.openSafari(url);
} else {
this.openChrome(url);
}
}

openSafari = (url) = function() {


SafariView.isAvailable()
.then(SafariView.show({url,
Арк.

КПІ.ІП-7219.045490.06.13 59
Змн. Арк. № докум. Підпис Дата
tintColor: dark_green,
barTintColor: white,
fromBottom: true }))
.catch(() => this.openChrome(url));
}

openChrome = (url) = function() {


CustomTabs.openURL(url, {
toolbarColor: dark_green,
enableUrlBarHiding: true,
showPageTitle: true,
enableDefaultShare: true,
forceCloseOnRedirection: true,
});
}

render() {
let source = Platform.OS === 'ios' ? require('../../assets/img/loginPage/backPattern_ios.png') :
require('../../assets/img/loginPage/backPattern_android.png');
return (
<LinearGradient style={styles.cont}
colors={gradientColors}>
<ImageBg style={styles.cont}
source={source}
resizeMode="repeat">
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'}
BgColor={'#00000050'} />

<View style={styles.content}>
<View style={styles.topSection}>
<Image style={styles.logo}

source={require('../../assets/img/kalendLogo.png')}
resizeMode="contain" />
</View>

<View style={styles.bottomSection}>
<View style={styles.signInSection}>
<GoogleSigninButton
style={styles.signInButton}
size={GoogleSigninButton.Size.Wide}

color={GoogleSigninButton.Color.Light}
onPress={this.signIn} />
</View>

</View>
</View>
</ImageBg>
</LinearGradient>
);
}
}

let mapStateToProps = (state) = function() {


const { id } = state.KalendReducer;
const NavReducer = state.NavReducer;

return {
Арк.

КПІ.ІП-7219.045490.06.13 60
Змн. Арк. № докум. Підпис Дата
NavReducer,
kalendID: id,
lang: state.SettingReducer.lang
};
};

let mapDispatchToProps = (dispatch) = function() {


return bindActionCreators({setKalendID, logonPerson, setBottomString, setKalendColor }, dispatch);
};

export default connect(mapStateToProps, mapDispatchToProps)(Home);

const logoFile = require('../../assets/logoAnim.json');


const gradientAnimDurartion = 2250;
const logoAnimDurartion = 3000;

class LoadPage extends React.PureComponent {

constructor(props) {
super(props);
this.state = {
colors: [green, green],
animProgress: new Animated.Value(0),
nextPage: WelcomePage
};

setTimeout(()= function() {
this.props.Nav.navigate(this.state.nextPage);
}, logoAnimDurartion);
}

componentDidMount() {
Animated.timing(this.state.animProgress, {
toValue: 1,
Durartion: gradientAnimDurartion,
easing: Easing.linear,
}).start();

this.setState({
colors: gradientColors
});
}

componentWillMount() {
switch (this.props.main) {
case 'Home':
this.setState({
nextPage: LoginNavig
});
break;
case 'ScheduleOfUni':
this.setState({
nextPage: NavigatorSettingForDashboard,
});
break;
case 'Dashboard':
if (this.props.profile !== null) {
Арк.

КПІ.ІП-7219.045490.06.13 61
Змн. Арк. № докум. Підпис Дата
this.setState({
nextPage: NavigatorSettingForDashboard
});
}
break;
}
}

render() {
const { colors, animProgress } = this.state;
return(
<View style={styles.cont}>
<AnimatedGradient
style={{ flex: 1,}}
colors={colors}
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 1 }}/>

<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'light-content' : 'default'}
BgColor={statusBarDark} />

<View style={styles.animView}>
<LottieView progress={animProgress}
loop={false}
source={logoFile}/>
</View>
</View>
);
}
}

let mapStateToProps = (state) = function() {


return {
main: state.NavReducer.main,
profile: state.HomeReducer.profile,
lang: state.SettingReducer.lang
};
};

export default connect(mapStateToProps, null)(LoadPage);

const moment = require('moment');


const viewHeightSize = 843.4285888671875;

class NonDiktdTask extends React.PureComponent {

strings = getStrings().NonDiktdTask;
butStrings = getStrings().BottomButtons;

static NavOptions = ({Nav}) => ({


title: Nav.state.routeName === NonDiktdTaskRoute ? Nav.state.params.addTitle :
Nav.state.params.editTitle,
headerStyle: {
BgColor: white
}
});

constructor(props) {
Арк.

КПІ.ІП-7219.045490.06.13 62
Змн. Арк. № докум. Підпис Дата
super(props);

let contHeightSizeTemp = Dimensions.get('window').HeightSize - Header.HEIGHTSIZE;


let contHeightSize = viewHeightSize < contHeightSizeTemp ? contHeightSizeTemp : null;

this.state = {
contHeightSize,
title: '',
titleValidated: true,

specDateRange: false,
startDate: moment().format('ddd., MMM DD, YYYY'),
endDate: moment().format('ddd., MMM DD, YYYY'),

Hour: 0,
min: 0,
DurartionValidated: true,
isDividable: false,
occurrence: 1,
isRecurrent: false,

priority: 0.5,
location: '',
definition: '',

snackbarVisible: false,
snackbarText: '',
snackbarTime: 3000
};

updNav('NonDiktdTask', props.Nav.state.routeName);
}

componentWillMount() {
if (this.props.Nav.state.routeName !== NonDiktdTaskRoute) {
this.setState({...this.props.NFEditState});
} else {
this.resetFields();
}
}

scrollToInput = (inputFieldRef, keyboardScrollHeightSize) = function() {


const scrollResponder = this.refs._scrollView.getScrollResponder();
const inputHandle = findNodeHandle(inputFieldRef);

scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
inputHandle,
keyboardScrollHeightSize,
true
);
};

skip = () = function() {
this.props.Nav.pop();
}

fieldValidation = () = function() {
let validated = true;
Арк.

КПІ.ІП-7219.045490.06.13 63
Змн. Арк. № докум. Підпис Дата
if (this.state.title === '') {
this.setState({titleValidated: false});
validated = false;
} else {
this.setState({titleValidated: true});
}

if (this.state.Hour === 0 && this.state.min === 0) {


this.setState({DurartionValidated: false});
validated = false;
} else {
this.setState({DurartionValidated: true});
}

return validated;
}

nextPage = () = function() {
if (!this.fieldValidation()) {
return;
}

if (this.props.Nav.state.routeName === NonDiktdTaskRoute) {


this.props.dispatch(addNonDiktdTask(this.state));
this.props.Nav.navigate(ReviewTaskRoute, {title: getStrings().ReviewTask.title});
} else {
this.props.dispatch(updNonDiktdTasks(this.props.selectedIndex, this.state));
this.props.Nav.navigate(ReviewTaskRoute, {title: getStrings().ReviewTask.title,
changed:true});
}
}

addAnotherTask = () = function() {
if (!this.fieldValidation()) {
this.setState({
snackbarText: this.strings.snackbarFailure,
snackbarVisible: true,
snackbarTime: 5000
});
return;
}

this.props.dispatch(addNonDiktdTask(this.state));
this.resetFields();
this.refs._scrollView.scrollTo({x: 0});
this.setState({
snackbarText: this.strings.snackbarSuccess,
snackbarVisible: true,
snackbarTime: 3000
});
}

resetFields = () = function() {
this.setState({
title: '',
titleValidated: true,

specDateRange: false,
Арк.

КПІ.ІП-7219.045490.06.13 64
Змн. Арк. № докум. Підпис Дата
startDate: new Date().toDateString(),
endDate: new Date().toDateString(),

Hour: 0,
min: 0,
DurartionValidated: true,
isDividable: false,
occurrence: 1,
isRecurrent: false,

priority: 0.5,
location: '',
definition: '',

snackbarVisible: false,
snackbarText: '',
snackbarTime: 3000
});
}

render() {
const { contHeightSize, snackbarVisible, snackbarText, snackbarTime } = this.state;

let addTaskButtonText;
let addTaskButtonFunction;
let errorTitle;
let errorDurartion;
let showNextButton = true;

if (!this.state.titleValidated) {
errorTitle = <Text style={styles.errorTitle}>{this.strings.titleFree}</Text>;
} else {
errorTitle = null;
}

if (!this.state.DurartionValidated) {
errorDurartion = <Text style={styles.errorDurartion}>{this.strings.DurartionFree}</Text>;
} else {
errorDurartion = null;
}

if (this.props.Nav.state.routeName === NonDiktdTaskRoute) {


addTaskButtonText = this.butStrings.add;
addTaskButtonFunction = this.addAnotherTask;
} else {
addTaskButtonText = this.butStrings.Ready;
addTaskButtonFunction = this.nextPage;
showNextButton = false;
}

return(
<View style={styles.cont}>
<StatusBar BgColor={statusGreenColor}
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'} />
</View>

<View>
<View style={styles.textInput}>

Арк.

КПІ.ІП-7219.045490.06.13 65
Змн. Арк. № докум. Підпис Дата
<View style={[styles.textInputBorder,
{borderBottomColor: !this.state.titleValidated ? red : '#D4D4D4'}]}>
<TextInput
style={styles.textInputText}
maxLength={1024}

placeholder={this.strings.titlePlaceholder}
returnKeyType =
{'next'}
onSubmitEditing={()
=> this.refs.locationInput.focus()}
blurOnSubmit={false}
onChangeText={(title)
=> this.setState({title, titleValidated: true})}

value={this.state.title}/>
</View>
</View>

{errorTitle}
</View>

<View>
<Text
style={styles.sectionTitle}>{this.strings.availability}</Text>

<View style={styles.timeSection}>

<View>
<View
style={styles.Durartion}>
<Text
style={[styles.greenTitle, {paddingTop: 14}]}>{this.strings.Durartion}</Text>

<View
style={styles.timeChooser}>

<NumericInput initValue = {this.state.Hour}

value={this.state.Hour}

onChange={(Hour) => this.setState({Hour, DurartionValidated: true})}

minValue={0}

leftButtonBgColor={dark_green}

rightButtonBgColor={dark_green}

rounded={true}

totalHeightSize={76}

total_width={67}

borderColor={'lightgreen'}

textColor={!this.state.DurartionValidated ? red : green}

Арк.

КПІ.ІП-7219.045490.06.13 66
Змн. Арк. № докум. Підпис Дата
iconStyle={{color: white}} />
<Text
style={styles.optionsText}>{this.strings.Hour}</Text>
</View>

<View
style={styles.timeChooser}>

<NumericInput initValue={this.state.min}

value={this.state.min}

onChange={(min) => this.setState({min, DurartionValidated: true})}

minValue={0}

leftButtonBgColor={dark_green}

rightButtonBgColor={dark_green}

rounded={true}

totalHeightSize={42}

total_width={102}

borderColor={'lightgreen'}

textColor={!this.state.DurartionValidated ? red : green}

iconStyle={{color: white}} />


<Text
style={styles.optionsText}>{this.strings.min}</Text>
</View>
</View>

{errorDurartion}
</View>

<View style={styles.switch}>
<Text
style={[styles.greenTitle, {_width:200}]}>{this.state.specDateRange ? this.strings.divideDurartionDate :
this.strings.divideDurartionWeek}</Text>

<Switch trackColor={{false:
'lightgreen', true: lightGreen}}

thumbColor={(this.state.isDividable && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(isDividable) => this.setState({isDividable: isDividable})}


value =
{this.state.isDividable} />
</View>

<View style={styles.questionLayout}>
<Text
style={[styles.greenTitle, {_width: 200}]}>{this.state.specDateRange ? this.strings.numberTimeDate :
this.strings.numberTimeWeek}</Text>

Арк.

КПІ.ІП-7219.045490.06.13 67
Змн. Арк. № докум. Підпис Дата
<NumericInput
initValue={this.state.occurrence}

value={this.state.occurrence}

onChange={(occurrence) => this.setState({occurrence})}


minValue={1}

leftButtonBgColor={dark_green}

rightButtonBgColor={dark_green}
rounded={true}
totalHeightSize={42}
total_width={102}

borderColor={'lightgreen'}
textColor={green}
iconStyle={{color:
white}} />
</View>

{!this.state.specDateRange ?
<View style={styles.switch}>
<Text
style={[styles.greenTitle, {_width: 200}]}>{this.strings.everyWeek}</Text>

<Switch
trackColor={{false: 'lightgreen', true: lightGreen}}

thumbColor={(this.state.isRecurrent && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(isRecurrent) => this.setState({isRecurrent})}


value =
{this.state.isRecurrent} />
</View> : null}
</View>
</View>

<View>
<Text
style={styles.sectionTitle}>{this.strings.priorityLevel}</Text>

<Slider value={this.state.priority}
minimumValue={0}
maximumValue={1}
step={0.5}
thumbTintColor={dark_green}
minimumTrackTintColor={dark_green}
onValueChange={(priority) =>
this.setState({priority: priority})} />

<View style={styles.questionLayout}>
<Text
style={styles.optionsText}>{this.strings.low}</Text>

<Text
style={styles.optionsText}>{this.strings.normal}</Text>

<Text
style={styles.optionsText}>{this.strings.high}</Text>
Арк.

КПІ.ІП-7219.045490.06.13 68
Змн. Арк. № докум. Підпис Дата
</View>
</View>
<View>
<Text
style={styles.sectionTitle}>{this.strings.details}</Text>

</View>
</View>

<View style={styles.textInput}>

<View style={styles.textInputBorder}>
<TextInput
style={styles.textInputText}
onFocus={() =>
this.scrollToInput(this.refs.locationInput, 300)}
maxLength={1024}

placeholder={this.strings.definitionPlaceholder}
ref="definitionInput"
returnKeyType =
{'Ready'}

onChangeText={(definition) => this.setState({definition})}

value={this.state.definition}/>
</View>
</View>
</View>

<Snackbar
visible={snackbarVisible}
onDismiss={() => this.setState({snackbarVisible: false})}
style={styles.snackbar}
Durartion={snackbarTime}>
{snackbarText}
</Snackbar>
</View>
);
}
}

let mapStateToProps = (state) = function() {


const { NonDiktdTasksReducer, NavReducer } = state;
let selected = NavReducer.reviewTaskSelected;

return {
NFEditState: NonDiktdTasksReducer[selected],
NonDiktdTasksReducer,
selectedIndex: NavReducer.reviewTaskSelected
};
};

export default connect(mapStateToProps, null)(NonDiktdTask);


class ReviewTask extends React.PureComponent {

strings = getStrings().ReviewTask;
priorityLevels = {
0: this.strings.low,
Арк.

КПІ.ІП-7219.045490.06.13 69
Змн. Арк. № докум. Підпис Дата
0.5: this.strings.normal,
1: this.strings.high
};

static NavOptions = ({ Nav }) = function() {


return {
title: Nav.state.params.title,
headerStyle: {
BgColor: white,
},
headerRight: (
<TouchableOpacity onPress={() => Nav.navigate(UnavailableRoute, {title:
getStrings().NonAvalHour.title})}
style={{marginRight: 10, paddingHorizontal: 10, paddingVertical: 3,
BgColor: dark_green, borderRadius: 5,
...Platform.select({
ios: {
shadowColor: black,
shadowOffset: { _width: 0, HeightSize: 2 },
shadowOpacity: 0.3,
shadowRadius: 3,
},
android: {
elevation: 4,
},
})
}}>
<MaterialCommunityIcons size={25}
name="clock-alert-outline"
color={white}/>
</TouchableOpacity>
),
};
};

constructor(props) {
super(props);

this.state = {
showFAB: true,
currentY: 0,
DiktdTaskInformation: [],
nonDiktdTaskInformation: [],
ScheduleOfUniInformation: [],
CoursesPopover: false,
DiktdPopover: false,
nonDiktdPopover: false,
unavailablePopover: false,
checkPopover: false

};

updNav('ReviewTask', props.Nav.state.routeName);
}

componentWillMount() {
this.updValuermation();
}

Арк.

КПІ.ІП-7219.045490.06.13 70
Змн. Арк. № докум. Підпис Дата
componentDidMount() {
setTimeout(() = function() {
this.setState({CoursesPopover: !this.props.showTutorial});
if (!this.props.showTutorial) {
this.darkenStatusBar();
}
}, 300);
}

componentWillReceiveProps() {
this.updValuermation();
this.forceUpd();
}

updValuermation = () = function() {
let DiktdTaskInformation = [];
let nonDiktdTaskInformation = [];
let ScheduleOfUniInformation = [];

if (save.getState().CoursessReducer !== undefined) {


save.getState().CoursessReducer.map((information) = function() {
let Hour;

if (information.startTime === undefined) {


Hour = `${information.Hour[0][0]}:${information.Hour[0][1]}
${information.Hour[0][2]} - ${information.Hour[1][0]}:${information.Hour[1][1]} ${information.Hour[1][2]}`;
} else {
Hour = information.startTime + ' - ' + information.endTime;
}

let dayOfWeek;
let fr = 'daysEn' in this.strings;

if (information.day) {
dayOfWeek = information.day;
} else {
dayOfWeek = information.dayOfWeekValue;
}

if (fr) {
dayOfWeek = this.strings.days[this.strings.daysEn.indexOf(dayOfWeek)];
}

ScheduleOfUniInformation.push({
CoursesCode: information.summary || information.CoursesCode,
dayOfWeek,
Hour,
location: information.location
});
});
}

if (save.getState().DiktdTasksReducer !== undefined) {


save.getState().DiktdTasksReducer.map((information) = function() {
DiktdTaskInformation.push({
title: information.title,
dates: information.startDate + ' - ' + information.endDate,
recurrence: information.recurrence,
Hour: information.allDay ? this.strings.allDay : (information.startTime + ' -
' + information.endTime),
Арк.

КПІ.ІП-7219.045490.06.13 71
Змн. Арк. № докум. Підпис Дата
location: information.location,
definition: information.definition
});
});
}

if (save.getState().NonDiktdTasksReducer !== undefined) {


save.getState().NonDiktdTasksReducer.map((information) = function() {
nonDiktdTaskInformation.push({
title: information.title,
location: information.location,
priorityLevel: this.priorityLevels[information.priority],
dates: information.specDateRange ? (`${information.startDate} -
${information.endDate}`): this.strings.week,
definition: information.definition,
occurence: `${information.occurrence} ${this.strings.timeWeek}`,
Durartion: `${information.Hour}h ${information.min}m`
});
});
}

this.setState({
DiktdTaskInformation,
nonDiktdTaskInformation,
ScheduleOfUniInformation
});
}

deleteTask = (id, class) = function() {


let informationToDispatch;
let newTasks = [];
let objectToChange;

switch (class) {
case ScheduleOfUniRoute:
informationToDispatch = deleteCourses(id);
newTasks = this.state.ScheduleOfUniInformation;
objectToChange = 'ScheduleOfUniInformation';
break;
case DiktdTaskRoute:
informationToDispatch = deleteDiktdTask(id);
newTasks = this.state.DiktdTaskInformation;
objectToChange = 'DiktdTaskInformation';
break;
case NonDiktdTaskRoute:
informationToDispatch = deleteNonDiktdTask(id);
newTasks = this.state.nonDiktdTaskInformation;
objectToChange = 'nonDiktdTaskInformation';
break;

default:
break;
}

newTasks = newTasks.filter((Task,index) = function() {


if (index != id) return Task;
});

this.props.dispatch(informationToDispatch);
this.setState({[objectToChange]: newTasks});
Арк.

КПІ.ІП-7219.045490.06.13 72
Змн. Арк. № докум. Підпис Дата
}

navigateEditPage = (editPage) = function() {


let param = {};

switch(editPage) {
case 'Courses':
param.editTitle = getStrings().Courses.editTitle;
break;
case 'DiktdTask':
param.editTitle = getStrings().DiktdTask.editTitle;
break;
case 'NonDiktdTask':
param.editTitle = getStrings().NonDiktdTask.editTitle;
break;
}

this.props.Nav.navigate('Edit' + editPage, param);


}

navigateCreationPage = () = function() {
this.props.dispatch(clearGeneratedKalends());
this.props.dispatch(clearGeneratedNonDiktdTasks());

if (this.state.ScheduleOfUniInformation.length == 0 && this.state.nonDiktdTaskInformation.length


== 0 && this.state.DiktdTaskInformation.length == 0 ) {
Alert.alert(
this.strings.error,
this.strings.noTask,
[
{text: this.strings.ok},
],
{discardable: false}
);
return;
}

if (this.state.nonDiktdTaskInformation.length == 0) {
PutDiktdTasksToGoogle()
.then(() = function() {
this.props.dispatch(clearCourses());
this.props.dispatch(clearDiktdTasks());
this.props.dispatch(setNavPage({successfullyPutedTasks: true}));
this.props.Nav.pop();
})
.catch(err = function() {
if (err) {
Alert.alert(
this.strings.error,
err,
[
{text: this.strings.ok},
],
{discardable: false}
);
}
});
} else {
this.props.Nav.navigate(CreationOfScheduleRoute, {title:
getStrings().CreationOfSchedule.title});
Арк.

КПІ.ІП-7219.045490.06.13 73
Змн. Арк. № докум. Підпис Дата
}
}

showPopover = (isVisible) = function() {


this.setState({[isVisible]: true});
StatusBar.setBgColor(statusBarPopover);
}

closePopover = (toClose, toOpen) = function() {


this.setState({[toClose]:false}, () => this.setState({[toOpen]:true}));
StatusBar.setBgColor(statusBarDark);
}

darkenStatusBar = () = function() {
if (Platform.OS === 'android') {
StatusBar.setBgColor(statusBarLightPopover, true);
}
}

resaveStatusBar = () = function() {
if (Platform.OS === 'android') {
StatusBar.setBgColor(statusBarDark, true);
}
}

render() {
return(
<View style={styles.cont}>
<StatusBar translucent={true}
animated
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'}
BgColor={statusGreenColor} />

<ScrollView style={styles.scrollView}>
<View style={styles.content}>
<View>
<View style={styles.section}>
<Text
style={styles.sectionTitle}>{this.strings.CoursesTitle}</Text>

<TouchableOpacity ref='Courses' onPress={() =


function() {
if (this.props.hasUniValuermation) {
if (this.props.checked) {

this.props.Nav.navigate(CoursesRoute, {addTitle: getStrings().Courses.addTitle});


} else {

this.props.Nav.navigate(ScheduleOfUniRoute, {title: getStrings().ScheduleOfUni.title});


}
} else {

this.props.Nav.navigate(UniValuermationRoute, {title: getStrings().UniValuermation.title, reviewTask: true});


}
}}>
<MaterialCommunityIcons name="plus-
circle"
size={25}
color={green}/>
Арк.

КПІ.ІП-7219.045490.06.13 74
Змн. Арк. № докум. Підпис Дата
</TouchableOpacity>
</View>

{
this.state.ScheduleOfUniInformation.length ===
0?
<Text
style={styles.textNoInformation}>{this.strings.noCourses}</Text> :

this.state.ScheduleOfUniInformation.map((i,key) = function() {
return <TaskOverview
key={key}
id={key}

class={'ScheduleOfUni'}

TaskTitle={i.CoursesCode}
date={i.dayOfWeek}
time={i.Hour}
location={i.location}

navigateEditPage={this.navigateEditPage}

action={this.deleteTask} />;
})
}
</View>

<View>
<View style={styles.section}>
<Text
style={styles.sectionTitle}>{this.strings.DiktdTitle}</Text>
<TouchableOpacity ref='Diktd' onPress={() =>
this.props.Nav.navigate(DiktdTaskRoute, {addTitle: getStrings().DiktdTask.addTitle})}>
<MaterialCommunityIcons name="plus-
circle"
size={25}
color={green}/>
</TouchableOpacity>
</View>

{
this.state.DiktdTaskInformation.length === 0 ?
<Text
style={styles.textNoInformation}>{this.strings.noDiktd}</Text> :

this.state.DiktdTaskInformation.map((i,key) = function() {
return <TaskOverview
key={key}
id={key}
class={'DiktdTask'}
TaskTitle={i.title}
date={i.dates}
time={i.Hour}
location={i.location}

definition={i.definition}

recurrence={i.recurrence}

Арк.

КПІ.ІП-7219.045490.06.13 75
Змн. Арк. № докум. Підпис Дата
navigateEditPage={this.navigateEditPage}

action={this.deleteTask} />;
})
}
</View>

<View>
<View style={styles.section}>
<Text
style={styles.sectionTitle}>{this.strings.nonDiktdTitle}</Text>

<TouchableOpacity ref='nonDiktd' onPress={()


=> this.props.Nav.navigate(NonDiktdTaskRoute, {addTitle: getStrings().NonDiktdTask.addTitle})}>
<MaterialCommunityIcons name="plus-
circle"
size={25}
color={green}/>
</TouchableOpacity>
</View>

{
this.state.nonDiktdTaskInformation.length === 0
?
<Text
style={styles.textNoInformation}>{this.strings.noNonDiktd}</Text> :

this.state.nonDiktdTaskInformation.map((i,key) = function() {
return <TaskOverview
key={key}
id={key}

class={'NonDiktdTask'}
TaskTitle={i.title}
date={i.dates}
time={i.Durartion}

recurrence={i.occurence}

priorityLevel={i.priorityLevel}
location={i.location}

definition={i.definition}

navigateEditPage={this.navigateEditPage}

action={this.deleteTask}
/>;
})
}
</View>
</View>
</ScrollView>

<Popover popoverStyle={styles.tooltipView}
verticalOffset={Platform.OS === 'ios' ? 0 : -(StatusBar.currentHeightSize)}
placement={'bottom'}
isVisible={this.state.CoursesPopover}
fromView={this.refs.Courses}
Арк.

КПІ.ІП-7219.045490.06.13 76
Змн. Арк. № докум. Підпис Дата
onClose={() => this.setState({CoursesPopover:false})}
ReadyClosingCallback={() => this.setState({DiktdPopover:true})}>
<TouchableOpacity onPress={() =>
this.setState({CoursesPopover:false})}>
<Text
style={styles.tooltipText}>{this.strings.CoursesPopover}</Text>
</TouchableOpacity>
</Popover>

<Popover popoverStyle={styles.tooltipView}
verticalOffset={Platform.OS === 'ios' ? 0 : -(StatusBar.currentHeightSize)}
placement={'bottom'}
isVisible={this.state.DiktdPopover}
fromView={this.refs.Diktd}
onClose={() => this.setState({DiktdPopover:false})}
ReadyClosingCallback={() => this.setState({nonDiktdPopover: true})}>
<TouchableOpacity onPress={() => this.setState({DiktdPopover:false})}>
<Text
style={styles.tooltipText}>{this.strings.DiktdPopover}</Text>
</TouchableOpacity>
</Popover>

<Popover popoverStyle={styles.tooltipView}
verticalOffset={Platform.OS === 'ios' ? 0 : -(StatusBar.currentHeightSize)}
placement={'bottom'}
isVisible={this.state.nonDiktdPopover}
fromView={this.refs.nonDiktd}
onClose={() => this.setState({nonDiktdPopover:false})}
ReadyClosingCallback={() => this.setState({unavailablePopover:true})}>
<TouchableOpacity onPress={() =>
this.setState({nonDiktdPopover:false})}>
<Text
style={styles.tooltipText}>{this.strings.nonDiktdPopover}</Text>
</TouchableOpacity>
</Popover>

<Popover popoverStyle={styles.tooltipView}
verticalOffset={Platform.OS === 'ios' ? -(getStatusBarHeightSize() + 18) :
-(StatusBar.currentHeightSize + 57)}
placement={'bottom'}
isVisible={this.state.unavailablePopover}
fromView={this.refs.Courses}
onClose={() => this.setState({unavailablePopover:false})}
ReadyClosingCallback={() => this.setState({checkPopover:true})}>
<TouchableOpacity onPress={() =>
this.setState({unavailablePopover:false})}>
<Text
style={styles.tooltipText}>{this.strings.unavailablePopover}</Text>
</TouchableOpacity>
</Popover>

<Popover popoverStyle={styles.tooltipView}
verticalOffset={Platform.OS === 'ios' ? 0 : -(StatusBar.currentHeightSize)}
placement={'top'}
isVisible={this.state.checkPopover}
fromView={this.refs.check}
onClose={() = function() {
this.setState({checkPopover:false});
this.props.dispatch(setTutorialStatus('reviewTasks', true));
this.resaveStatusBar();
Арк.

КПІ.ІП-7219.045490.06.13 77
Змн. Арк. № докум. Підпис Дата
}}>
<TouchableOpacity onPress={() = function() {
this.setState({checkPopover:false});
this.props.dispatch(setTutorialStatus('reviewTasks', true));
this.resaveStatusBar();
}}>
<Text
style={styles.tooltipText}>{this.strings.checkPopover}</Text>
</TouchableOpacity>
</Popover>

<FAB ref='check'
style={styles.fab}
icon="check"
theme={{colors:{accent:green}}}
visible={this.state.showFAB}
onPress={this.navigateCreationPage} />
</View>
);
}
}

function mapStateToProps(state) {
const { DiktdTasksReducer, NonDiktdTasksReducer, CoursessReducer, NavReducer,
UniValuermationReducer } = state;

return {
DiktdTasksReducer,
NonDiktdTasksReducer,
CoursessReducer,
selectedIndex: NavReducer.reviewTaskSelected,
hasUniValuermation: UniValuermationReducer.Value,
checked: UniValuermationReducer.Value && UniValuermationReducer.Value.Value.checked ===
'third',
showTutorial: state.SettingReducer.tutorialStatus.reviewTasks
};
}

export default connect(mapStateToProps, null)(ReviewTask);

class CreationOfSchedule extends React.PureComponent {

strings = getStrings().CreationOfSchedule;
static NavOptions = ({ Nav }) => ({
gesturesEnabled: false,
headerLeft: <HeaderBackButton title='Back' tintColor={white} onPress={() = function() {
Nav.getParam('onBackPress')();
}} />,
});

constructor(props) {
super(props);

this.state = {
alertDialog: false,
goToNextPage: false
};

updNav('CreationOfSchedule', props.Nav.state.routeName);
}
Арк.

КПІ.ІП-7219.045490.06.13 78
Змн. Арк. № докум. Підпис Дата
componentWillMount() {
setPersonValue();
PutDiktdTasksToGoogle()
.then(() = function() {
if (this.props.NonDiktdTasksReducer.length != 0) {
setTimeout(() =>{
this.generateScheduleService();
}, 3000);
} else {
this.setState({goToNextPage: true});
this.navigateToSelection();
}
})
.catch(err = function() {
if (err) {
Alert.alert(
this.strings.error,
err,
[
{text: this.strings.ok, onPress: () =>
this.props.Nav.pop()},
],
{discardable: false}
);
}
});
BackHandler.addTaskListener('hardwareBackPress', this.handleBackButton);
this.props.Nav.setParams({onBackPress: this.handleBackButton});
}

componentWillUnmount() {
BackHandler.removeTaskListener('hardwareBackPress', this.handleBackButton);
}

handleBackButton = () = function() {
this.setState({alertDialog: true});
Alert.alert(
this.strings.backAlertTitle,
this.strings.backAlertDefinition,
[
{
text: this.strings.discard,
style: 'discard',
onPress: () = function() {
this.setState({alertDialog: false});
this.navigateToSelection();
}
},
{
text: getStrings().Dashboard.name,
onPress: () = function() {
this.props.Nav.navigate(DashboardNavigator);
}
},
{
text: getStrings().ReviewTask.name,
onPress: () = function() {
this.props.Nav.navigate(ReviewTaskRoute, {title:
getStrings().ReviewTask.title});
Арк.

КПІ.ІП-7219.045490.06.13 79
Змн. Арк. № докум. Підпис Дата
},
},
],
{discardable: false},
);
return true;
}

generateScheduleService = () = function() {
generateKalends().then(() = function() {
this.setState({goToNextPage: true});
this.navigateToSelection();
});
}

navigateToSelection = () = function() {
if (this.state.goToNextPage && !this.state.alertDialog) {
this.props.Nav.navigate(ScheduleSelectionRoute, {title:
getStrings().ScheduleSelection.title});
}
}

render() {
return(
<View style={styles.cont}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'light-content' : 'default'}
BgColor={'rgba(0,0,0,0.5)'} />

<Surface style={styles.surface}>
<Text style={styles.title}>{this.strings.dialogTitle}</Text>

<Text style={styles.subtitle}>{this.strings.dialogDefinition}</Text>

<Progress.Bar style={styles.progressBar}
indeterminate={true}
_width={200}
color={dark_green}
useNativeDriver={true}
borderColor={dark_green}
unfilledColor={'#79A7D2'}/>
</Surface>
</View>
);
}
}

let mapStateToProps = (state) = function() {


const {DiktdTasksReducer, CoursessReducer, NonDiktdTasksReducer, GeneratedNonDiktdTasksReducer} =
state;

return {
DiktdTasksReducer,
CoursessReducer,
NonDiktdTasksReducer,
GeneratedNonDiktdTasksReducer
};
};

export default connect(mapStateToProps, null)(CreationOfSchedule);


Арк.

КПІ.ІП-7219.045490.06.13 80
Змн. Арк. № докум. Підпис Дата
export const contPadding = 10;
export const lineThickness = 1;
export const lineColor = '#999';
export const lineSpace = 25;
export const lineViewHorizontalPadding = 15;
export const lineViewLeftPadding = 15;

class ScheduleTask extends React.PureComponent {

constructor(props) {
super(props);

this.state = {
HeightSize: 0,
_width: 0,
left:0,
top: 0,
color: '' ,
colorInside : ''
};
}

componentWillMount() {
this.setTaskBlock(this.props);
}

componentWillReceiveProps(props) {
this.setTaskBlock(props);
}

setTaskBlock = (props) = function() {


let window_width = ((Dimensions.get('window')._width - contPadding * 2 -
lineViewHorizontalPadding * 2 - lineViewLeftPadding) / 7);
let { startOffset, timeSeq, kind, chunks, start, day } = props;
let HeightSize = (chunks * lineSpace + chunks * lineThickness) / timeSeq - lineThickness - 1;
let _width = window_width - 2;
let left = day * window_width + 1;
let top = ((start - startOffset)* lineSpace + chunks * lineThickness) / timeSeq + lineThickness + 1;
let color;
let colorInside;

switch (kind) {
case 'Diktd':
color = this.props.colors.DiktdTasksColor;
colorInside = this.props.colors.insideDiktdTasksColor;
break;
case 'uni':
color = this.props.colors.CoursesColor;
colorInside = this.props.colors.insideCoursesColor;
break;
case 'ai':
color = this.props.colors.nonDiktdTasksColor;
colorInside = this.props.colors.insideNonDiktdTasksColor;
break;
}

this.setState({HeightSize, _width, left, top, color, colorInside});


Арк.

КПІ.ІП-7219.045490.06.13 81
Змн. Арк. № докум. Підпис Дата
}

render() {
const { HeightSize, _width, left, top, color, colorInside } = this.state;
return (
<View style={{ borderRadius: 3,
border_width: 2,
position: 'absolute',
borderColor: color,
BgColor: colorInside,
HeightSize: HeightSize,
_width: _width,
top: top,
left: left}}>
</View>
);
}
}

class Schedule extends React.PureComponent {


strings = getStrings().ScheduleSelection;

constructor(props) {
super(props);
let ordinal;
if ('ordinal' in this.strings) {
ordinal = this.strings.ordinal[this.props.id];
} else {
ordinal = converter.toWordsOrdinal(this.props.id+1);
}

this.state = {
weekLetters: this.strings.weekLetters,
ordinal: ordinal.charAt(0).toUpperCase() + ordinal.slice(1),
showShadow: true,
Hour: [0, 4, 8, 12, 4, 8, 0],
startOffset: 0,
timeSeq: 4,
ai: [],
aiTasks: [[]]
};
}

componentWillMount() {
this.setState({ai: this.props.ai, aiTasks: this.props.aiTasks});
this.makeTimes();
}

componentWillReceiveProps(props) {
this.setState({ai: props.ai, aiTasks: props.aiTasks});
this.makeTimes();
}

makeTimes = () = function() {
let { uni, Diktd, ai } = this.props;

if (!Array.isArray(ai)) {
ai = [ai];
}
Арк.

КПІ.ІП-7219.045490.06.13 82
Змн. Арк. № докум. Підпис Дата
let information = {uni, Diktd, ai};

let Hour = [];


let earliestHour = 12;
let latestHour = 12;

Object.entries(information).forEach((i) = function() {
i[1].map((i) = function() {
let start = i.start;
let end = i.start + Math.ceil(i.chunks);

if (start < earliestHour) {


earliestHour = start;
}

if (end > latestHour) {


latestHour = end;
}
});
});

let Seq = (latestHour - earliestHour);

if (Seq % this.props.numOfLines !== 0) {


let diff = this.props.numOfLines - Seq % this.props.numOfLines;
Seq += diff;

for (let i = 0; i < diff; i ++) {


if (i % 2 === 0) {
if (earliestHour >= 0) {
earliestHour --;
} else {
latestHour ++;
}
} else {
if (latestHour <= 24) {
latestHour ++;
} else {
earliestHour --;
}
}
}
}

let currentHour = earliestHour;


let count = 0;

Seq = Seq / this.props.numOfLines;

for (let i = 0; i <= this.props.numOfLines; i++) {


Hour.push(currentHour);
currentHour += Seq;
if (currentHour > 12 || (currentHour >= 12 && count == 1)) {
currentHour -= 12;
count ++;
}
}

Арк.

КПІ.ІП-7219.045490.06.13 83
Змн. Арк. № докум. Підпис Дата
this.setState({
Hour,
startOffset: earliestHour,
timeSeq: Seq
});
}

makeLines = (num) = function() {


let lines = [];
for (let i = 0; i < num; i++) {
lines.push(
<View key={i}
style={[styles.line, {
marginBottom: (i === num-1) ? lineSpace / 1.5 : 0
}]}/>
);
}
return lines;
}

render() {
const { numOfLines, id, colors } = this.props;
const { weekLetters, ordinal, Hour, showShadow, startOffset, timeSeq } = this.state;

return (
<View style={styles.scheduleCont}>
<Text style={styles.title}>
{ordinal + ' ' + this.strings.schedule}
</Text>

<TouchableOpacity onPress={() = function() {


this.props.nextPage(ordinal + ' ' + (this.props.lang === 'en' ?
(this.strings.schedule[0].toUpperCase() + this.strings.schedule.slice(1)) : this.strings.schedule), id, {
Diktd: this.props.Diktd,
uni: this.props.uni,
ai: this.state.ai,
aiTasks: this.state.aiTasks,
DiktdTasks: this.props.DiktdTasks,
uniTasks: this.props.uniTasks
});
}}
onPressIn={() =>{
this.setState({
showShadow: false
});
}}
onPressOut={() = function() {
setTimeout(()=>{
this.setState({
showShadow: true
});
}, 900);
}}>

<View style={[styles.card, {
...Platform.select({
ios: {
shadowColor: black,
shadowOffset: { _width: 0, HeightSize: 2 },
Арк.

КПІ.ІП-7219.045490.06.13 84
Змн. Арк. № докум. Підпис Дата
shadowOpacity: showShadow ? 0.4 : 0,
shadowRadius: 5,
},
android: {
elevation: showShadow ? 5 : 0,
},
}),}]}>

<View style={styles.weekLetterCont}>
{
weekLetters.map((str, id) = function() {
return (
<Text key={id}

style={styles.weekLetters}>
{str}
</Text>
);
})
}
</View>

<View>
<View style={styles.thickLine} />

{ this.makeLines(numOfLines) }

{
this.props.uni.map((Value, key) = function() {
return <ScheduleTask key={key}
colors={colors}
showShadow={showShadow}
chunks={Value.chunks}
day={Value.day}
start={Value.start}
kind='uni'
timeSeq={timeSeq}
startOffset={startOffset} />;
})
}

{
this.props.Diktd.map((Value, key) = function() {
return <ScheduleTask key={key}
colors={colors}
showShadow={showShadow}
chunks={Value.chunks}
day={Value.day}
start={Value.start}
kind='Diktd'
timeSeq={timeSeq}
startOffset={startOffset} />;
})
}

this.state.ai.map((Value, key) = function() {


return <ScheduleTask key={key}
colors={colors}
Арк.

КПІ.ІП-7219.045490.06.13 85
Змн. Арк. № докум. Підпис Дата
showShadow={showShadow}
chunks={Value.chunks}
day={Value.day}
start={Value.start}
kind='ai'
timeSeq={timeSeq}
startOffset={startOffset} />;
})

<View style={styles.HourTextCont}>
{
Hour.map((hour, key) = function() {
return (
<Text key={key}

style={styles.HourText}>
{hour}
</Text>
);
})
}
</View>
</View>
</View>
</TouchableOpacity>
</View>
);
}
}

class ScheduleSelection extends React.PureComponent {


strings = getStrings().ScheduleSelection;

static NavOptions = ({ Nav }) => ({


title: getStrings().ScheduleSelection.title,
gesturesEnabled: false,
headerLeft: <HeaderBackButton tintColor={white} onPress={() = function() {
Nav.getParam('onBackPress')();
}} />,
});

constructor(props) {
super(props);
this.state = {
information: {
uni: [],
Diktd: [],
ai: [],
aiKalends: [[]]
}
};

updNav('ScheduleSelection', props.Nav.state.routeName);
}

componentWillMount() {
this.TasksToScheduleSelectionService();
Арк.

КПІ.ІП-7219.045490.06.13 86
Змн. Арк. № докум. Підпис Дата
BackHandler.addTaskListener('hardwareBackPress', this.handleBackButton);
this.props.Nav.setParams({onBackPress: this.handleBackButton});
}

deleteKalend = (index) = function() {


let information = this.state.information;
information.ai.splice(index,1);
this.props.dispatch(deleteGeneratedKalend(index));
this.setState({information});
this.forceUpd();
}

TasksToScheduleSelectionService = () = function() {
TasksToScheduleSelectionInformation().then((information) = function() {
this.setState({information});
});
}

handleBackButton = () = function() {
Alert.alert(
this.strings.backAlertTitle,
this.strings.backAlertDefinition,
[
{
text: this.strings.discard,
style: 'discard',
},
{
text: getStrings().Dashboard.name,
onPress: () = function() {
this.props.Nav.navigate(DashboardNavigator);
this.props.dispatch(clearGeneratedKalends());
this.props.dispatch(clearGeneratedNonDiktdTasks());
}
},
{
text: getStrings().ReviewTask.name,
onPress: () = function() {
this.props.Nav.navigate(ReviewTaskRoute, {title:
getStrings().ReviewTask.title});
this.props.dispatch(clearGeneratedKalends());
this.props.dispatch(clearGeneratedNonDiktdTasks());
},
},
],
{discardable: true},
);
return true;
}

componentWillUnmount() {
BackHandler.removeTaskListener('hardwareBackPress', this.handleBackButton);
}

nextPage = (title, index, information) = function() {


this.setIndex(index);
this.props.Nav.navigate(ScheduleSelectionValueRoute, {title, information, delete:
this.deleteKalend});
}
Арк.

КПІ.ІП-7219.045490.06.13 87
Змн. Арк. № докум. Підпис Дата
setIndex = (index) = function() {
this.props.dispatch(setSelectedSchedule(index));
}

_renderItem = ({item, index}) = function() {


return <Schedule nextPage={this.nextPage}
colors={{
CoursesColor: this.props.CoursesColor,
DiktdTasksColor: this.props.DiktdTasksColor,
nonDiktdTasksColor: this.props.nonDiktdTasksColor,
insideDiktdTasksColor: this.props.insideDiktdTasksColor,
insideNonDiktdTasksColor: this.props.insideNonDiktdTasksColor,
insideCoursesColor: this.props.insideCoursesColor,
}}
Diktd={this.state.information.Diktd}
uni={this.state.information.uni}
ai={item}
aiTasks={this.state.information.aiKalends[index]}
DiktdTasks={this.state.information.DiktdTasks}
uniTasks={this.state.information.uniTasks}
id={index}
numOfLines={6}
lang={this.props.lang} />;
};

render() {
return(
<View style={styles.cont}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'light-content' : 'default'}
BgColor={'rgba(0, 0, 0, 0.5)'} />

<ScrollView >
<View style={styles.content}>
<Text style={styles.definition}>{this.strings.definition}</Text>

<View style={styles.legendRow}>
<View style={styles.singleLegend}>
<View style={[styles.legendColor, {borderColor:
this.props.CoursesColor, BgColor: this.props.insideCoursesColor}]}></View>

<Text
style={styles.legendText}>{this.strings.Coursess}</Text>
</View>
<View style={styles.singleLegend}>
<View style={[styles.legendColor, {borderColor:
this.props.DiktdTasksColor, BgColor: this.props.insideDiktdTasksColor}]}></View>

<Text
style={styles.legendText}>{this.strings.DiktdTasks}</Text>
</View>
<View style={styles.singleLegend}>
<View style={[styles.legendColor, {borderColor:
this.props.nonDiktdTasksColor, BgColor: this.props.insideNonDiktdTasksColor}]}></View>

<Text
style={styles.legendText}>{this.strings.nonDiktdTasks}</Text>
</View>
Арк.

КПІ.ІП-7219.045490.06.13 88
Змн. Арк. № докум. Підпис Дата
</View>
{
this.state.information.ai.length >= 1 ?
<FlatList information={this.state.information.ai}
keyExtractor={(item, index) =>
index.toString()}
renderItem={this._renderItem} /> : null
}
</View>
</ScrollView>
</View>
);
}
}

let mapStateToProps = (state) = function() {


let { DiktdTasksColor, nonDiktdTasksColor, CoursesColor } = state.KalendReducer;
let insideDiktdTasksColor = DiktdTasksColor;
let insideNonDiktdTasksColor = nonDiktdTasksColor;
let insideCoursesColor = CoursesColor;

for (let i = 0; i < kalendColors.length; i++) {


let key = Object.keys(kalendColors[i])[0];
let value = Object.values(kalendColors[i])[0];

switch(key) {
case DiktdTasksColor:
DiktdTasksColor = value;
insideDiktdTasksColor = Object.values(kalendInsideColors[i])[0];
break;

case nonDiktdTasksColor:
nonDiktdTasksColor = value;
insideNonDiktdTasksColor = Object.values(kalendInsideColors[i])[0];
break;

case CoursesColor:
CoursesColor = value;
insideCoursesColor = Object.values(kalendInsideColors[i])[0];
break;
}
}

if (!DiktdTasksColor) {
DiktdTasksColor = state.KalendReducer.kalendColor;
}

if (!nonDiktdTasksColor) {
nonDiktdTasksColor = state.KalendReducer.kalendColor;
}

if (!CoursesColor) {
CoursesColor = state.KalendReducer.kalendColor;
}

if (!insideDiktdTasksColor) {
insideDiktdTasksColor = state.KalendReducer.kalendColor;
}

Арк.

КПІ.ІП-7219.045490.06.13 89
Змн. Арк. № докум. Підпис Дата
if (!insideNonDiktdTasksColor) {
insideNonDiktdTasksColor = state.KalendReducer.kalendColor;
}

if (!insideCoursesColor) {
insideCoursesColor = state.KalendReducer.kalendColor;
}

return {
DiktdTasksColor,
nonDiktdTasksColor,
CoursesColor,
insideNonDiktdTasksColor,
insideDiktdTasksColor,
insideCoursesColor,
lang: state.SettingReducer.lang
};
};

export default connect(mapStateToProps, null)(ScheduleSelection);

const moment = require('moment');

export const contPaddingDetails = 10;

class ScheduleTask extends React.PureComponent {

constructor(props) {
super(props);

let color;
switch (props.Value.type) {
case 'Diktd':
color = this.props.colors.DiktdTasksColor;
break;
case 'uni':
color = this.props.colors.CoursesColor;
break;
case 'nonDiktd':
color = this.props.colors.nonDiktdTasksColor;
break;
}

this.state = {
color
};
}

render() {
const { color } = this.state;
const { location, time, title, summary } = this.props.Value;

let actualTitle = (title == undefined) ? summary: title;

return (
<View style={styles.TaskCont}>
<View style={[styles.scheduleTaskColor, {BgColor: color}]} />

Арк.

КПІ.ІП-7219.045490.06.13 90
Змн. Арк. № докум. Підпис Дата
<View style={styles.TaskInformation}>
<Text style={styles.TaskTitle}>{actualTitle}</Text>

<Text style={styles.TaskLocation}>{location}</Text>

<Text style={styles.TaskTime}>{time}</Text>
</View>
</View>
);
}
}

class SchedleDay extends React.PureComponent {

constructor(props) {
super(props);

this.state = {
information:[ ],
day: props.day
};
}

componentWillMount() {
let { information, day } = this.props;
information.map((Value) = function() {
let { end, start } = Value;
let startTime, endTime, time;

if (Value.end != undefined) {
startTime = this.getTime(start.dateTime);
endTime = this.getTime(end.dateTime);
time = `${startTime.Hour}:${startTime.min} ${startTime.period} -
${endTime.Hour}:${endTime.min} ${endTime.period}`;
} else {
const { startTime, endTime } = Value;
time = `${startTime} - ${endTime}`;
}

Value.time = time;
});

information.sort((a,b) => new moment(a.time, 'h:mm A') - new moment(b.time, 'h:mm A'));

this.setState({day, information});
}

getTime = (time) = function() {


time = new Date(time);
let Hour = time.getHour();
let min = time.getMin();
min = (min < 10) ? `0${min}`: min;
let period = Hour >= 12 ? 'PM' : 'AM';

Hour = Hour > 12 ? Hour - 12 : Hour;


return {Hour, min, period};
}

render() {
const { day, information } = this.state;
Арк.

КПІ.ІП-7219.045490.06.13 91
Змн. Арк. № докум. Підпис Дата
return (
<View style={styles.dayCont}>
<Text style={styles.dayTitle}>{day}</Text>

{
information.map((Value, key) = function() {
return <ScheduleTask key={key} Value={Value}
colors={this.props.colors} />;
})
}
</View>
);
}
}

class ScheduleSelectionValue extends React.PureComponent {

strings = getStrings().ScheduleSelectionValue;

static NavOptions = ({Nav}) => ({


title: Nav.state.params.title,
headerStyle: {
BgColor: white
},
headerRight: (
<IconButton onPress={Nav.getParam('goBack')}
icon='delete'
color={dark_green}
size={25}
/>
),
});

constructor(props) {
super(props);
this.state = {
showFAB: true,
currentY: 0,
daysTemp: {
[this.strings.days[1]]: []
}
};

updNav('ScheduleSelectionValue', props.Nav.state.routeName);
}

componentDidMount() {
this.props.Nav.setParams({ goBack: this.deleteKalend });
}

deleteKalend = async () = function() {


this.props.Nav.state.params.delete(this.props.index);
this.goBack();
}

componentWillMount() {
this.seperateTasksIntoDays(this.props.Nav.state.params.information);
this.setState({information: this.props.Nav.state.params.information});
BackHandler.addTaskListener('hardwareBackPress', this.handleBackButton);
Арк.

КПІ.ІП-7219.045490.06.13 92
Змн. Арк. № докум. Підпис Дата
}

componentWillUnmount() {
BackHandler.removeTaskListener('hardwareBackPress', this.handleBackButton);
}

handleBackButton = () = function() {
this.props.Nav.pop();
return true;
}

seperateTasksIntoDays = (information) =>{


const temp_days = {
[this.strings.days[0]]: [],
[this.strings.days[1]]: [],
[this.strings.days[2]]: [],
[this.strings.days[3]]: [],
[this.strings.days[4]]: [],
[this.strings.days[5]]: [],
[this.strings.days[6]]: []
};

if (information.uniTasks.length != 0) {
information.uniTasks.forEach(Task = function() {
Task.type = 'uni';
if ('daysEn' in this.strings) {

temp_days[this.strings.days[this.strings.daysEn.indexOf(Task.dayOfWeekValue)]].push(Task);
} else {
temp_days[Task.dayOfWeekValue].push(Task);
}
});
}

if (information.DiktdTasks.length != 0) {
information.DiktdTasks.forEach(Task = function() {
Task.type = 'Diktd';
let day = new Date(Task.startDate).getDay();
temp_days[this.strings.days[day]].push(Task);
});
}

if (information.aiTasks) {
information.aiTasks.forEach(Task = function() {
Task.type = 'nonDiktd';
let day = new Date(Task.start.dateTime).getDay();
temp_days[this.strings.days[day]].push(Task);
});
}

this.setState({daysTemp: temp_days});
}

goBack = () = function() {
this.props.Nav.pop();
}

getTaskForWeekday = (day) = function() {


Арк.

КПІ.ІП-7219.045490.06.13 93
Змн. Арк. № докум. Підпис Дата
let information = this.state.daysTemp[day];
return information;
}

nextPage = () = function() {
if (this.state.information.aiTasks) {
this.state.information.aiTasks.forEach(Task = function() {
PutGeneratedTask(Task);
});
}
this.clearTasks();
this.props.Nav.navigate(DashboardNavigator);
}

clearTasks = () = function() {
this.props.dispatch(clearGeneratedKalends());
this.props.dispatch(clearGeneratedNonDiktdTasks());
this.props.dispatch(clearNonDiktdTasks());
this.props.dispatch(clearDiktdTasks());
this.props.dispatch(clearCourses());
}

render() {
const { showFAB, daysTemp } = this.state;
const objectArray = Object.keys(daysTemp);
return(
<View style={styles.cont}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'}
BgColor={'rgba(0, 0, 0, 0.5)'} />

<ScrollView>
<View style={styles.content}>
{
objectArray.map((day, key) = function() {
return (<ScheduleDay key={key}
colors={{
CoursesColor:
this.props.CoursesColor,
DiktdTasksColor:
this.props.DiktdTasksColor,
nonDiktdTasksColor:
this.props.nonDiktdTasksColor,
}}
day={day}

information={this.getTaskForWeekday(day)} />);
})
}
</View>
</ScrollView>

<FAB style={styles.fab}
theme={{colors:{accent:green}}}
icon="check"
visible={showFAB}
onPress={this.nextPage} />
</View>
);
Арк.

КПІ.ІП-7219.045490.06.13 94
Змн. Арк. № докум. Підпис Дата
}
}

let mapStateToProps = (state) = function() {


const { index } = state.ScheduleSelectionReducer;
const { GeneratedNonDiktdTasksReducer } = state;
let { DiktdTasksColor, nonDiktdTasksColor, CoursesColor } = state.KalendReducer;

for (let i = 0; i < kalendColors.length; i++) {


let key = Object.keys(kalendColors[i])[0];
let value = Object.values(kalendColors[i])[0];

switch(key) {
case DiktdTasksColor:
DiktdTasksColor = value;
break;

case nonDiktdTasksColor:
nonDiktdTasksColor = value;
break;

case CoursesColor:
CoursesColor = value;
break;
}
}

if (!DiktdTasksColor) {
DiktdTasksColor = state.KalendReducer.kalendColor;
}

if (!nonDiktdTasksColor) {
nonDiktdTasksColor = state.KalendReducer.kalendColor;
}

if (!CoursesColor) {
CoursesColor = state.KalendReducer.kalendColor;
}

return {
index,
GeneratedNonDiktdTasksReducer,
DiktdTasksColor,
nonDiktdTasksColor,
CoursesColor
};
};

export default connect(mapStateToProps, null)(ScheduleSelectionValue);

const moment = require('moment');


const viewHeightSize = 495.75;

class ValuermationOfUniextends React.PureComponent {

strings = getStrings().UniValuermation;
butStrings = getStrings().BottomButtons;

static NavOptions = ({ Nav }) = function() {


return {
Арк.

КПІ.ІП-7219.045490.06.13 95
Змн. Арк. № докум. Підпис Дата
title: Nav.state.params.title,
headerTintColor: dark_green,
headerStyle: {
BgColor: white,
}
};
}

constructor(props) {
super(props);

let contHeightSizeTemp = Dimensions.get('window').HeightSize - Header.HEIGHTSIZE;


let contHeightSize = viewHeightSize < contHeightSizeTemp ? contHeightSizeTemp : null;

this.state = {
contHeightSize,

startDate: moment().format('ddd., MMM DD, YYYY'),


endDate: moment().format('ddd., MMM DD, YYYY'),

uniValidated: true,

checked: 'none',
otherUni: ''
};

updNav('UniValuermation', props.Nav.state.routeName);
}

componentWillMount() {
if (this.props.UniValuermationReducer && this.props.UniValuermationReducer.Value &&
this.props.UniValuermationReducer.Value.Value ) {
this.setState({...this.props.UniValuermationReducer.Value.Value});
}
}

fieldValidation = () = function() {
let validated = true;

if (this.state.checked === 'none') {


this.setState({uniValidated: false});
validated = false;
} else {
this.setState({uniValidated: true});
}

return validated;
}

saveValuermation = () = function() {
if (this.fieldValidation()) {
this.props.dispatch(setUniValuermation(this.state));
let temp = this.props.Nav.state.params;

if (temp) {
if (temp.ScheduleOfUni || temp.reviewTask) {
if (this.state.checked === 'third') {
this.props.Nav.navigate(CoursesRoute, {addTitle:
getStrings().Courses.addTitle, editTitle: getStrings().Courses.editTitle});
} else {
Арк.

КПІ.ІП-7219.045490.06.13 96
Змн. Арк. № докум. Підпис Дата
this.props.Nav.navigate(ScheduleOfUniRoute, {title:
getStrings().ScheduleOfUni.title});
}
} else {
this.props.Nav.pop();
}
} else {
this.props.Nav.pop();
}
}
}

render() {
const { contHeightSize, startDate, endDate, checked, uniValidated } = this.state;

return (
<View style={styles.cont}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'}
BgColor={statusGreenColor} />

<ScrollView>
<View style={[styles.content, {HeightSize: contHeightSize}]}
onLayout={(Task) = function() {
let HeightSize = Task.nativeTask;
console.log(HeightSize);
}}>
<View style={styles.instruction}>
<Text style={styles.text}>{this.strings.definition}</Text>

</View>

<View>
<View style={styles.uni}>
<Text
style={styles.subHeader}>{this.strings.institution}</Text>

<View>
<View style={styles.radioButton}>
<RadioButton.Android
color={dark_green}

uncheckedColor={uniValidated ? green : red}


value="first"
status={checked ===
'first' ? 'checked' : 'unchecked'}
onPress={() =
function() {
this.setState({

checked: 'first',

uniValidated: true
});

this.refs._other.blur();
}} />

<TouchableOpacity
onPress={() = function() {
Арк.

КПІ.ІП-7219.045490.06.13 97
Змн. Арк. № докум. Підпис Дата
this.setState({
checked:
'first',
uniValidated:
true
});
this.refs._other.blur();
}}>
<Text
style={[styles.smallText, {color: uniValidated ? null : red}]}>

{this.strings.KPI}
</Text>
</TouchableOpacity>
</View>

<View style={styles.radioButton}>
<RadioButton.Android
color={dark_green}

uncheckedColor={uniValidated ? green : red}


value="second"
status={checked ===
'second' ? 'checked' : 'unchecked'}
onPress={() =
function() {
this.setState({

checked: 'second',

uniValidated: true
});

this.refs._other.blur();
}} />

<TouchableOpacity
onPress={() = function() {
this.setState({
checked:
'second',
uniValidated:
true
});
this.refs._other.blur();
}}>
<Text
style={[styles.smallText, {color: uniValidated ? null : red}]}>

{this.strings.Sheva}
</Text>
</TouchableOpacity>
</View>

<View style={styles.radioButton}>
<RadioButton.Android
color={dark_green}

uncheckedColor={uniValidated ? green : red}


value="third"
Арк.

КПІ.ІП-7219.045490.06.13 98
Змн. Арк. № докум. Підпис Дата
status={checked ===
'third' ? 'checked' : 'unchecked'}
onPress={() =
function() {
this.setState({

checked: 'third',

uniValidated: true
});

this.refs._other.focus();
}} />

<TextInput
placeholder={this.strings.other}
ref="_other"

style={styles.otherInput}
maxLength={1024}
onFocus={() =>
this.setState({
checked:
'third',
uniValidated:
true
})}

onChangeText={(otherUni) => this.setState({checked: 'third', otherUni})}

value={this.state.otherUni}/>
</View>

{
uniValidated ?
null
:
<Text
style={styles.error}>{this.strings.noInstitution}</Text>
}
</View>
</View>

<View style={styles.Durartion}>
<Text
style={styles.subHeader}>{this.strings.Durartion}</Text>

<View style={styles.date}>
<Text style={styles.greenTitle}>
{this.strings.start}
</Text>

<DateChooser showIcon={false}
date={startDate}
mode="date"
style={{_width:140}}
customStyles={{

dateInput:{border_width: 0},

Арк.

КПІ.ІП-7219.045490.06.13 99
Змн. Арк. № докум. Підпис Дата
dateText:{fontFamily:
'OpenSans-Regular'}
}}
format="ddd., MMM DD,
YYYY"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}
onDateChange={(startDate) =
function() {

this.setState({startDate,
endDate:
dateValidation(startDate, this.state.endDate, this.state.endDate)});
}} />
</View>

<View style={styles.date}>
<Text style={styles.greenTitle}>
{this.strings.end}
</Text>

<DateChooser showIcon={false}
date={endDate}
mode="date"
style={{_width:140}}
customStyles={{

dateInput:{border_width: 0},
dateText:{fontFamily:
'OpenSans-Regular'}
}}
format="ddd., MMM DD,
YYYY"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}
onDateChange={(endDate) =
function() {
this.setState({endDate,
startDate:
dateValidation(this.state.startDate, endDate, this.state.startDate)});
}} />
</View>
</View>
</View>

<View>
<BottomButtons twoButtons={false}
buttonText={[this.butStrings.Ready]}
buttonMethods={[this.saveValuermation]}
/>
</View>

</View>
</ScrollView>
</View>
);
Арк.

КПІ.ІП-7219.045490.06.13 100
Змн. Арк. № докум. Підпис Дата
}
}

let mapStateToProps = (state) = function() {


const { UniValuermationReducer } = state;

return {
UniValuermationReducer
};
};

export default connect(mapStateToProps, null)(UniValuermation);

class ScheduleOfUni extends React.PureComponent {

strings = getStrings().ScheduleOfUni;

static NavOptions = ({ Nav }) = function() {


return {
title: Nav.state.params.title
};
};

constructor(props) {
super(props);
this.state = {
contHeightSize: null,
};
updNav('ScheduleOfUni', props.Nav.state.routeName);
}

selectAPicture() {
if (Platform.OS !== 'ios') {
requestStoragePermission().then((accepted) = function() {
if (accepted) {
this.props.Nav.navigate(ScheduleOfUniSelectPictureRoute, {title:
getStrings().ScheduleOfUniSelectPicture.title});
}
});
} else {
this.props.Nav.navigate('ScheduleOfUniSelectPicture', {title:
getStrings().ScheduleOfUniSelectPicture.title});
}
}

cameraCapture() {
if (Platform.OS !== 'ios') {
requestCamera().then((accepted) = function() {
if (accepted) {
this.props.Nav.navigate(ScheduleOfUniTakePictureRoute, {title:
getStrings().ScheduleOfUniTakePicture.title});
}
});
} else {
this.props.Nav.navigate(ScheduleOfUniTakePictureRoute, {title:
getStrings().ScheduleOfUniTakePicture.title});
}
}

Арк.

КПІ.ІП-7219.045490.06.13 101
Змн. Арк. № докум. Підпис Дата
manualImport() {
this.props.Nav.navigate(CoursesRoute, {addTitle: getStrings().Courses.addTitle});
}

render() {
return (
<View style={styles.cont}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'}
BgColor={statusGreenColor} />

<ScrollView contentContStyle={styles.content}>
<View style={styles.instruction}>
<FontAwesome5 name="university"
size={130}
color={dark_green} />

<Text style={styles.text}>{this.strings.definition}</Text>
</View>

<View style={styles.button}>

<Text style={styles.manual}>
<Text
style={styles.textManual}>{this.strings.manual}</Text>

<Text style={styles.buttonManual}
onPress={() =>
this.manualImport()}>{this.strings.manually}</Text>

<Text style={styles.textManual}>.</Text>

</Text>
</View>
</ScrollView>
</View>
);
}
}

export default ScheduleOfUni;

import React from 'react';

class ScheduleOfUniCreation extends React.PureComponent {

strings = getStrings().ScheduleOfUniCreation;

constructor(props) {
super(props);
this.state = {
_width: 0,
alertDialog: false,
goToNextPage: false
};

updNav('ScheduleOfUniCreation', props.Nav.state.routeName);
}

navigateAction = NavOperations.navigate({
Арк.

КПІ.ІП-7219.045490.06.13 102
Змн. Арк. № докум. Підпис Дата
action: 'FinishUniCreation'
})

static NavOptions = ({ Nav }) => ({


gesturesEnabled: false,
headerLeft: <HeaderBackButton title='Back' tintColor={white} onPress={() = function() {
Nav.getParam('onBackPress')();
}} />,
});

componentWillMount() {
if (this.props.hasImage) {
let uri = this.props.imgURI;

if (Platform.OS === 'ios') {


const appleId = uri.substring(5, 41);
const ext = 'JPG';
}

RNFetchBlob.fs.readFile(uri, 'base64')
.then(information = function() {
if (information != undefined) {
this.success(information);
} else {
this.error(this.strings.fileNoInformation);
}
})
.catch(this.error);
}

BackHandler.addTaskListener('hardwareBackPress', this.handleBackButton);
this.props.Nav.setParams({onBackPress: this.handleBackButton});
}

success = (base64String) = function() {


base64String = base64String.toString();
let fakeEscape = base64String.replace(/[+]/g,'PLUS');
fakeEscape = fakeEscape.replace(/[=]/g,'EQUALS');
analyzePicture({information: fakeEscape})
.then(information = function() {
this.setState({goToNextPage: true, information});
this.nextPage();
})
.catch(err = function() {
if (err) {
Alert.alert(
this.strings.error,
err,
[
{text: 'Ok', onPress: () => this.props.Nav.pop()},
],
{discardable: false}
);
}
});
}

nextPage = () = function() {
if (this.state.goToNextPage && !this.state.alertDialog) {
let routes = this.props.Nav.dangerouslyGetParent().state.routes;
Арк.

КПІ.ІП-7219.045490.06.13 103
Змн. Арк. № докум. Підпис Дата
if (this.state.information != undefined) {
saveCoursessTasks(this.state.information)
.then((success) = function() {
if (!success) {
Alert.alert(
'Помилка',
'Помилка конвертації до Google Kalend',
[
{text: 'OK', onPress: () =>
this.props.Nav.pop()},
],
{discardable: false}
);
}
});
}

if (routes && routes[routes.length - 4].routeName == ReviewTaskRoute) {


this.props.Nav.navigate(ReviewTaskRoute, {title: getStrings().ReviewTask.title});
} else {
this.props.Nav.dispatch(this.navigateAction);
}
}
}

error = (err) = function() {


console.log('error', err);
}

handleBackButton = () = function() {
this.setState({alertDialog: true});
Alert.alert(
this.strings.backAlertTitle,
this.strings.backAlertDefinition,
[
{
text: this.strings.discard,
style: 'discard',
onPress: () = function() {
this.setState({alertDialog: false});
this.nextPage();
}
},
{
text: getStrings().Dashboard.name,
onPress: () = function() {
this.props.Nav.navigate(DashboardNavigator);
}
},
{
text: getStrings().ReviewTask.name,
onPress: () = function() {
this.props.Nav.navigate(ReviewTaskRoute, {title:
getStrings().ReviewTask.title});
},
},
],
{discardable: false},
);
Арк.

КПІ.ІП-7219.045490.06.13 104
Змн. Арк. № докум. Підпис Дата
return true;
}

componentWillUnmount() {
BackHandler.removeTaskListener('hardwareBackPress', this.handleBackButton);
}

render() {
return(
<View style={styles.cont}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'light-content' : 'default'}
BgColor={'rgba(0, 0, 0, 0.4)'} />

<Surface style={styles.surface}>
<Text style={styles.title}>{this.strings.dialogTitle}</Text>

<Text style={styles.subtitle}>{this.strings.dialogDefinition}</Text>

<Progress.Bar style={{alignSelf:'center'}}
indeterminate={true}
_width={200}
color={dark_green}
useNativeDriver={true}
unfilledColor={'#79A7D2'} />
</Surface>
</View>
);
}
}

let mapStateToProps = (state) = function() {


return {
imgURI: state.ImageReducer.information,
hasImage: state.ImageReducer.hasImage
};
};

export default connect(mapStateToProps, null)(ScheduleOfUniCreation);

const view const viewHeightSize = 723.432795329854;

class Setting extends React.PureComponent {

static NavOptions = {
header: null
}

strings = getStrings().Setting;

constructor(props) {
super(props);

let contHeightSizeTemp = Dimensions.get('window').HeightSize - Header.HEIGHTSIZE;


let contHeightSize = viewHeightSize < contHeightSizeTemp ? contHeightSizeTemp : null;

this.state = {
contHeightSize,
showTasksColorChooser: false,
Арк.

КПІ.ІП-7219.045490.06.13 105
Змн. Арк. № докум. Підпис Дата
snackbarVisible: false,
snackbarText: ''”,
snackbarTime: 3000,
langDialogVisible: false,
showImportKalend: false,
};

updNav('Setting', props.Nav.state.routeName);
}

dismissTasksColorChooser = () = function() {
this.setState({showTasksColorChooser: false});
this.resaveStatusBar();
}

logout = () = function() {
googleSignOut();
clearEveryReducer();
this.props.Nav.navigate(LoginNavig);
}

dismissImportKalend = () = function() {
this.setState({showImportKalend: false});
this.resaveStatusBar();
}

dismissLang = () = function() {
this.setState({langDialogVisible: false});
this.resaveStatusBar();
}

showTasksColorChooser = () = function() {
this.setState({showTasksColorChooser: true});
this.darkenStatusBar();
}

showImportKalend = () = function() {
this.setState({showImportKalend: true});
this.darkenStatusBar();
}

showLang = () = function() {
this.setState({langDialogVisible: true});
this.darkenStatusBar();
}

darkenStatusBar = () = function() {
if (Platform.OS === 'android') {
StatusBar.setBgColor(statusBarPopover, true);
}
}

resaveStatusBar = () = function() {
if (Platform.OS === 'android') {
StatusBar.setBgColor(statusBarDark, true);
}
}

showWebsite = (url) = function() {


if (Platform.OS === 'ios') {
Арк.

КПІ.ІП-7219.045490.06.13 106
Змн. Арк. № докум. Підпис Дата
this.openSafari(url);
} else {
this.openChrome(url);
}
}

openSafari = (url) = function() {


SafariView.isAvailable()
.then(SafariView.show({url,
tintColor: dark_green,
barTintColor: white,
fromBottom: true }))
.catch(() => this.openChrome(url));
}

openChrome = (url) = function() {


CustomTabs.openURL(url, {
toolbarColor: dark_green,
enableUrlBarHiding: true,
showPageTitle: true,
enableDefaultShare: true,
forceCloseOnRedirection: true,
});
}

render() {
const { contHeightSize, showTasksColorChooser, showImportKalend, snackbarText, snackbarTime,
snackbarVisible, langDialogVisible } = this.state;

return(
<View style={styles.cont}>
<StatusBar translucent={true}
animated
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'}
BgColor={statusBarDark} />

<TasksColorChooser visible={showTasksColorChooser}
dismiss={this.dismissTasksColorChooser} />

<ImportKalend visible={showImportKalend}
dismiss={this.dismissImportKalend} />

<LangSwitcher visible={langDialogVisible}
dismiss={this.dismissLang} />

<ScrollView>
<View style={[styles.content, {HeightSize: contHeightSize}]}>
<View style={styles.topProfileCont}>
<View style={styles.profileIconCont}>
<View style={{position: 'absolute', _width: 100,
HeightSize: 100, elevation: 9, borderRadius: 50, marginTop: 20}}/>
<Image style={styles.profileImage}
source={{uri:
this.props.profileImage}}/>
</View>

<Text style={styles.profileDefinition}
onPress={() = function() {
if (__DEV__)
this.props.Nav.navigate(CleanReducersRoute);
Арк.

КПІ.ІП-7219.045490.06.13 107
Змн. Арк. № докум. Підпис Дата
}} >
{this.props.personName}
</Text>
</View>

<View style={styles.titleRow}>

<Text
style={styles.title}>{this.strings.preferences}</Text>
</View>

<TouchableOpacity style={styles.button}
onPress={this.showImportKalend}>
<Text
style={styles.buttonText}>{this.strings.import}</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.button}
onPress={() = function() {
this.props.Nav.navigate(UnavailableRoute, {title:
getStrings().NonAvalHour.title});
}}>
<Text
style={styles.buttonText}>{this.strings.nonAvalHour}</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.button}
onPress={() = function() {
this.props.Nav.navigate(UniValuermationRoute,
{title: getStrings().UniValuermation.title});
}}>
<Text
style={styles.buttonText}>{this.strings.uniValuermation}</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.button}
onPress={this.showTasksColorChooser}>
<Text
style={styles.buttonText}>{this.strings.theme}</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.button}
onPress={() = function() {
Alert.alert(
this.strings.modifyKalend,
this.strings.modifyKalendDefinition,
[
{text: this.strings.discard, style:
'discard'},
{text: this.strings.deleteKalend,
onPress: () = function() {
Alert.alert(

this.strings.warning,

this.strings.warningDefinition,
[

{text: this.strings.discard, style: 'discard'},

Арк.

КПІ.ІП-7219.045490.06.13 108
Змн. Арк. № докум. Підпис Дата
{text: this.strings.ok, onPress: async () = function() {

await deleteKalend(this.props.kalendId);

this.logout();
}},
],
{discardable:
false}
);
}},
{text: this.strings.clearKalend,
onPress: async () = function() {
this.setState({

snackbarVisible: true,

snackbarTime: 4000,
snackbarText:
this.strings.clearing,
});
let deleteInformation =
await deleteKalend(this.props.kalendId);
let makeInformation =
await makeSecondaryKalend({summary: 'Kalend'});

if ('error' in
deleteInformation || 'error' in makeInformation) {
this.setState({

snackbarVisible: true,

snackbarTime: 3000,

snackbarText: this.strings.clearingError,
});
} else {

this.props.dispatch(setKalendID(makeInformation.id));
this.setState({

snackbarVisible: true,

snackbarTime: 3000,

snackbarText: this.strings.deleteKalendSuccess,
});
}
}},
],
{discardable: true}
);
}}>
<Text
style={styles.buttonText}>{this.strings.clearDeleteKalend}</Text>
</TouchableOpacity>

<TouchableOpacity style={styles.button}
onPress={this.logout}>
Арк.

КПІ.ІП-7219.045490.06.13 109
Змн. Арк. № докум. Підпис Дата
<Text
style={styles.buttonLogOutText}>{this.strings.logout}</Text>
</TouchableOpacity>

</View>
</ScrollView>

<Snackbar
visible={snackbarVisible}
onDismiss={() => this.setState({snackbarVisible: false})}
style={styles.snackbar}
Durartion={snackbarTime}>
{snackbarText}
</Snackbar>
</View>
);
}
}

let mapStateToProps = (state) = function() {


const { HomeReducer, KalendReducer, SettingReducer } = state;

let hasPersonValue = HomeReducer.profile != null;

return {
profileImage: hasPersonValue ? HomeReducer.profile.profile.person.photo :
`https://api.adorable.io/avatars/285/${new Date().getTime()}.png`,
personName: hasPersonValue ? HomeReducer.profile.profile.person.name : 'Unknown person',
kalendId: KalendReducer.id,
lang: SettingReducer.lang
};
};

export default connect(mapStateToProps, null)(Setting);

import React from 'react';


import { ScrollView, StatusBar, Text, View, Switch, TouchableOpacity, Platform } from 'react-native';
import DateChooser from 'react-native-dateChooser';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { connect } from 'react-redux';
import {setNonAvalHour} from '../../Operations';
import { DiktdUnavailableRoute } from '../../constants/PageNames';
import updNav from '../NavHelper';
import { nonAvalHourStyles as styles, white, statusGreenColor, dark_green, lightGreen } from '../../styles';
import { getStrings } from '../../services/helper';

const moment = require('moment');


require('moment-round');

class NonAvalHour extends React.PureComponent {

strings = getStrings().NonAvalHour;
butStrings = getStrings().BottomButtons;

static NavOptions = ({ Nav }) = function() {


return {
title: Nav.state.params.title,
headerStyle: {
BgColor: white
},
Арк.

КПІ.ІП-7219.045490.06.13 110
Змн. Арк. № докум. Підпис Дата
};
};

constructor(props) {
super(props);

this.state = {
sleepWeek: false,
startSleepWeek: moment().round(30, 'min').format('h:mm A'),
endSleepWeek: moment().round(30, 'min').format('h:mm A'),
sleepWeekEnd: false,
startSleepWeekEnd: moment().round(30, 'min').format('h:mm A'),
endSleepWeekEnd: moment().round(30, 'min').format('h:mm A'),

WorkWeek: false,
startWorkWeek: moment().round(30, 'min').format('h:mm A'),
endWorkWeek: moment().round(30, 'min').format('h:mm A'),
WorkWeekEnd: false,
startWorkWeekEnd: moment().round(30, 'min').format('h:mm A'),
endWorkWeekEnd: moment().round(30, 'min').format('h:mm A'),

LunchWeek: false,
startLunchWeek: moment().round(30, 'min').format('h:mm A'),
endLunchWeek: moment().round(30, 'min').format('h:mm A'),
LunchWeekEnd: false,
startLunchWeekEnd: moment().round(30, 'min').format('h:mm A'),
endLunchWeekEnd: moment().round(30, 'min').format('h:mm A'),

otherWeek: false,
startOtherWeek: moment().round(30, 'min').format('h:mm A'),
endOtherWeek: moment().round(30, 'min').format('h:mm A'),
otherWeekEnd: false,
startOtherWeekEnd: moment().round(30, 'min').format('h:mm A'),
endOtherWeekEnd: moment().round(30, 'min').format('h:mm A')
};

updNav('NonAvalHour', props.Nav.state.routeName);
}

componentDidMount() {
if (this.props.UnavailableReducer && this.props.UnavailableReducer.Value &&
this.props.UnavailableReducer.Value.Value) {
this.setState({...this.props.UnavailableReducer.Value.Value});
}
}

manualImport() {
this.props.Nav.navigate(DiktdUnavailableRoute, {addTitle: 'Add Unavailable Hour'});
}
next = () = function() {
this.props.dispatch(setNonAvalHour(this.state));

this.props.Nav.pop();
}

render() {
return(
<View style={styles.cont}>
<StatusBar translucent={true}
barStyle={Platform.OS === 'ios' ? 'dark-content' : 'default'}
Арк.

КПІ.ІП-7219.045490.06.13 111
Змн. Арк. № докум. Підпис Дата
BgColor={statusGreenColor} />

<ScrollView>
<View style={[styles.content]}>
<View style={styles.instruction}>
<Text style={styles.text}>{this.strings.definition}</Text>
</View>

<View style={styles.HourView}>
<View>
<View style={styles.row}>

<Text
style={styles.greenTitle}>{this.strings.Night}</Text>
</View>
<View>
<View style={styles.rowContent}>
<View
style={styles.colContent}>
<View
style={styles.row}>
<Text
style={styles.type}>{this.strings.week}</Text>

<Switch
trackColor={{false: 'lightgreen', true: lightGreen}}

thumbColor={(this.state.sleepWeek && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(sleepWeek) => this.setState({sleepWeek})}

value={this.state.sleepWeek} />
</View>
{this.state.sleepWeek
?
<View
style={styles.rowTime}>

<DateChooser showIcon={false}

date={this.state.startSleepWeek}

mode="time"

style={styles.time_width}

onDateChange={(startSleepWeek) =>

this.setState({startSleepWeek})

} />

<DateChooser showIcon={false}

date={this.state.endSleepWeek}

mode="time"

style={styles.time_width}
Арк.

КПІ.ІП-7219.045490.06.13 112
Змн. Арк. № докум. Підпис Дата
customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(endSleepWeek) => this.setState({endSleepWeek})} />


</View> :
<View style={[styles.rowTime]}><Text> </Text></View>}
</View>

<View
style={styles.colContent}>
<View
style={styles.row}>
<Text
style={styles.type}>{this.strings.weekEnd}</Text>

<Switch
trackColor={{false: 'lightgreen', true: lightGreen}}

thumbColor={(this.state.sleepWeekEnd && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(sleepWeekEnd) => this.setState({sleepWeekEnd})}

value={this.state.sleepWeekEnd} />
</View>

{this.state.sleepWeekEnd ?
<View
style={styles.rowTime}>

<DateChooser showIcon={false}

date={this.state.startSleepWeekEnd}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

Арк.

КПІ.ІП-7219.045490.06.13 113
Змн. Арк. № докум. Підпис Дата
}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(startSleepWeekEnd) =>

this.setState({startSleepWeekEnd})

} />

<Text> - </Text>

<DateChooser showIcon={false}

date={this.state.endSleepWeekEnd}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(endSleepWeekEnd) => this.setState({endSleepWeekEnd})} />


</View> :
<View style={[styles.rowTime]}><Text> </Text></View>}
</View>
</View>
</View>
</View>

<View>
<View style={styles.row}>
Арк.

КПІ.ІП-7219.045490.06.13 114
Змн. Арк. № докум. Підпис Дата
<Text
style={styles.greenTitle}>{this.strings.Work}</Text>
</View>

<View>
<View style={styles.rowContent}>
<View
style={styles.colContent}>
<View
style={styles.row}>
<Text
style={styles.type}>{this.strings.week}</Text>

<Switch
trackColor={{false: 'lightgreen', true: lightGreen}}

thumbColor={(this.state.WorkWeek && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(WorkWeek) => this.setState({WorkWeek})}

value={this.state.WorkWeek} />
</View>

{this.state.WorkWeek
?
<View
style={styles.rowTime}>

<DateChooser showIcon={false}

date={this.state.startWorkWeek}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(startWorkWeek) =>

this.setState({startWorkWeek})

} />
Арк.

КПІ.ІП-7219.045490.06.13 115
Змн. Арк. № докум. Підпис Дата
<Text> - </Text>

<DateChooser showIcon={false}

date={this.state.endWorkWeek}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(endWorkWeek) => this.setState({endWorkWeek})} />


</View> :
<View style={[styles.rowTime]}><Text> </Text></View>}
</View>
<View
style={styles.colContent}>
<View
style={styles.row}>
<Text
style={styles.type}>{this.strings.weekEnd}</Text>

<Switch
trackColor={{false: 'lightgreen', true: lightGreen}}

thumbColor={(this.state.WorkWeekEnd && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(WorkWeekEnd) => this.setState({WorkWeekEnd})}

value={this.state.WorkWeekEnd} />
</View>

{this.state.WorkWeekEnd ?
<View
style={styles.rowTime}>

<DateChooser showIcon={false}

Арк.

КПІ.ІП-7219.045490.06.13 116
Змн. Арк. № докум. Підпис Дата
date={this.state.startWorkWeekEnd}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={'UA'}

isAllDayTime={false}

onDateChange={(startWorkWeekEnd) =>

this.setState({startWorkWeekEnd})

} />

<Text> - </Text>

<DateChooser showIcon={false}

date={this.state.endWorkWeekEnd}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

Арк.

КПІ.ІП-7219.045490.06.13 117
Змн. Арк. № докум. Підпис Дата
isAllDayTime={false}

onDateChange={(endWorkWeekEnd) => this.setState({endWorkWeekEnd})} />


</View> :
<View style={[styles.rowTime]}><Text> </Text></View>}
</View>
</View>
</View>
</View>

<View>
<View style={styles.row}>

<Text
style={styles.greenTitle}>{this.strings.Lunch}</Text>
</View>

<View>
<View style={styles.rowContent}>
<View
style={styles.colContent}>
<View
style={styles.row}>
<Text
style={styles.type}>{this.strings.week}</Text>

<Switch
trackColor={{false: 'lightgreen', true: lightGreen}}

thumbColor={(this.state.LunchWeek && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(LunchWeek) => this.setState({LunchWeek})}

value={this.state.LunchWeek} />
</View>

{this.state.LunchWeek
?
<View
style={styles.rowTime}>

<DateChooser showIcon={false}

date={this.state.startLunchWeek}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

Арк.

КПІ.ІП-7219.045490.06.13 118
Змн. Арк. № докум. Підпис Дата
agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(startLunchWeek) =>

this.setState({startLunchWeek})

} />

<Text> - </Text>

<DateChooser showIcon={false}

date={this.state.endLunchWeek}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(endLunchWeek) => this.setState({endLunchWeek})} />


</View> :
<View style={[styles.rowTime]}><Text> </Text></View>}
</View>
<View
style={styles.colContent}>
<View
style={styles.row}>
<Text
style={styles.type}>{this.strings.weekEnd}</Text>

<Switch trackColor={{false: 'lightgreen', true: lightGreen}}

Арк.

КПІ.ІП-7219.045490.06.13 119
Змн. Арк. № докум. Підпис Дата
thumbColor={(this.state.LunchWeekEnd && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(LunchWeekEnd) => this.setState({LunchWeekEnd})}

value={this.state.LunchWeekEnd} />
</View>

{this.state.LunchWeekEnd ?
<View
style={styles.rowTime}>

<DateChooser showIcon={false}

date={this.state.startLunchWeekEnd}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(startLunchWeekEnd) =>

this.setState({startLunchWeekEnd})

} />

<Text> - </Text>

<DateChooser showIcon={false}

date={this.state.endLunchWeekEnd}

mode="time"

style={styles.time_width}

customStyles={{

Арк.

КПІ.ІП-7219.045490.06.13 120
Змн. Арк. № докум. Підпис Дата
dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(endLunchWeekEnd) => this.setState({endLunchWeekEnd})} />


</View> :
<View style={[styles.rowTime]}><Text> </Text></View>}
</View>
</View>
</View>
</View>

<View>
<View style={styles.row}>

<Text
style={styles.greenTitle}>{this.strings.other}</Text>
</View>

<View>
<View style={styles.rowContent}>
<View
style={styles.colContent}>
<View
style={styles.row}>
<Text
style={styles.type}>{this.strings.week}</Text>

<Switch
trackColor={{false: 'lightgreen', true: lightGreen}}

thumbColor={(this.state.otherWeek && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(otherWeek) => this.setState({otherWeek})}

value={this.state.otherWeek} />
</View>

{this.state.otherWeek
?
<View
style={styles.rowTime}>

<DateChooser showIcon={false}

date={this.state.startOtherWeek}

Арк.

КПІ.ІП-7219.045490.06.13 121
Змн. Арк. № докум. Підпис Дата
mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(startOtherWeek) =>

this.setState({startOtherWeek})

} />

<Text> - </Text>

<DateChooser showIcon={false}

date={this.state.endOtherWeek}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

Арк.

КПІ.ІП-7219.045490.06.13 122
Змн. Арк. № докум. Підпис Дата
onDateChange={(endOtherWeek) => this.setState({endOtherWeek})} />
</View> :
<View style={[styles.rowTime]}><Text> </Text></View>}
</View>
<View
style={styles.colContent}>
<View
style={styles.row}>
<Text
style={styles.type}>{this.strings.weekEnd}</Text>

<Switch
trackColor={{false: 'lightgreen', true: lightGreen}}

thumbColor={(this.state.otherWeekEnd && Platform.OS !== 'ios') ? dark_green : null}

onValueChange={(otherWeekEnd) => this.setState({otherWeekEnd})}

value={this.state.otherWeekEnd} />
</View>

{this.state.otherWeekEnd ?
<View
style={styles.rowTime}>

<DateChooser showIcon={false}

date={this.state.startOtherWeekEnd}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(startOtherWeekEnd) =>

this.setState({startOtherWeekEnd})

} />

Арк.

КПІ.ІП-7219.045490.06.13 123
Змн. Арк. № докум. Підпис Дата
<Text> - </Text>

<DateChooser showIcon={false}

date={this.state.endOtherWeekEnd}

mode="time"

style={styles.time_width}

customStyles={{

dateInput:{border_width: 0},

dateText:{fontFamily: 'OpenSans-Regular'}

}}

format="h:mm A"

agreeBtnText={this.strings.agreeButton}

discardBtnText={this.strings.discardButton}

locale={‘UA’}

isAllDayTime={false}

onDateChange={(endOtherWeekEnd) => this.setState({endOtherWeekEnd})} />


</View> :
<View style={[styles.rowTime]}><Text> </Text></View>}
</View>
</View>
</View>
</View>

</View>

<View style={[styles.buttons, {marginBottom: 20}]}>


<TouchableOpacity style={[styles.button,
{_width:'100%'}]} onPress={this.next}>
<Text
style={styles.buttonText}>{this.butStrings.Ready}</Text>
</TouchableOpacity>
</View>
</View>
</ScrollView>
</View>
);
}
}

let mapStateToProps = (state) = function() {


const { UnavailableReducer } = state;

return {
UnavailableReducer
Арк.

КПІ.ІП-7219.045490.06.13 124
Змн. Арк. № докум. Підпис Дата
};
};

export default connect(mapStateToProps, null)(NonAvalHour);

class WelcomePage extends React.PureComponent {

constructor(props) {
super(props);

updNav('WelcomePage', props.Nav.state.routeName);
}

renderSlide = props => (


<LinearGradient
style={[styles.mainContent, {
paddingTop: props.topSpacer,
paddingBottom: props.bottomSpacer }]}
colors={props.colors}
start={{x: 0, y: .1}}
end={{x: .1, y: 1}}>

{
props.icon === '' ?
null :
<Ionicons style={styles.icon}
name={props.icon}
size={slidesIconSize}
color={props.color} />
}

<View>
<Text style={styles.title}>{props.title}</Text>

<Text style={styles.text}>{props.text}</Text>
</View>
</LinearGradient>
);

Арк.

КПІ.ІП-7219.045490.06.13 125
Змн. Арк. № докум. Підпис Дата
Факультет інформатики та обчислювальної техніки
Кафедра автоматизованих систем обробки інформації і управління

“ЗАТВЕРДЖЕНО”
В.о. завідувача кафедри
____________ Олександр ПАВЛОВ
“___” ___________________ 2021 р.

Мобільне застосування для контролю особистого часу


Програма та методика тестування
КПІ.ІП-7219.045490.04.51

“ПОГОДЖЕНО”
Керівник проєкту:
_______________ І.І. Вітковська

Нормоконтроль: Виконавець:
________________ І.І. Вітковська ________________Є.О. Нестеренко

Київ – 2021 року


1 ОБ’ЄКТ ВИПРОБУВАНЬ ..........................................................................................2

2 МЕТА ТЕСТУВАННЯ ................................................................................................3

3 МЕТОДИ ТЕСТУВАННЯ ..........................................................................................4

4 ЗАСОБИ ТА ПОРЯДОК ТЕСТУВАННЯ ................................................................5

Арк.

КПІ.ІП-7219.045490.04.51 2
Змн. Арк. № докум. Підпис Дата
1 ОБ’ЄКТ ВИПРОБУВАНЬ

Об’єктом випробувань є мобільне застосування, що придназначений для


керування особистим часом та задачами та розроблений на базі технологій
Node.js та фреймворку React.js.

Арк.

КПІ.ІП-7219.045490.04.51 2
Змн. Арк. № докум. Підпис Дата
2 МЕТА ТЕСТУВАННЯ

При тестуванні даного програмного забезпечення мають бути перевірені


наступні вимоги:
− працездатність елементів та сторінок мобільного застосунку, а також її
функціональність відповідно вимогам;
− інтеграція мобільного застосунку з сторонніми сервісами;
− забезпечення належного рівня безпеки даних;
− зручність роботи з мобільним додатком;
− відповідність вимогам, що були поставлені в технічному завданні.

Арк.

КПІ.ІП-7219.045490.04.51 3
Змн. Арк. № докум. Підпис Дата
3 МЕТОДИ ТЕСТУВАННЯ

Тестування мобільного застосування виконується з використанням


методу Gray Box Testing. Цей метод представляє перевірку коду, а також
відповідності застосунку функціональним вимогам, що були поставлені на
початкових етапах розробки.
Для тестування було використано такі методи, як:
− тестування користувацького інтерфейсу;
− тестування навантаження;
− тестування функціоналу мобільного додатку, тобто системне
тестування;
− тестування безпеки.

Арк.

КПІ.ІП-7219.045490.04.51 4
Змн. Арк. № докум. Підпис Дата
4 ЗАСОБИ ТА ПОРЯДОК ТЕСТУВАННЯ

Коректна працездатність мобільного застосування перевіряється


наступним чином:
− ручне тестування – введення граничних та недопустимих значень у поля,
які можна редагувати;
− ручне тестування на відповідність функціональним вимогам;
− статичного тестування коду;
− тестування застосунку при різних версіях операційної системи;
− тестування стабільності роботи при різних умовах;
− тестування зручності використання;
− тестування інтерфейсу.

Арк.

КПІ.ІП-7219.045490.04.51 5
Змн. Арк. № докум. Підпис Дата
Факультет інформатики та обчислювальної техніки
Кафедра автоматизованих систем обробки інформації і управління

“ЗАТВЕРДЖЕНО”
В.о. завідувача кафедри
____________ Олександр ПАВЛОВ
“___” ___________________ 2021 р.

Мобільне застосування для контролю особистого часу


Керівництво користувача
КПІ.ІП-7219.045490.05.34

“ПОГОДЖЕНО”
Керівник проєкту:
_______________ І.І. Вітковська

Нормоконтроль: Виконавець:
________________ І.І. Вітковська ________________Є.О. Нестеренко

Київ – 2021 року


Для того, щоби розпочати роботу з мобільним застосунком, необхідно
увійти до системи. На початковому екрані мобільного застосунку (рис. 1.1) є
кнопка для авторизації. Натиснувши цю кнопку стає доступною авторизація в
додатку за допомогою акаунту Google.

Рисунок 1.1 – Початковий екран мобільного застосування

Після успішної авторизації користувача в мобільному застосунку,


користувач потрапляє на головну сторінку (рис. 1.2). Користувач бачить задачі,
які має виконати сьогодні та календар задач на найближчий поточний тиждень.
Для додавання нової задачі в календар, користувач має натиснути кнопку
«Створити».

Арк.

КПІ.ІП-7219.045490.05.34 2
Змн. Арк. № докум. Підпис Дата
Рисунок 1.2 – Головна сторінка мобільного застосування

На сторінці створення задачі (рис. 1.3) користувачеві доступні декілька


варіантів: додати задачу з розкладу, додати задачу з фіксованим часом, тобто
таку, для якої важливий конкретний час виконання, додати задачу з
нефіксованим часом виконання, таку, яка може бути виконана в будь-який час,
запропонований алгоритмом програми, а також встановити зайняті часи в
правому верхньому куті.

Арк.

КПІ.ІП-7219.045490.05.34 3
Змн. Арк. № докум. Підпис Дата
Рисунок 1.3 – Сторінка створення задач

При виборі опції «Розклад предметів» користувач потрапляє на сторінку


додавання інформації про навчальний семестр (рис. 1.4), яку заповнює
одноразово – на сторінці користувач має вказати, протягом якого періоду
відбуватиметься навчання по дисциплінам, що будуть додані далі.

Арк.

КПІ.ІП-7219.045490.05.34 4
Змн. Арк. № докум. Підпис Дата
Рисунок 1.4 – Додавання інформації про навчальний семестр

На сторінці додавання навчальної дисципліни (рис. 1.5) користувач має


додати дисципліну, яка поставиться в розклад протягом всього періоду
семестру, що був встановлений до цього. Після заповнення інформації про
дисципліну, користувач має натиснути кнопку «Додати» та «Готово».

Арк.

КПІ.ІП-7219.045490.05.34 5
Змн. Арк. № докум. Підпис Дата
Рисунок 1.5 – Додавання навчальної дисципліни

При виборі опції на сторінці додавання задачі, якщо користувач обирає


варіант «Додати задачу з фіксованим часом», то він потрапляє на сторінку де
він може додати фіксовану задачу (рис. 1.6), для якої важливий конкретний час
виконання. Користувач має заповнити відповідні поля з назвою задачі, її
конкретним часом виконання та опційно додати інформацію про локацію до
опис. Після заповнення інформації про задачу з фіксованим, користувач має
натиснути кнопку «Додати» та «Готово».

Арк.

КПІ.ІП-7219.045490.05.34 6
Змн. Арк. № докум. Підпис Дата
Рисунок 1.6 – Додавання задачі з фіксованим часом

При виборі опції на сторінці додавання задачі, якщо користувач обирає


варіант «Додати задачу з нефіксованим часом», то він потрапляє на сторінку де
він може додати нефіксовану задачу (рис. 1.7), для якої важливий конкретний
час виконання. Користувач має заповнити відповідні поля з назвою задачі, її
тривалістю, повторюваністю та пріоритетом, а також опційно додати
інформацію про локацію до опис. Після заповнення інформації про задачу з
нефіксованим, користувач має натиснути кнопку «Додати» та «Готово».

Арк.

КПІ.ІП-7219.045490.05.34 7
Змн. Арк. № докум. Підпис Дата
Рисунок 1.7 – Додавання задачі з нефіксованим часом

Також зі сторінки додавання задач користувач може встановити зайняті


часи, тобто такі, в які він не може виконувати задачі, що можуть бути
запропоновані мобільним додатком при виборі нефіксованої задачі. Для цього
користувач має натиснути на кнопку в верхньому правому куті, яка переведе
його на сторінку встановлення зайнятих годин (рис. 1.8). На цій сторінки
користувач може встановити години сну, приймання їжі, робочий час та інші
години, щоби вони враховувалися при додаванні інших задач.

Арк.

КПІ.ІП-7219.045490.05.34 8
Змн. Арк. № докум. Підпис Дата
Рисунок 1.8 – Сторінка встановлення зайнятих годин

Після додавання всіх задач, що має виконати користувач найближчим


часом, він має натиснути кнопку підтвердження на сторінці додавання задач.
Після роботи алгоритму програмам запропонує користувачеві варіанти
розкладу з обраними задачами (рис. 1.9). Ці декілька варіантів розкладів
сформовано за інформацією про інші події та зайняті години, що було
встановлено до цього. Користувач обирає будь-який, що підходить йому
найбільше та мобільний додаток розставляє їх в календарі користувача.

Арк.

КПІ.ІП-7219.045490.05.34 9
Змн. Арк. № докум. Підпис Дата
Рисунок 1.9 – Сторінка обрання оптимального розкладу

На сторінці «Налаштування» (рис. 1.10) користувач може імпортувати


календар, що є в нього в акаунті Google, натиснувши відповідну кнопку та
обравши необхідний календар. Також на цій сторінці він може обрати колір для
задачі, який буде відображатися на сторінці створення задачі, видалити чи
очистити календар, а також вийти з акаунту застосування.

Арк.

КПІ.ІП-7219.045490.05.34 10
Змн. Арк. № докум. Підпис Дата
Рисунок 1.10 – Сторінка налаштувань

Арк.

КПІ.ІП-7219.045490.05.34 11
Змн. Арк. № докум. Підпис Дата
-7219.045490.07.99. .

. -7219.045490.07.99.

. .
. .
..
. .
.
. . ..
. . . -72
-7219.045490.07.99. .

. -7219.045490.07.99.

. .
. .
..
. .
.
. . ..
. . . -72
-7219.045490.07.99. .

. -7219.045490.07.99.

. .
. .
..
. .
.
. . ..
. . . -72

You might also like