Compare commits

...

233 Commits

Author SHA1 Message Date
d123c773a0 Upload documentation artifact 2023-07-24 14:20:22 +00:00
e225fb0c43 Merge branch '136-final-documentation-adjustments' into 'master'
Resolve "Final documentation adjustments"

Closes #136

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!121
2020-06-29 22:43:49 +00:00
3b2959aff2 Fix some spelling mistakes 2020-06-30 00:31:12 +02:00
c35ebe2735 Add release apk 2020-06-29 14:41:48 +02:00
wiecktobi
8156081e04 Finish corrections 2020-06-29 12:48:07 +02:00
wiecktobi
d7492ad5a1 One change 2020-06-29 01:58:45 +02:00
wiecktobi
564e4feb86 First corrections 2020-06-28 17:31:31 +02:00
bd9a23a0b0 Merge branch '136-final-documentation-adjustments' into 'master'
Resolve "Final documentation adjustments"

Closes #136

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!120
2020-06-20 15:51:15 +00:00
50f8e36800 Update README, Add Clockify Report, Move artifacts to better location 2020-06-20 17:01:59 +02:00
6a00a258a2 Add Projectjournal 2020-06-20 16:54:58 +02:00
0d6df49493 Fix spelling for documentation, backend and frontend 2020-06-20 16:38:25 +02:00
4188c15b4b Final formatting changes
Reduce toc depth from two to one
Remove abstract
2020-06-20 16:08:33 +02:00
ee874ac51e Merge branch '116-fazit-und-ausblick-chapter' into 'master'
Resolve "Fazit und Ausblick Chapter"

Closes #116

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!113
2020-06-20 12:17:37 +00:00
9c145c0d9c Merge branch '112-frontend-chapter' into 'master'
Resolve "Frontend Chapter"

Closes #112

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!109
2020-06-20 12:17:35 +00:00
Tim Zieger
76530bc09f Change home picture and some text 2020-06-20 14:04:12 +02:00
4974ae0e10 Change size and indentation of listings, minor fixes 2020-06-20 14:04:12 +02:00
1d9740c9bc Add TODO content, fix section depth of Probleme und Lösungen 2020-06-20 14:04:12 +02:00
Tim Zieger
2152fa61f7 Add Problems 2020-06-20 14:04:11 +02:00
6feed2ec2a Add DummyDaten and Diagramme 2020-06-20 14:04:11 +02:00
Tim Zieger
605dcb7f51 Edit Frontend Documentation umsetzung 2020-06-20 14:04:11 +02:00
e0cab7f5b3 Add Technologiebeschreibung 2020-06-20 14:04:10 +02:00
Tim Zieger
6defc2c353 Add listing js Edit frontend documentation 2020-06-20 14:04:10 +02:00
6f42a97b9e Add Fazit und Ausblick 2020-06-20 14:00:59 +02:00
b79a997bf8 Merge branch '121-prepare-presentation' into 'master'
Resolve "Prepare presentation"

Closes #121

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!119
2020-06-20 11:58:38 +00:00
56bfd038e4 Add Presentation 2020-06-20 13:56:36 +02:00
9382624d3f Init LFS for pptx 2020-06-20 13:56:23 +02:00
8de701c19f Merge branch '135-statisticss-some-entries-missing' into 'master'
Resolve "Statisticss some entries missing"

Closes #135

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!117
2020-06-14 17:22:29 +00:00
1f7d044ccc Fix missing requests 2020-06-14 19:14:59 +02:00
fa82e80f13 Merge branch '134-fix-request-loop-on-statistics-page' into 'master'
Resolve "Fix request loop on statistics page"

Closes #134

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!116
2020-06-14 15:08:01 +00:00
f525316400 Fix endless loop bug 2020-06-14 17:00:02 +02:00
ad833f3831 Merge branch '132-frontend-improvements' into 'master'
Resolve "Frontend improvements"

Closes #132

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!114
2020-06-14 14:28:53 +00:00
d46411c4e4 Merge branch '133-fix-todays-entires-also-show-future-entries' into 'master'
Resolve "Fix todays entires also show future entries"

Closes #133

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!115
2020-06-14 14:26:04 +00:00
fffde13440 Add a bit of everything 2020-06-14 16:20:34 +02:00
7b4887ecac Update documentation to show right lines for record repository 2020-06-14 16:14:27 +02:00
ff1afd7c32 Don't show future entries on /today endpoint 2020-06-14 16:11:13 +02:00
Tobias Wieck
f9ce4d4db8 Merge branch '131-fix-recyclerview-entries' into 'master'
Resolve "Fix RecyclerView entries"

Closes #131

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!112
2020-06-11 18:54:58 +00:00
wiecktobi
d9ee892c76 Fix bug 2020-06-11 20:25:36 +02:00
7efd7ec891 Merge branch '130-to-large-request-in-pie-chart' into 'master'
Resolve "To large request in Pie chart"

Closes #130

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!111
2020-06-11 16:04:40 +00:00
99e5005831 Fix request size 2020-06-11 17:56:44 +02:00
e38a3c4ce0 Merge branch '111-backend-chapter' into 'master'
Resolve "Backend Chapter"

Closes #111

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!108
2020-06-11 15:41:05 +00:00
9255bce492 Correct spelling android chapter 2020-06-11 17:24:26 +02:00
d3a0f3ad64 Correct spelling einleitung chapter 2020-06-11 16:45:15 +02:00
89d64102bd Correct spelling backend chapter 2020-06-11 16:41:17 +02:00
9e4584cf12 Merge branch '129-month-sumary-time-record-count-too-small' into 'master'
Resolve "Month Sumary time record count too small"

Closes #129

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!110
2020-06-11 14:35:57 +00:00
8c7eb8431c Fix request size 2020-06-11 16:26:54 +02:00
dd110e04fb Correct spelling projektplanung chapter 2020-06-11 16:17:22 +02:00
814bd585c2 Add list of listings 2020-06-11 14:35:33 +02:00
cdb87ea4c9 Change ich in android chapter to wir 2020-06-11 14:35:33 +02:00
207f300e66 Fix duplicate label at code listing 2020-06-11 14:35:33 +02:00
95d5025c0a Add samples to the projections subsection 2020-06-11 14:35:32 +02:00
f7c6914f4d Write probleme und loesungen 2020-06-11 14:35:32 +02:00
41b53998de Write endpoints section 2020-06-11 14:35:31 +02:00
88bc4517fe Write Repositories and Projections 2020-06-11 14:35:31 +02:00
38db18a492 Add JSON formatting for listings 2020-06-11 14:35:30 +02:00
cc41db8f16 Add Geotime logo in titlepage 2020-06-11 14:35:30 +02:00
92da1e539e Autoformat backend project 2020-06-11 14:35:29 +02:00
d0cae8af87 Write Entities and JWT 2020-06-11 14:35:29 +02:00
1fce986a2d Write Backend Technologiebeschreibung 2020-06-11 14:35:28 +02:00
Tobias Wieck
f7ad0c43ea Merge branch '108-einleitung-chapter' into 'master'
Resolve "Einleitung Chapter"

Closes #108

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!103
2020-06-11 12:28:02 +00:00
Tobias Wieck
b2b884731a Merge branch '113-android-chapter' into 'master'
Resolve "Android Chapter"

Closes #113

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!104
2020-06-11 12:04:56 +00:00
wiecktobi
722f9cd465 Finish Android chapter 2020-06-11 13:48:12 +02:00
Tim Zieger
8f0f6c2d8f Merge branch '109-projektplanung-chapter' into 'master'
Resolve "Projektplanung Chapter"

Closes #109

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!102
2020-06-11 10:08:43 +00:00
Tim Zieger
40f7f218a2 Edit projektplanung chapter 2020-06-11 11:56:30 +02:00
wiecktobi
de3b086a45 Chapter 6.4 and small changes in the code 2020-06-10 23:07:10 +02:00
wiecktobi
089df551a7 Add Kotlin source code formatting 2020-06-10 16:01:05 +02:00
wiecktobi
dade003d84 Chapter 6.3.4 2020-06-10 15:42:37 +02:00
wiecktobi
5b2d6a7cfe Finish chapter 6.3.3 2020-06-10 15:42:37 +02:00
wiecktobi
1cc688bed0 Chapter 6.1 to 6.3.3 2020-06-10 15:21:59 +02:00
53d5321541 Merge branch '128-fix-timezone-for-database' into 'master'
Resolve "Adjust timezone for database"

Closes #128

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!107
2020-06-10 13:14:11 +00:00
6f996d2784 Add timezone env variable to database Dockerfile 2020-06-10 15:13:34 +02:00
25c9c23b0b Merge branch '114-application-stack-chapter' into 'master'
Resolve "Application Stack Chapter"

Closes #114

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!106
2020-06-09 18:01:53 +00:00
1f5d8deb66 Fix spelling mistakes 2020-06-09 19:51:16 +02:00
5d14d379db Chapter ApplicationStack 2020-06-09 19:44:06 +02:00
1d24f210f1 Merge branch '110-entwicklungsumgebung-chapter' into 'master'
Resolve "Entwicklungsumgebung Chapter"

Closes #110

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!105
2020-06-09 13:58:42 +00:00
6cdd8d9ce1 Correct spelling mistakes and sentence structure 2020-06-09 15:48:14 +02:00
62bac89e98 Chapter Entwicklungsumgebung 2020-06-09 15:14:24 +02:00
2a66c34356 Import listings package
Add listings language Docker and Docker-Compose
2020-06-09 15:13:32 +02:00
wiecktobi
acf9736f92 First version of introduction 2020-06-08 12:00:39 +02:00
8a5904d513 Merge branch '118-donut-diagram-for-time-balance-over-all-accounts' into 'master'
Resolve "Donut diagram for time balance over all accounts"

Closes #118

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!101
2020-06-06 16:46:50 +00:00
edd03c5a7e Add donut diagram for time by type paid for accounts with revenue calc display in tooltip 2020-06-06 18:39:30 +02:00
Tim Zieger
5e13d103d0 Merge branch '124-fill-about-page-with-content' into 'master'
Resolve "Fill about page with content"

Closes #124

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!100
2020-06-05 09:34:03 +00:00
Tim Zieger
d235c8ddc0 Fill about page with contend 2020-06-05 11:25:39 +02:00
Tobias Wieck
23aa33209a Merge branch '101-get-todays-entries' into 'master'
Resolve "Get todays entries"

Closes #101

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!98
2020-06-04 19:10:53 +00:00
wiecktobi
8e73cc6e2c Fix problems and finish app 2020-06-04 18:45:10 +02:00
wiecktobi
c304632d6e Query /today endpoint an display response 2020-06-03 23:49:00 +02:00
Tim Zieger
aab586a4c8 Merge branch '120-frontend-code-cleanup' into 'master'
Resolve "Frontend code cleanup"

Closes #120

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!99
2020-06-03 14:44:54 +00:00
Tim Zieger
6a42650c40 Cleanup frontend code 2020-06-03 16:32:36 +02:00
wiecktobi
08ffe71433 Add recycler view with hardcoded values 2020-06-03 01:22:09 +02:00
wiecktobi
2ed09692f8 Small changes for user with no data 2020-06-02 23:09:15 +02:00
wiecktobi
20ab2931f2 Change button text 2020-06-02 22:47:23 +02:00
wiecktobi
c11d57de2e Update version and fix issue to show only accounts belonging to the user 2020-06-02 22:04:38 +02:00
Tim Zieger
b0759a64b7 Merge branch '126-remove-create-buttons-if-logged-out' into 'master'
Resolve "Remove create buttons if logged out"

Closes #126

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!97
2020-06-02 10:26:23 +00:00
Tim Zieger
a80acb0797 Remove crate Butttons if logged out 2020-06-02 12:16:30 +02:00
Tim Zieger
b379a750dc Merge branch '127-query-all-users' into 'master'
Resolve "Query all users"

Closes #127

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!96
2020-06-02 09:43:43 +00:00
Tim Zieger
8c75cf414f Implement pages for admin view 2020-06-02 11:34:30 +02:00
Tim Zieger
5597afb4b0 Merge branch '123-query-all-pages-when-more-than-20-entries-are-present' into 'master'
Resolve "Query all pages when more than 20 entries are present"

Closes #123

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!95
2020-06-02 09:15:42 +00:00
Tim Zieger
e30aa5798a Remove sort funktion and Add pagin 2020-06-02 11:06:07 +02:00
Tim Zieger
3e772d989e Merge branch '119-change-fontsize-for-edit-views' into 'master'
Resolve "Change fontsize for edit views"

Closes #119

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!94
2020-06-01 10:11:26 +00:00
Tim Zieger
7954bf7c04 Change h tags to p with pt 2020-06-01 11:58:22 +02:00
Tim Zieger
e2c698fd08 Merge branch '122-fix-date-and-time-pickers' into 'master'
Resolve "Fix date and time pickers"

Closes #122

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!93
2020-06-01 09:25:12 +00:00
Tim Zieger
8e54997b87 Fix pickers 2020-06-01 11:15:29 +02:00
Tim Zieger
f2397c6c92 Merge branch '107-sort-timerecords-by-date' into 'master'
Resolve "Sort timerecords by date"

Closes #107

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!92
2020-06-01 08:53:21 +00:00
Tim Zieger
04c9b7b55f Implemented sort for records 2020-06-01 10:44:45 +02:00
daf542c527 Merge branch '117-change-date-calculation-of-the-today-endpoint' into 'master'
Resolve "Change date calculation of the /today endpoint"

Closes #117

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!91
2020-05-31 21:36:03 +00:00
0a0bc34e38 Fix today endpoint to only show entries for today
Add more sample data
2020-05-31 23:29:13 +02:00
207b22afa1 Merge branch '95-fill-diagrams-with-data' into 'master'
Resolve "Fill diagrams with data"

Closes #95

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!82
2020-05-31 19:17:36 +00:00
Tim Zieger
09d206c279 Merge branch '58-fill-home-with-content' into 'master'
Resolve "Fill Home with content"

Closes #58

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!84
2020-05-31 19:16:55 +00:00
Tim Zieger
b07e5cd357 Implemented fill home with contend 2020-05-31 20:55:25 +02:00
918cf67b1f Implement diagrams with backend data 2020-05-31 20:48:51 +02:00
9a8faa3f0d Fix bug in documentation build script 2020-05-30 22:25:26 +02:00
8f2b697f05 Merge branch 'documentation-pipeline' into 'master'
Add documentation build step to CI

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!89
2020-05-30 19:59:25 +00:00
179af69564 Add documentation build step to CI 2020-05-30 21:57:05 +02:00
ca4da8c993 Merge branch 'improve-mr-build-times' into 'master'
Change builds to only build the specific module

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!87
2020-05-30 19:29:17 +00:00
5060da28ca Change builds to only build the specific module 2020-05-30 21:23:24 +02:00
61fccccd33 Merge branch '93-table-of-contents' into 'master'
Resolve "Table of contents"

Closes #93

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!86
2020-05-30 19:15:12 +00:00
0c1550c7a7 Extract large sections into separate files 2020-05-30 20:50:22 +02:00
b602db1bd9 Split project into main sections 2020-05-30 20:39:33 +02:00
cf00b314d3 Remove old structure 2020-05-30 19:49:35 +02:00
bd1795fb7e Add samples for the documentation 2020-05-30 19:44:45 +02:00
f3952c1ea8 Merge branch '106-fix-record-endpoint-allbetweenanduser' into 'master'
Resolve "Fix Record Endpoint allBetweenAndUser"

Closes #106

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!85
2020-05-30 17:05:16 +00:00
17a759ce1e bugfix for allBetweenAndUser Endpoint of records 2020-05-30 18:46:39 +02:00
Tim Zieger
ea4493f955 Merge branch '105-dropdown-edit-create-timetrack' into 'master'
Resolve "Dropdown edit create timetrack"

Closes #105

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!83
2020-05-30 09:11:06 +00:00
Tim Zieger
ea53de257e dropdown and projection for account 2020-05-30 10:54:00 +02:00
Tim Zieger
f3b9a0879b Merge branch '96-date-and-time-picker-for-timetracks' into 'master'
Resolve "date and time picker for timetracks"

Closes #96

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!81
2020-05-30 07:54:30 +00:00
Tim Zieger
3c4cbaf126 pickers 2020-05-29 17:34:23 +02:00
Tim Zieger
f509ad0d9a Merge branch '103-frontend-crashes-if-something-is-null' into 'master'
Resolve "Frontend crashes if something is null"

Closes #103

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!80
2020-05-29 08:23:06 +00:00
Tim Zieger
04abba8c1c bug fixed chrash if null 2020-05-29 10:05:18 +02:00
Tobias Wieck
d1910ea10d Merge branch '100-change-timetrack-account' into 'master'
Resolve "Change timetrack account"

Closes #100

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!76
2020-05-28 19:16:22 +00:00
wiecktobi
8521832315 Query track endpoint and show start/enddate 2020-05-28 20:49:58 +02:00
613c5cf0ac Merge branch '104-update-backend-timezone' into 'master'
Resolve "Update backend timezone"

Closes #104

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!79
2020-05-28 18:13:08 +00:00
161f5c7054 Update timezone of jre docker image 2020-05-28 19:57:05 +02:00
77c6b85eec Merge branch '102-track-endpoint-crashes-if-something-is-null' into 'master'
Resolve "Track endpoint crashes if something is null"

Closes #102

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!78
2020-05-28 17:19:10 +00:00
75d2cb6670 Fix crash when all record fields are null 2020-05-28 19:03:10 +02:00
Tim Zieger
19d01b1502 Merge branch '97-account-link-and-admin' into 'master'
Resolve "Account link and admin"

Closes #97

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!77
2020-05-28 15:41:34 +00:00
Tim Zieger
5ace5c4317 account link and admin + remove role 2020-05-28 17:18:05 +02:00
wiecktobi
c52daee815 Read, save and display account name, description and revenue 2020-05-28 16:19:55 +02:00
wiecktobi
dad0594854 Set geofenc according to users location values 2020-05-28 11:33:11 +02:00
Tobias Wieck
2a1eb3feef Merge branch '98-fix-geofence' into 'master'
Resolve "Fix geofence"

Closes #98

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!75
2020-05-27 22:22:54 +00:00
wiecktobi
1fbed97fb2 Update input fields and buttons plus fix security gap at logout 2020-05-27 22:55:21 +02:00
wiecktobi
73187542cd Bug fix 2020-05-27 20:19:05 +02:00
Tim Zieger
08b996308f Merge branch '94-action-listener-for-butttons' into 'master'
Resolve "Action listener for Butttons"

Closes #94

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!74
2020-05-27 13:53:33 +00:00
Tim Zieger
95aaac09c8 event listener 2020-05-27 15:30:18 +02:00
Tim Zieger
8e2a8208f3 Merge branch '92-time-reccord-data-from-backend' into 'master'
Resolve "Time Reccord data from Backend"

Closes #92

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!73
2020-05-27 12:19:30 +00:00
Tim Zieger
f41a2af656 time record backend and replace selfuri 2020-05-27 13:01:18 +02:00
Tobias Wieck
5cd2defd3d Merge branch '51-logout-implementation' into 'master'
Resolve "Logout implementation"

Closes #51

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!72
2020-05-26 09:19:17 +00:00
wiecktobi
6bd11577e0 Logout functionality and small changes 2020-05-26 10:51:27 +02:00
Tim Zieger
57e8541c0e Merge branch '90-delete-user' into 'master'
Resolve "Delete User"

Closes #90

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!71
2020-05-26 08:33:33 +00:00
Tim Zieger
d5ef0004f6 delete user 2020-05-26 10:17:42 +02:00
Tobias Wieck
5908de5650 Merge branch '66-implement-functional-top-toolbar' into 'master'
Resolve "Implement functional Top Toolbar"

Closes #66

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!34
2020-05-25 22:16:37 +00:00
wiecktobi
6334b62e2b Override old JWT on login 2020-05-25 23:52:31 +02:00
wiecktobi
c124818fa6 Remove JWT data class
* Code formatting
* Static code analysis
2020-05-25 23:29:36 +02:00
wiecktobi
6907710f51 Create private file with JWT 2020-05-25 23:20:23 +02:00
wiecktobi
881d01535d Change app theme
* Toolbar with logout on home screen
* Add a Toolbar to login and register view
* Embed Settings view
2020-05-25 22:55:56 +02:00
50446e0654 Merge branch '91-backend-uses-cascaded-delete-for-entities' into 'master'
Resolve "Backend uses cascaded delete for entities"

Closes #91

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!70
2020-05-25 19:25:42 +00:00
4e560b9d36 Add cascading delete for connected entities 2020-05-25 21:10:25 +02:00
Tim Zieger
0d4eb694c9 Merge branch '89-remove-localhost' into 'master'
Resolve "Remove localhost"

Closes #89

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!69
2020-05-25 17:39:15 +00:00
Tim Zieger
8992dd028b no more localhost 2020-05-25 19:22:09 +02:00
b1320fb11e Merge branch '78-update-timetrack-accounts' into 'master'
Resolve "Update Timetrack Accounts"

Closes #86 and #78

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!65
2020-05-25 16:58:48 +00:00
214d5b9218 add timetrack account managing views and implement functionality 2020-05-25 18:38:34 +02:00
Tobias Wieck
0047556180 Merge branch '80-query-backend-endpoint-whoami' into 'master'
Resolve "Query backend endpoint whoami"

Closes #80

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!67
2020-05-25 12:46:03 +00:00
Tobias Wieck
52e862afbe Resolve "Query backend endpoint whoami" 2020-05-25 12:46:02 +00:00
b20f2e8a61 Merge branch '88-list-all-timetrack-accounts-for-a-given-user' into 'master'
Resolve "List all Timetrack accounts for a given user"

Closes #88

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!68
2020-05-25 12:26:33 +00:00
781ec85851 Implement to query accounts for a given user 2020-05-25 14:11:34 +02:00
Tim Zieger
83bd086792 Merge branch '85-implement-default-actions' into 'master'
Resolve "Implement default actions"

Closes #85

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!66
2020-05-25 10:30:45 +00:00
Tim Zieger
2a68777f67 implement default actions 2020-05-25 12:10:02 +02:00
Tim Zieger
edd7317aa4 Merge branch '79-update-users-properties-from-frontend' into 'master'
Resolve "Update users properties from frontend"

Closes #79

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!63
2020-05-24 10:02:51 +00:00
Tim Zieger
3a04ff8823 edit user 2020-05-24 10:46:03 +02:00
Tobias Wieck
70e3a60930 Merge branch '74-disable-start-button' into 'master'
Resolve "Disable start button"

Closes #74

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!64
2020-05-23 16:39:27 +00:00
Tobias Wieck
841a9545e6 Resolve "Disable start button" 2020-05-23 16:39:27 +00:00
Tim Zieger
4b2fe41d9f Merge branch '83-user-list-add-style' into 'master'
Resolve "User list add style"

Closes #83

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!62
2020-05-22 12:15:27 +00:00
Tim Zieger
736e571471 users same style as timetrack 2020-05-22 13:57:47 +02:00
Tim Zieger
7488b5bb03 Merge branch '77-list-of-users' into 'master'
Resolve "List of users"

Closes #77

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!60
2020-05-21 08:36:55 +00:00
Tim Zieger
030b8c47d3 list of Users 2020-05-21 10:21:32 +02:00
ed284da1ac Merge branch '81-custom-endpoints' into 'master'
Resolve "Custom endpoints"

Closes #81

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!59
2020-05-20 22:02:04 +00:00
5e161d9c9d Remove accounts references from User 2020-05-20 23:45:36 +02:00
5eb6efcffe Implement /track endpoint 2020-05-20 23:26:35 +02:00
9f8fd0af1e Implement custom record searches
Create record overview projection
Add today search with scoped principal
Add sample data for records
2020-05-20 23:26:35 +02:00
3310062138 Merge branch '76-rework-the-design-for-a-single-time-record' into 'master'
Resolve "Rework the design for a single time record"

Closes #76

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!61
2020-05-20 16:03:23 +00:00
a6c4cc48d9 add new TimeRecordItem and TimeRecord list design 2020-05-20 17:47:17 +02:00
Tim Zieger
3d1f2fd741 Merge branch '75-menu-depending-on-loginstate' into 'master'
Resolve "Menu depending on loginstate"

Closes #75

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!56
2020-05-19 13:40:56 +00:00
Tim Zieger
a1072f40b3 menu depending on loginstate 2020-05-19 15:16:09 +02:00
04d4d9563b Merge branch '50-create-record-rest-controller' into 'master'
Resolve "Create record rest controller"

Closes #50

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!58
2020-05-19 09:33:50 +00:00
24b6ff4618 Make UserController a BasePathAwareController 2020-05-19 10:50:33 +02:00
0f700d87b0 Adjust records endpoint name and rels, enable paging 2020-05-19 10:35:14 +02:00
Tobias Wieck
0aa257dbaf Merge branch '82-update-geofence-for-api-29' into 'master'
Resolve "Update geofence for API 29"

Closes #82

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!57
2020-05-18 21:13:15 +00:00
wiecktobi
65fc373f97 Added permission request 2020-05-18 21:52:52 +02:00
Tobias Wieck
10a1d3a3fa Merge branch '73-implement-geofence' into 'master'
Resolve "Implement geofence"

Closes #73

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!55
2020-05-18 15:29:41 +00:00
wiecktobi
5d366e3c8b Implemented Geofence and updated package structure 2020-05-18 17:06:01 +02:00
Tobias Wieck
4c34ac3497 Merge branch '72-bug-new-retrofit-verion' into 'master'
Resolve "Bug: New retrofit verion"

Closes #72

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!54
2020-05-18 09:18:25 +00:00
Tim Zieger
5d98afb5c4 Merge branch '62-register-communication' into 'master'
Resolve "Register Communication"

Closes #62

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!53
2020-05-18 09:17:49 +00:00
wiecktobi
b673c0767f Fixed 2020-05-18 11:00:20 +02:00
Tim Zieger
5085cd45ce register communication 2020-05-18 10:55:02 +02:00
af3d5686d7 Merge branch 'fix-backend-uri' into 'master'
Important Fix: Backend uri

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!52
2020-05-17 19:36:21 +00:00
8a79d863f6 Important Fix: Backend uri 2020-05-17 21:21:40 +02:00
Tim Zieger
fc813f14db Merge branch '61-login-communication' into 'master'
Resolve "Login Communication"

Closes #61

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!50
2020-05-17 19:05:55 +00:00
Tim Zieger
efa7587702 login communication 2020-05-17 20:33:45 +02:00
6e4200cea6 Merge branch '68-statistic-overview-view' into 'master'
Resolve "Statistic Overview view"

Closes #68

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!51
2020-05-17 17:57:50 +00:00
4c81923977 add Statistics Page - move WeekSummary into Statistics 2020-05-17 17:32:50 +02:00
Tobias Wieck
3dd71a11eb Merge branch '67-read-geo-information' into 'master'
Resolve "Read geo-information"

Closes #67

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!42
2020-05-17 14:27:17 +00:00
Tobias Wieck
3c42d55881 Merge branch '64-login-functionality-with-retrofit' into 'master'
Resolve "Login functionality with Retrofit"

Closes #64

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!41
2020-05-17 14:13:36 +00:00
wiecktobi
9326b88973 Working login function with token storage 2020-05-17 15:58:42 +02:00
8b2e26042c Merge branch 'backend-bugfix-persistent-data' into 'master'
Bugfix backend data-initialization

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!49
2020-05-15 14:40:13 +00:00
f8f9f12dcc Bugfix data-initialization
Bugfix login
Bugfix sign-up
Docker-compose restart backend always
2020-05-15 16:02:19 +02:00
Tim Zieger
8d3d820286 Merge branch '69-vuetify-login' into 'master'
Resolve "Vuetify login"

Closes #69

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!40
2020-05-15 10:54:31 +00:00
Tim Zieger
b0b33bfbb3 login vuetify 2020-05-15 12:40:59 +02:00
0e2f880755 Merge branch '19-find-way-to-store-location-data' into 'master'
Resolve "Find way to store location data"

Closes #19

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!48
2020-05-13 14:07:39 +00:00
71ee4b9bd9 Add projection userAllEmbedded to the whoami endpoint 2020-05-13 15:50:54 +02:00
89ba108078 Add location implementation 2020-05-13 15:50:08 +02:00
d47733cc6f Merge branch '49-create-accounts-rest-controller' into 'master'
Resolve "Create accounts rest controller"

Closes #49

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!47
2020-05-13 13:01:57 +00:00
0d95e5f8f6 Update whoami endpoint to return a userWithRole projection 2020-05-13 14:44:45 +02:00
bd89747020 Comply to spring package structure 2020-05-13 14:27:15 +02:00
055abd95a4 Add withUser projection to account 2020-05-13 14:18:58 +02:00
3a48734544 Switch from crud to paging and sorting, adjust paths and rels 2020-05-13 14:02:24 +02:00
96671d5350 Merge branch '52-lazy-and-eager-fetching' into 'master'
Resolve "Lazy and eager fetching"

Closes #52

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!46
2020-05-13 11:56:56 +00:00
4c05c668cb Define lazy and eager fetching explicit 2020-05-13 13:42:43 +02:00
8dc4604f61 Merge branch 'android-mr-build' into 'master'
Add android build to merge request

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!45
2020-05-12 09:43:06 +00:00
75e7d7f11e Fix compile break 2020-05-11 23:39:55 +02:00
ae796fe3ab Add android build to merge request 2020-05-11 23:33:49 +02:00
ad0d279b82 Merge branch '71-login-doesn-t-work-anymore-since-user-projection' into 'master'
Resolve "Login doesn't work anymore since user projection"

Closes #71

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!44
2020-05-11 19:22:51 +00:00
d4f39f27ae Add LoginUser to successfully parse the request and create a token 2020-05-11 21:15:25 +02:00
wiecktobi
b4d6b5dd0c Changed to read header 2020-05-11 20:59:32 +02:00
7cd4d52138 Merge branch '70-fix-duplicate-key-in-timetrackaccounts-tabe' into 'master'
Resolve "Fix duplicate key in timetrackAccounts table"

Closes #70

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!43
2020-05-11 18:33:19 +00:00
3ca73361f0 Introduce proper many to one relation 2020-05-11 20:21:05 +02:00
wiecktobi
859fd1d5ec Get the actual location and update it 2020-05-11 18:00:33 +02:00
wiecktobi
e6834a18e6 Created Login with Retrofit and store Token (with exceptions) 2020-05-11 14:24:01 +02:00
d66ecc43b7 Merge branch '59-navigation-to-header' into 'master'
Resolve "Navigation to Header"

Closes #60 and #59

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!36
2020-05-10 17:18:51 +00:00
e83bfd6e5a add new navigation design (vuetify framework) - modify colors, font - delete Header.vue, Footer.vue 2020-05-10 19:08:38 +02:00
48fd0cfdc0 Merge branch '47-create-users-rest-controller' into 'master'
Resolve "Create users rest controller"

Closes #47

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!39
2020-05-09 19:22:12 +00:00
22df05010b Expose user resource
Add projection withRole
Remove TimetrackUser reference from TimetrackAccount
2020-05-09 21:05:06 +02:00
798ca8b168 Merge branch '48-create-login-rest-controller' into 'master'
Resolve "Create login rest controller"

Closes #48

See merge request marcel.schwarz/2020ss-qbc-geofence-timetracking!30
2020-05-09 17:26:51 +00:00
f186a89bb8 Change sign-up url, Add whoami endpoint 2020-05-09 19:20:35 +02:00
6abdab2cf6 Formatting and little optimization 2020-05-09 19:19:14 +02:00
8e7f9c06d3 Implement Sign-up, Add id generation type identity to Entities 2020-05-09 19:19:13 +02:00
170 changed files with 16132 additions and 1879 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
*.pptx filter=lfs diff=lfs merge=lfs -text
*.apk filter=lfs diff=lfs merge=lfs -text

View File

@ -8,6 +8,10 @@ build-vue:
- docker build --pull -t vue .
rules:
- if: $CI_MERGE_REQUEST_ID
changes:
- frontend/**/*
when: always
- when: never
build-backend:
image: docker:latest
@ -19,11 +23,41 @@ build-backend:
- docker build --pull -t backend .
rules:
- if: $CI_MERGE_REQUEST_ID
changes:
- backend/**/*
when: always
- when: never
build-android:
image: debian
image: docker:latest
stage: build
services:
- docker:dind
script:
- echo "To be done"
- cd android
- docker build --pull -t android .
rules:
- if: $CI_MERGE_REQUEST_ID
changes:
- android/**/*
when: always
- when: never
build-documentation:
image: icaotix/latex:full-incremental
stage: build
script:
- export CI_JOB_TIMESTAMP=$(date --utc -I)
- cd documentation
- latexmk -pdf documentation
- mv "documentation.pdf" "documentation-${CI_JOB_TIMESTAMP}.pdf"
artifacts:
paths:
- "**/documentation*.pdf"
rules:
- if: $CI_MERGE_REQUEST_ID
changes:
- documentation/**/*
when: always
- when: never

View File

@ -1 +1,2 @@
# UBC - Timetracking with Geofences
# UBC SS2020 - Geo Timetracking - Team TacocaT
![Geo Timetracking](other-artifacts/Product-Flyer.png)

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
</component>
</project>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="JDK" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

29
android/Dockerfile Normal file
View File

@ -0,0 +1,29 @@
FROM gradle:jdk8
USER root
ENV SDK_URL="https://dl.google.com/android/repository/commandlinetools-linux-6200805_latest.zip" \
ANDROID_HOME="/usr/local/android-sdk" \
ANDROID_VERSION=29 \
ANDROID_BUILD_TOOLS_VERSION=29.0.3
# Download Android SDK
RUN mkdir "$ANDROID_HOME" .android && cd "$ANDROID_HOME" \
&& curl -o sdk.zip $SDK_URL && unzip sdk.zip && rm sdk.zip \
&& mkdir "$ANDROID_HOME/licenses" || true \
&& echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" > "$ANDROID_HOME/licenses/android-sdk-license"
# && yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses
# Install Android Build Tool and Libraries
RUN $ANDROID_HOME/tools/bin/sdkmanager --sdk_root=$ANDROID_HOME "tools" > /dev/null
RUN $ANDROID_HOME/tools/bin/sdkmanager --update > /dev/null
RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" "platforms;android-${ANDROID_VERSION}" "platform-tools" > /dev/null
# Install Build Essentials
RUN apt-get update \
&& apt-get install build-essential -y \
&& apt-get install file -y \
&& apt-get install apt-utils -y
COPY . .
RUN gradle assembleDebug

View File

@ -26,6 +26,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
@ -33,10 +37,12 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.core:core-ktx:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.2.0-alpha06'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation "com.google.android.gms:play-services-location:17.0.0"
implementation 'androidx.preference:preference:1.1.1'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
@ -45,4 +51,7 @@ dependencies {
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
implementation "android.arch.navigation:navigation-fragment-ktx:2.2.2"
implementation "android.arch.navigation:navigation-ui-ktx:2.2.2"
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
implementation 'com.squareup.retrofit2:converter-gson:2.8.1'
}

View File

@ -1,30 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.hft.geotracker">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- Required if your app targets Android 10 (API level 29) or higher -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/AppTheme">
<activity android:name=".Register"></activity>
<activity android:name=".activities.Register" />
<activity
android:name=".Settings"
android:name=".activities.Settings"
android:label="@string/title_activity_settings" />
<activity android:name=".Login">
<activity android:name=".activities.Login">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity">
<activity android:name=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<receiver android:name=".GeofenceBroadcastReceiver"/>
</application>
</manifest>

View File

@ -0,0 +1,58 @@
package de.hft.geotracker
import android.content.BroadcastReceiver
import android.content.ContentValues.TAG
import android.content.Context
import android.content.Intent
import android.util.Log
import com.google.android.gms.location.Geofence
import com.google.android.gms.location.GeofenceStatusCodes
import com.google.android.gms.location.GeofencingEvent
class GeofenceBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceStatusCodes.getStatusCodeString(geofencingEvent.errorCode)
println("Event error")
Log.e(TAG, errorMessage)
return
}
// Test that the reported transition was of interest.
when (val geofenceTransition = geofencingEvent.geofenceTransition) {
Geofence.GEOFENCE_TRANSITION_ENTER -> {
context!!.getSharedPreferences("LOCATION", Context.MODE_PRIVATE)
?.edit()
?.putBoolean("ENABLED", true)
?.apply()
// Get the geofences that were triggered. A single event can trigger multiple geofences.
val triggeringGeofences = geofencingEvent.triggeringGeofences
// Get the transition details as a String.
val geofenceTransitionDetails = "Transition: $geofenceTransition" +
"\nTriggering Geofences: $triggeringGeofences"
println("Success Transition: ")
Log.i(TAG, geofenceTransitionDetails)
}
Geofence.GEOFENCE_TRANSITION_EXIT -> {
context!!.getSharedPreferences("LOCATION", Context.MODE_PRIVATE)
?.edit()
?.putBoolean("ENABLED", false)
?.apply()
val triggeringGeofences = geofencingEvent.triggeringGeofences
val geofenceTransitionDetails =
"Transition: $geofenceTransition\nTriggering Geofences: $triggeringGeofences"
println("Success Transition: ")
Log.i(TAG, geofenceTransitionDetails)
}
else -> {
println("Error Transition: ")
Log.e(TAG, geofenceTransition.toString())
}
}
}
}

View File

@ -1,37 +0,0 @@
package de.hft.geotracker
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
/**
* A simple [Fragment] subclass.
*/
class Login : AppCompatActivity() {
lateinit var login : TextView
lateinit var reg : TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
login = findViewById(R.id.button_create_account)
login.setOnClickListener {
login()
}
reg = findViewById(R.id.button_register)
reg.setOnClickListener {
register()
}
}
private fun register() {
val intent = Intent(this, Register::class.java)
startActivity(intent)
}
private fun login() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}

View File

@ -1,79 +0,0 @@
package de.hft.geotracker
import android.annotation.SuppressLint
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.Window
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.Spinner
import androidx.navigation.findNavController
import androidx.databinding.DataBindingUtil
import androidx.navigation.ui.NavigationUI
import com.google.android.material.textfield.TextInputLayout
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setSupportActionBar(findViewById(R.id.my_toolbar))
setContentView(R.layout.activity_home)
// val binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// val navController = this.findNavController(R.id.HostFragment)
// NavigationUI.setupActionBarWithNavController(this, navController)
// val dropdown : TextInputLayout = findViewById(R.id.filled_exposed_dropdown)
/*val editTextFilledExposedDropdown : AutoCompleteTextView = findViewById(R.id.filled_exposed_dropdown)
ArrayAdapter.createFromResource(this, R.array.accounts, R.layout.spinner_layout).also {
arrayAdapter -> arrayAdapter.setDropDownViewResource(R.layout.spinner_layout)
editTextFilledExposedDropdown.setAdapter(arrayAdapter)
}*/
/*val array = arrayOf("Test1", "Test2")
val a : ArrayAdapter<String> = ArrayAdapter(this, R.layout.spinner_layout, array)
val editTextFilledExposedDropdown : AutoCompleteTextView = findViewById(R.id.filled_exposed_dropdown)
editTextFilledExposedDropdown.setAdapter(a)*/
val spinner: Spinner = findViewById(R.id.account_spinner)
// Create an ArrayAdapter using the string array and a default spinner layout
ArrayAdapter.createFromResource(this, R.array.accounts, android.R.layout.simple_spinner_item).also { adapter ->
// Specify the layout to use when the list of choices appears
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// Apply the adapter to the spinner
spinner.adapter = adapter
}
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.settings -> {
// User chose the "Settings" item, show the app settings UI...
var intent = Intent(this, Settings::class.java)
startActivity(intent)
println("test")
true
}
R.id.logout -> {
// User chose the "Settings" item, show the app settings UI...
var intent = Intent(this, Login::class.java)
startActivity(intent)
true
}
else -> {
// If we got here, the user's action was not recognized.
// Invoke the superclass to handle it.
println("test")
super.onOptionsItemSelected(item)
}
}
override fun onBackPressed() {
}
}

View File

@ -0,0 +1,44 @@
package de.hft.geotracker
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
import de.hft.geotracker.activities.RecordEntry
class RecordsAdapter : RecyclerView.Adapter<TextItemViewHolder>() {
var data = listOf<RecordEntry>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as CardView
return TextItemViewHolder(view)
}
override fun getItemCount(): Int {
return data.size
}
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
val item = data[position]
holder.textFrom.setText("Start: " + item.from)
holder.textTo.setText("End: " + item.to)
if (item.duration != -1) {
holder.textTotal.setText("Duration: " + item.duration)
}
}
}
class TextItemViewHolder(textView: CardView): RecyclerView.ViewHolder(textView) {
val textFrom = itemView.findViewById<TextView>(R.id.recyclerText_from)
val textTo = itemView.findViewById<TextView>(R.id.recyclerText_to)
val textTotal = itemView.findViewById<TextView>(R.id.recyclerText_total)
}

View File

@ -1,24 +0,0 @@
package de.hft.geotracker
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
class Settings : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
}
}
}

View File

@ -0,0 +1,108 @@
package de.hft.geotracker.activities
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import de.hft.geotracker.R
import de.hft.geotracker.retrofit.GeofenceService
import de.hft.geotracker.retrofit.ValuesUserLogin
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import kotlinx.android.synthetic.main.activity_login.*
/**
* A simple [Fragment] subclass.
*/
class Login : AppCompatActivity() {
lateinit var login: TextView
lateinit var reg: TextView
lateinit var service: GeofenceService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
1000
)
} else {
// Background location runtime permission already granted.
// You can now call geofencingClient.addGeofences().
}
val retrofit = Retrofit.Builder()
.baseUrl("http://plesk.icaotix.de:5000")
.addConverterFactory(GsonConverterFactory.create())
.build()
service = retrofit.create(GeofenceService::class.java)
login = findViewById(R.id.button_login)
login.setOnClickListener {
intent = Intent(this, MainActivity::class.java)
login()
}
reg = findViewById(R.id.button_register)
reg.setOnClickListener {
register()
}
}
private fun register() {
val intent = Intent(this, Register::class.java)
startActivity(intent)
}
private fun login() {
val name = input_username.text.toString()
val pswd = input_password.text.toString()
val call = service.login(ValuesUserLogin(name, pswd))
call.enqueue(object : Callback<Void> {
override fun onResponse(call: Call<Void>?, response: Response<Void>?) {
if (response != null && response.isSuccessful) {
val headers = response.headers()
val authentication = headers.get("Authorization")
deleteFile("JWToken")
openFileOutput("JWToken", Context.MODE_PRIVATE).use {
it.write(authentication!!.toByteArray())
}
println(response.code())
startActivity(intent)
} else {
if (response != null) {
println(response.code())
Toast.makeText(this@Login, "Wrong Username or Password!", Toast.LENGTH_LONG)
.show()
} else {
println("Response is null")
}
}
}
override fun onFailure(call: Call<Void>?, t: Throwable?) {
println("Error: ${t.toString()}")
}
})
}
}

View File

@ -0,0 +1,405 @@
package de.hft.geotracker.activities
import android.Manifest
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.app.AlertDialog
import android.app.PendingIntent
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Looper
import android.view.View
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityCompat.requestPermissions
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.gms.location.*
import de.hft.geotracker.GeofenceBroadcastReceiver
import de.hft.geotracker.R
import de.hft.geotracker.RecordsAdapter
import de.hft.geotracker.retrofit.*
import kotlinx.android.synthetic.main.activity_home.*
import okhttp3.OkHttpClient
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.BufferedReader
import java.io.InputStreamReader
class MainActivity : AppCompatActivity() {
lateinit var geofencingClient: GeofencingClient
lateinit var geofence: Geofence
lateinit var actionButton: TextView
var running = false
var accName: String? = null
var workingSince: String? = null
lateinit var accounts: ValuesTimetrackAccounts
lateinit var service: GeofenceService
lateinit var locationRequest: LocationRequest
lateinit var fusedLocationClient: FusedLocationProviderClient
lateinit var locationCallback: LocationCallback
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
//Get location data and permissions
createLocationRequest()
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
if (ActivityCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) !=
PackageManager.PERMISSION_GRANTED
) {
requestPermissions(this, arrayOf(ACCESS_FINE_LOCATION), 1000)
}
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult ?: return
}
}
//React on geofence state
this.getSharedPreferences("LOCATION", Context.MODE_PRIVATE)
?.edit()
?.putBoolean("ENABLED", false)
?.apply()
getSharedPreferences("LOCATION", Context.MODE_PRIVATE)
.registerOnSharedPreferenceChangeListener { sharedPreferences, key ->
val isInside = sharedPreferences.getBoolean("ENABLED", false)
println("Is inside? -> $isInside")
if (isInside) {
button_start_stop?.text = getString(R.string.start)
button_start_stop?.setBackgroundColor(resources.getColor(R.color.logo_blue))
} else {
button_start_stop?.setBackgroundColor(resources.getColor(R.color.colorPrimaryDark))
if (running) {
callStartStop()
}
button_start_stop?.text = getString(R.string.outside_place)
}
button_start_stop.isEnabled = isInside
}
//JWToken lesen
val fis = openFileInput("JWToken")
val isr = InputStreamReader(fis)
val bufferedReader = BufferedReader(isr)
val stringBuilder = StringBuilder()
var text: String? = null
while ({ text = bufferedReader.readLine(); text }() != null) {
stringBuilder.append(text)
}
val token = stringBuilder.toString()
println("Token Main: " + token)
//Retrofit declaration
val httpClient = OkHttpClient.Builder()
val interceptor = AuthenticationInterceptor(token)
httpClient.addInterceptor(interceptor)
val builder = Retrofit.Builder()
.baseUrl("http://plesk.icaotix.de:5000")
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
val retrofit = builder.build()
service = retrofit.create(GeofenceService::class.java)
showUsername()
updateRecyclerView()
actionButton = findViewById(R.id.button_start_stop)
actionButton.setBackgroundColor(resources.getColor(R.color.colorPrimaryDark))
actionButton.setOnClickListener {
if (running) {
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setTitle(R.string.app_name)
builder.setMessage("Do you want to stop?")
builder.setIcon(R.drawable.ic_logo)
builder.setPositiveButton("Yes", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, id: Int) {
callStartStop()
dialog.dismiss()
}
})
builder.setNegativeButton("No", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, id: Int) {
dialog.dismiss()
}
})
val alert: AlertDialog = builder.create()
alert.show()
} else {
callStartStop()
}
}
//Toolbar listener
my_toolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.settings -> {
startActivity(Intent(this, Settings::class.java))
println("Settings pressed")
true
}
R.id.logout -> {
if (running) {
callStartStop()
}
deleteFile("JWToken")
startActivity(Intent(this, Login::class.java))
println("Logout pressed")
true
}
else -> false
}
}
}
private fun updateRecyclerView() {
//Recycler View
val recView: RecyclerView = records_list
recView.setHasFixedSize(true)
val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(this)
val adapter = RecordsAdapter()
val recordList = ArrayList<RecordEntry>()
val call = service.getTodaysRecords()
call.enqueue(object: Callback<EmbeddedRecords> {
override fun onResponse(call: Call<EmbeddedRecords>, response: Response<EmbeddedRecords>) {
if (response.isSuccessful) {
val entries = response.body()!!.records.entries
if (!entries.isEmpty()) {
entries.forEach {
if (it.type.equals("PAID")) {
recordList.add(RecordEntry(it.startdate.substring(11, 16)
, it.enddate.substring(11, 16)
, it.duration))
}
}
} else {
println("No Records!")
}
if (running) {
recordList.add(RecordEntry(workingSince!!, "PENDING", -1))
}
adapter.data = recordList
recView.layoutManager = layoutManager
recView.adapter = adapter
} else {
println("Response for todays records was not successful")
}
}
override fun onFailure(call: Call<EmbeddedRecords>, t: Throwable) {
println("Getting todays records failed")
}
})
}
private fun callStartStop() {
running = !running
if (running) {
account_spinner.visibility = View.GONE
button_start_stop?.text = getString(R.string.stop)
} else {
account_spinner.visibility = View.VISIBLE
button_start_stop?.text = getString(R.string.start)
}
if (!accName.isNullOrEmpty()) {
val call = service.triggerTracking(accName!!)
call.enqueue(object : Callback<ValuesTracking> {
override fun onResponse(call: Call<ValuesTracking>, response: Response<ValuesTracking>) {
workingSince = response.body()?.startdate?.substring(11, 16)
updateRecyclerView()
println("Tracking event successful!")
}
override fun onFailure(call: Call<ValuesTracking>, t: Throwable) {
println("Problem at start tracking: " + t.message)
}
})
} else {
println("Accounts list is emty")
}
println("StartStop pressed: $running")
}
private fun showUsername() {
val call = service.getUser()
call.enqueue(object : Callback<ValuesUser> {
override fun onResponse(call: Call<ValuesUser>, response: Response<ValuesUser>) {
if (response.isSuccessful) {
val firstname = response.body()?.firstname
val location = response.body()?.location
val username = response.body()?.username
getTimetrackAccounts(username!!)
lbl_username.text = "Hello " + firstname
if (location?.latitude == null) {
button_start_stop?.text = "No geofence set for you"
button_start_stop?.setBackgroundColor(resources.getColor(R.color.colorPrimaryDark))
Toast.makeText(this@MainActivity, "No geofence set for you", Toast.LENGTH_LONG)
.show()
} else {
initializeGeofence(location.latitude, location.longitude, location.radius)
}
} else {
println("Response not successful: ${response.code()}")
}
}
override fun onFailure(call: Call<ValuesUser>, t: Throwable) {
println("Response 'whoami' failed. " + t.message)
}
})
}
private fun getTimetrackAccounts(user: String) {
val accountNames = mutableListOf<String>()
// accountNames.add("None")
val call = service.getAccounts(user)
call.enqueue(object: Callback<EmbeddedAccounts> {
override fun onResponse(
call: Call<EmbeddedAccounts>,
response: Response<EmbeddedAccounts>
) {
if (response.isSuccessful) {
accounts = response.body()!!.accounts
if (!accounts.entries.isEmpty()) {
accounts.entries.forEach {
accountNames.add(it.name + "")
}
} else {
accountNames.add("None")
initializeDropdown(accountNames)
display_description.setText("You dont have any Timetrack Accounts")
Toast.makeText(this@MainActivity, "You dont have any Timetrack Accounts", Toast.LENGTH_LONG)
.show()
}
initializeDropdown(accountNames)
println("Dropdown initialized")
}
}
override fun onFailure(call: Call<EmbeddedAccounts>, t: Throwable) {
println("Failed to get accounts")
}
})
}
private fun initializeDropdown(accountNames: MutableList<String>) {
val spinner: Spinner = findViewById(R.id.account_spinner)
// Create an ArrayAdapter using the string array and a default spinner layout
val arrayAdapter = ArrayAdapter<String>(this, R.layout.spinner_layout, accountNames)
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = arrayAdapter
spinner.onItemSelectedListener = object: AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
if (!accountNames.get(0).equals("None")) {
accName = accounts.entries.get(position).name
display_description.setText(accounts.entries.get(position).description)
display_revenue.setText(accounts.entries.get(position).revenue.toString())
} else {
display_revenue.visibility = View.GONE
display_revenue_layout.visibility = View.GONE
}
println("Selected: " + accountNames.get(position))
}
override fun onNothingSelected(parent: AdapterView<*>?) {
println("Nothing selected")
}
}
}
private fun initializeGeofence(lat: Double, long: Double, rad: Float) {
geofencingClient = LocationServices.getGeofencingClient(this)
geofence = Geofence.Builder().setRequestId("Test")
.setCircularRegion(lat, long, rad)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
.build()
if (ActivityCompat.checkSelfPermission(
this,
ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(this, arrayOf(ACCESS_FINE_LOCATION), 1000)
}
geofencingClient.addGeofences(getGeofencingRequest(), geofencePendingIntent)?.run {
addOnSuccessListener {
println("Geofence added with: latitude: $lat longitude: $long radius: $rad")
}
addOnFailureListener {
println("Error: " + it.stackTrace.forEach { println(it.toString()) })
}
}
}
private fun getGeofencingRequest(): GeofencingRequest {
return GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofence(geofence)
}.build()
}
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(this, GeofenceBroadcastReceiver::class.java)
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun onResume() {
super.onResume()
startLocationUpdates()
}
private fun startLocationUpdates() {
if (ActivityCompat.checkSelfPermission(
this,
ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(this, arrayOf(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION), 1000)
}
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
}
private fun createLocationRequest() {
locationRequest = LocationRequest.create().apply {
interval = 10000
fastestInterval = 5000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
}
override fun onBackPressed() {
}
}
class RecordEntry(from: String, to: String, duration: Int) {
val from = from
val to = to
val duration = duration
}

View File

@ -1,24 +1,26 @@
package de.hft.geotracker
package de.hft.geotracker.activities
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import de.hft.geotracker.R
class Register : AppCompatActivity() {
lateinit var reg : TextView
lateinit var reg: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register)
reg = findViewById(R.id.button_create_account)
reg = findViewById(R.id.button_login)
reg.setOnClickListener {
createAccount()
}
}
private fun createAccount() {
var intent = Intent(this, MainActivity::class.java)
startActivity(intent)
Toast.makeText(this@Register, "Not yet implemented!", Toast.LENGTH_LONG)
.show()
}

View File

@ -0,0 +1,24 @@
package de.hft.geotracker.activities
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import de.hft.geotracker.R
import kotlinx.android.synthetic.main.activity_home.*
class Settings : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
my_toolbar.setNavigationOnClickListener {
onBackPressed()
}
findViewById<TextView>(R.id.button_submit).setOnClickListener {
Toast.makeText(this@Settings, "Not yet implemented!", Toast.LENGTH_LONG)
.show()
}
}
}

View File

@ -0,0 +1,17 @@
package de.hft.geotracker.retrofit
import okhttp3.Interceptor
import okhttp3.Response
class AuthenticationInterceptor(pToken: String) : Interceptor {
private val token = pToken
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val builder = original.newBuilder()
.header("Authorization", token)
val request = builder.build()
return chain.proceed(request)
}
}

View File

@ -0,0 +1,8 @@
package de.hft.geotracker.retrofit
import com.google.gson.annotations.SerializedName
class EmbeddedAccounts(accounts: ValuesTimetrackAccounts) {
@SerializedName("_embedded")
var accounts = accounts
}

View File

@ -0,0 +1,8 @@
package de.hft.geotracker.retrofit
import com.google.gson.annotations.SerializedName
class EmbeddedRecords(records: ValuesRecordsArray) {
@SerializedName("_embedded")
var records = records
}

View File

@ -0,0 +1,21 @@
package de.hft.geotracker.retrofit
import retrofit2.Call
import retrofit2.http.*
interface GeofenceService {
@POST("/login")
fun login(@Body login_data: ValuesUserLogin): Call<Void>
@GET("whoami")
fun getUser(): Call<ValuesUser>
@GET("accounts/search/findByUsername")
fun getAccounts(@Query("username") username : String): Call<EmbeddedAccounts>
@GET("track")
fun triggerTracking(@Query("account") account: String): Call<ValuesTracking>
@GET("records/search/today")
fun getTodaysRecords(): Call<EmbeddedRecords>
}

View File

@ -0,0 +1,19 @@
package de.hft.geotracker.retrofit
import com.google.gson.annotations.SerializedName
class ValuesLocation(
latitude: Double,
longitude: Double,
radius: Int
) {
@SerializedName("latitude")
var latitude = latitude
@SerializedName("longitude")
var longitude = longitude
@SerializedName("radius")
var radius = radius.toFloat()
}

View File

@ -0,0 +1,22 @@
package de.hft.geotracker.retrofit
import com.google.gson.annotations.SerializedName
class ValuesRecordEntry(
start: String,
end: String,
type: String,
duration: Int
) {
@SerializedName("startdate")
var startdate = start
@SerializedName("enddate")
var enddate = end
@SerializedName("type")
var type = type
@SerializedName("duration")
var duration = duration
}

View File

@ -0,0 +1,8 @@
package de.hft.geotracker.retrofit
import com.google.gson.annotations.SerializedName
class ValuesRecordsArray(entries: Array<ValuesRecordEntry>) {
@SerializedName("records")
var entries = entries
}

View File

@ -0,0 +1,8 @@
package de.hft.geotracker.retrofit
import com.google.gson.annotations.SerializedName
class ValuesTimetrackAccounts(entries: Array<ValuesTimetrackAccountsEntries>) {
@SerializedName("accounts")
var entries = entries
}

View File

@ -0,0 +1,19 @@
package de.hft.geotracker.retrofit
import com.google.gson.annotations.SerializedName
class ValuesTimetrackAccountsEntries(
revenue: Double,
name: String,
description: String
) {
@SerializedName("revenue")
var revenue = revenue
@SerializedName("name")
var name = name
@SerializedName("description")
var description = description
}

View File

@ -0,0 +1,30 @@
package de.hft.geotracker.retrofit
import com.google.gson.annotations.SerializedName
class ValuesTracking(
duration: Int,
start: String,
end: String,
account: String,
user: String,
type: String
) {
@SerializedName("duration")
var duration = duration
@SerializedName("startdate")
var startdate = start
@SerializedName("enddate")
var enddate = end
@SerializedName("account")
var account = account
@SerializedName("username")
var username = user
@SerializedName("type")
var type = type
}

View File

@ -0,0 +1,32 @@
package de.hft.geotracker.retrofit
import com.google.gson.annotations.SerializedName
class ValuesUser(
role: String,
firstname: String,
lastname: String,
username: String,
location: ValuesLocation,
id: Int
) {
@SerializedName("role")
var role = role
@SerializedName("firstname")
var firstname = firstname
@SerializedName("lastname")
var lastname = lastname
@SerializedName("username")
var username = username
@SerializedName("location")
var location = location
@SerializedName("id")
var id = id
}

View File

@ -0,0 +1,13 @@
package de.hft.geotracker.retrofit
import com.google.gson.annotations.SerializedName
class ValuesUserLogin(name: String, pswd: String) {
@SerializedName("username")
var username = name
@SerializedName("password")
var password = pswd
}

View File

@ -0,0 +1,61 @@
<vector android:height="35dp" android:viewportHeight="12000"
android:viewportWidth="12000" android:width="35dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#2a2a2a"
android:pathData="M5640,11934c-297,-19 -571,-57 -905,-124 -566,-115 -1236,-379 -1770,-698 -441,-263 -792,-538 -1166,-911 -167,-168 -239,-244 -363,-391 -218,-257 -481,-643 -655,-960 -260,-475 -492,-1097 -591,-1585 -55,-274 -72,-386 -112,-730 -17,-150 -17,-920 0,-1070 40,-344 57,-456 112,-730 99,-488 331,-1110 591,-1585 173,-316 431,-693 655,-960 208,-247 507,-546 754,-754 267,-224 644,-482 960,-655 475,-260 1096,-492 1585,-591 222,-45 311,-60 500,-84 290,-38 355,-41 765,-41 410,0 475,3 765,41 189,24 278,39 500,84 566,115 1236,379 1770,698 441,263 792,538 1166,911 167,168 239,244 363,391 218,257 481,643 655,960 260,475 492,1097 591,1585 55,274 72,386 112,730 8,72 13,256 13,535 0,279 -5,463 -13,535 -40,344 -57,456 -112,730 -114,565 -379,1236 -698,1770 -458,768 -1080,1422 -1817,1912 -293,195 -529,326 -860,478 -323,149 -836,317 -1170,385 -216,43 -389,72 -540,90 -49,5 -130,15 -180,21 -86,10 -790,20 -905,13z" android:strokeColor="#00000000"/>
<path android:fillColor="#801010"
android:pathData="M5640,11934c-297,-19 -571,-57 -905,-124 -566,-115 -1236,-379 -1770,-698 -441,-263 -792,-538 -1166,-911 -167,-168 -239,-244 -363,-391 -218,-257 -481,-643 -655,-960 -260,-475 -492,-1097 -591,-1585 -55,-274 -72,-386 -112,-730 -17,-150 -17,-920 0,-1070 40,-344 57,-456 112,-730 99,-488 331,-1110 591,-1585 173,-316 431,-693 655,-960 208,-247 507,-546 754,-754 267,-224 644,-482 960,-655 475,-260 1096,-492 1585,-591 222,-45 311,-60 500,-84 290,-38 355,-41 765,-41 410,0 475,3 765,41 189,24 278,39 500,84 566,115 1236,379 1770,698 441,263 792,538 1166,911 167,168 239,244 363,391 218,257 481,643 655,960 260,475 492,1097 591,1585 55,274 72,386 112,730 8,72 13,256 13,535 0,279 -5,463 -13,535 -40,344 -57,456 -112,730 -114,565 -379,1236 -698,1770 -458,768 -1080,1422 -1817,1912 -293,195 -529,326 -860,478 -323,149 -836,317 -1170,385 -216,43 -389,72 -540,90 -49,5 -130,15 -180,21 -86,10 -790,20 -905,13zM6405,11039c1939,-165 3607,-1411 4321,-3228 158,-403 270,-859 319,-1311 22,-199 31,-663 16,-875 -72,-1051 -449,-2023 -1102,-2841 -924,-1158 -2312,-1859 -3781,-1911l-188,-6 0,145 0,145 39,6c67,9 157,56 207,109 70,75 89,125 89,238 0,82 -4,103 -26,151 -48,101 -154,183 -258,198l-46,6 -3,1749 -2,1749 57,-6c32,-4 185,-20 341,-37 155,-16 408,-44 562,-60 154,-16 405,-43 558,-60 152,-16 448,-48 657,-70 209,-22 505,-54 658,-70 304,-33 812,-88 867,-95 35,-5 35,-4 -6,9 -23,8 -216,91 -430,186 -214,95 -681,302 -1039,460 -357,158 -972,431 -1365,606l-715,317 -115,13c-63,8 -241,27 -395,44 -154,16 -407,43 -562,60 -156,16 -409,44 -563,60 -154,17 -406,44 -560,60 -154,17 -407,44 -562,60 -156,17 -449,48 -653,70 -203,22 -395,47 -425,55 -30,8 -56,15 -58,15 -15,0 32,-31 54,-36 29,-7 342,-144 1669,-732 1202,-532 1100,-485 1073,-495 -12,-5 -48,-15 -78,-23 -30,-7 -107,-27 -170,-44 -63,-17 -140,-38 -170,-46 -30,-8 -77,-21 -105,-28 -27,-8 -102,-28 -165,-45 -63,-17 -167,-44 -230,-62 -63,-17 -137,-37 -165,-44 -27,-7 -84,-22 -125,-33 -41,-11 -118,-32 -170,-45 -52,-14 -131,-35 -175,-47 -44,-12 -93,-26 -110,-30 -16,-4 -73,-19 -125,-33 -204,-55 -297,-80 -345,-92 -27,-7 -77,-21 -110,-30 -60,-17 -130,-35 -300,-80 -49,-13 -117,-31 -150,-40 -33,-10 -82,-23 -110,-30 -27,-7 -77,-21 -110,-30 -33,-10 -85,-23 -115,-31 -30,-7 -107,-27 -170,-44 -186,-52 -441,-120 -500,-135 -30,-8 -80,-21 -110,-29 -30,-8 -81,-22 -113,-30l-58,-14 -10,26c-6,15 -26,95 -44,178 -235,1059 -125,2174 311,3162 624,1411 1864,2474 3344,2865 360,96 678,144 1115,171 96,6 499,-4 630,-15zM2215,3913c44,-22 65,-43 87,-87 40,-79 14,-199 -54,-247 -141,-101 -321,-11 -321,161 0,31 7,69 16,86 21,41 80,92 115,100 15,3 32,7 37,9 18,7 89,-7 120,-22zM3846,2283c116,-60 139,-232 42,-319 -45,-42 -87,-56 -150,-52 -167,12 -245,200 -135,325 64,73 158,90 243,46z" android:strokeColor="#00000000"/>
<path android:fillColor="#243a49"
android:pathData="M5640,11934c-297,-19 -571,-57 -905,-124 -566,-115 -1236,-379 -1770,-698 -441,-263 -792,-538 -1166,-911 -167,-168 -239,-244 -363,-391 -218,-257 -481,-643 -655,-960 -260,-475 -492,-1097 -591,-1585 -55,-274 -72,-386 -112,-730 -17,-150 -17,-920 0,-1070 40,-344 57,-456 112,-730 99,-488 331,-1110 591,-1585 173,-316 431,-693 655,-960 208,-247 507,-546 754,-754 267,-224 644,-482 960,-655 475,-260 1096,-492 1585,-591 222,-45 311,-60 500,-84 290,-38 355,-41 765,-41 410,0 475,3 765,41 189,24 278,39 500,84 566,115 1236,379 1770,698 441,263 792,538 1166,911 167,168 239,244 363,391 218,257 481,643 655,960 260,475 492,1097 591,1585 55,274 72,386 112,730 8,72 13,256 13,535 0,279 -5,463 -13,535 -40,344 -57,456 -112,730 -114,565 -379,1236 -698,1770 -458,768 -1080,1422 -1817,1912 -293,195 -529,326 -860,478 -323,149 -836,317 -1170,385 -216,43 -389,72 -540,90 -49,5 -130,15 -180,21 -86,10 -790,20 -905,13zM6405,11039c1939,-165 3607,-1411 4321,-3228 158,-403 270,-859 319,-1311 22,-199 31,-663 16,-875 -72,-1051 -449,-2023 -1102,-2841 -924,-1158 -2312,-1859 -3781,-1911l-188,-6 0,145 0,145 39,6c67,9 157,56 207,109 70,75 89,125 89,238 0,82 -4,103 -26,151 -48,101 -154,183 -258,198l-46,6 -5,1750 -5,1751 160,-17c88,-10 288,-31 445,-48 157,-17 412,-44 568,-61 155,-16 408,-44 562,-60 154,-17 407,-44 563,-60 155,-17 449,-48 652,-70 204,-22 453,-48 555,-59 101,-11 185,-19 187,-17 2,1 -68,22 -154,45 -87,23 -374,100 -638,171 -826,221 -1283,344 -1790,480 -269,72 -631,169 -804,215 -172,47 -316,86 -318,89 -3,3 130,527 144,562 3,7 -34,15 -104,22 -59,6 -235,25 -390,42 -156,16 -409,44 -563,60 -154,17 -406,44 -560,60 -154,17 -406,44 -560,60 -154,17 -407,44 -562,60 -156,17 -449,48 -653,70 -203,22 -395,47 -425,55 -30,8 -56,15 -58,15 -15,0 32,-31 54,-36 29,-7 342,-144 1669,-732 1202,-532 1100,-485 1073,-495 -12,-5 -48,-15 -78,-23 -30,-7 -107,-27 -170,-44 -63,-17 -140,-38 -170,-46 -30,-8 -77,-21 -105,-28 -27,-8 -102,-28 -165,-45 -63,-17 -167,-44 -230,-62 -63,-17 -137,-37 -165,-44 -27,-7 -84,-22 -125,-33 -41,-11 -118,-32 -170,-45 -52,-14 -131,-35 -175,-47 -44,-12 -93,-26 -110,-30 -16,-4 -73,-19 -125,-33 -204,-55 -297,-80 -345,-92 -27,-7 -77,-21 -110,-30 -60,-17 -130,-35 -300,-80 -49,-13 -117,-31 -150,-40 -33,-10 -82,-23 -110,-30 -27,-7 -77,-21 -110,-30 -33,-10 -85,-23 -115,-31 -30,-7 -107,-27 -170,-44 -186,-52 -441,-120 -500,-135 -30,-8 -80,-21 -110,-29 -30,-8 -81,-22 -113,-30l-58,-14 -10,26c-6,15 -26,95 -44,178 -235,1059 -125,2174 311,3162 624,1411 1864,2474 3344,2865 360,96 678,144 1115,171 96,6 499,-4 630,-15zM2215,3913c44,-22 65,-43 87,-87 40,-79 14,-199 -54,-247 -141,-101 -321,-11 -321,161 0,31 7,69 16,86 21,41 80,92 115,100 15,3 32,7 37,9 18,7 89,-7 120,-22zM3846,2283c116,-60 139,-232 42,-319 -45,-42 -87,-56 -150,-52 -167,12 -245,200 -135,325 64,73 158,90 243,46z" android:strokeColor="#00000000"/>
<path android:fillColor="#243a49"
android:pathData="M3670,10005c-78,-36 -120,-103 -120,-192 1,-187 228,-273 354,-133 83,92 66,236 -37,307 -49,34 -142,42 -197,18z" android:strokeColor="#00000000"/>
<path android:fillColor="#243a49"
android:pathData="M8115,10002c-80,-37 -121,-113 -113,-208 14,-171 225,-245 347,-123 91,90 77,240 -27,314 -55,38 -146,46 -207,17z" android:strokeColor="#00000000"/>
<path android:fillColor="#243a49"
android:pathData="M2035,8373c-81,-42 -121,-115 -113,-207 14,-166 213,-242 338,-130 105,95 87,262 -35,331 -49,28 -144,31 -190,6z" android:strokeColor="#00000000"/>
<path android:fillColor="#243a49"
android:pathData="M9745,8376c-38,-17 -90,-72 -104,-109 -6,-16 -11,-54 -11,-86 0,-79 37,-138 111,-175 87,-43 167,-30 235,38 67,67 80,145 39,233 -34,75 -96,113 -179,113 -34,-1 -74,-7 -91,-14z" android:strokeColor="#00000000"/>
<path android:fillColor="#243a49"
android:pathData="M9750,3925c-78,-36 -120,-103 -120,-192 1,-187 228,-273 354,-133 83,92 66,236 -37,307 -49,34 -142,42 -197,18z" android:strokeColor="#00000000"/>
<path android:fillColor="#243a49"
android:pathData="M8115,2293c-81,-42 -121,-115 -113,-207 14,-166 213,-242 338,-130 105,95 87,262 -35,331 -49,28 -144,31 -190,6z" android:strokeColor="#00000000"/>
<path android:fillColor="#e21d1f"
android:pathData="M5640,11934c-297,-19 -571,-57 -905,-124 -566,-115 -1236,-379 -1770,-698 -441,-263 -792,-538 -1166,-911 -167,-168 -239,-244 -363,-391 -218,-257 -481,-643 -655,-960 -260,-475 -492,-1097 -591,-1585 -55,-274 -72,-386 -112,-730 -17,-150 -17,-920 0,-1070 40,-344 57,-456 112,-730 99,-488 331,-1110 591,-1585 173,-316 431,-693 655,-960 208,-247 507,-546 754,-754 267,-224 644,-482 960,-655 475,-260 1096,-492 1585,-591 222,-45 311,-60 500,-84 290,-38 355,-41 765,-41 410,0 475,3 765,41 189,24 278,39 500,84 566,115 1236,379 1770,698 441,263 792,538 1166,911 167,168 239,244 363,391 218,257 481,643 655,960 260,475 492,1097 591,1585 55,274 72,386 112,730 8,72 13,256 13,535 0,279 -5,463 -13,535 -40,344 -57,456 -112,730 -114,565 -379,1236 -698,1770 -458,768 -1080,1422 -1817,1912 -293,195 -529,326 -860,478 -323,149 -836,317 -1170,385 -216,43 -389,72 -540,90 -49,5 -130,15 -180,21 -86,10 -790,20 -905,13zM6405,11039c1939,-165 3607,-1411 4321,-3228 158,-403 270,-859 319,-1311 22,-199 31,-663 16,-875 -72,-1051 -449,-2023 -1102,-2841 -925,-1159 -2315,-1861 -3786,-1911l-193,-6 0,145 0,145 43,5c70,8 162,55 213,110 70,75 89,125 89,238 0,82 -4,103 -26,151 -15,32 -46,76 -69,99 -53,53 -151,100 -207,100l-43,0 -2,1754 -3,1754 30,-4c17,-2 157,-18 313,-34 155,-17 408,-43 562,-60 154,-16 406,-43 560,-60 154,-16 406,-43 560,-60 154,-17 405,-43 558,-60 152,-16 383,-41 512,-55 129,-13 318,-33 420,-44 101,-11 185,-19 187,-17 2,1 -68,22 -154,45 -87,23 -374,100 -638,171 -826,221 -1283,344 -1790,480 -269,72 -631,169 -804,215 -172,47 -316,86 -318,89 -3,3 130,527 144,562 3,7 -34,15 -104,22 -59,6 -235,25 -390,42 -156,16 -409,44 -563,60 -154,17 -406,44 -560,60 -154,17 -406,44 -560,60 -154,17 -407,44 -562,60 -156,17 -449,48 -653,70 -203,22 -395,47 -425,55 -30,8 -56,15 -58,15 -15,0 32,-31 54,-36 16,-4 241,-100 499,-214 589,-260 1802,-797 2079,-921l209,-93 -124,-33c-68,-19 -146,-40 -174,-47 -27,-8 -180,-49 -340,-91 -159,-42 -310,-83 -335,-90 -25,-7 -175,-48 -335,-90 -159,-42 -312,-83 -340,-91 -27,-8 -153,-41 -280,-75 -126,-33 -252,-67 -280,-75 -27,-7 -160,-43 -295,-79 -135,-36 -261,-70 -280,-75 -19,-5 -145,-39 -280,-75 -135,-36 -267,-72 -295,-79 -209,-59 -615,-165 -639,-168 -30,-3 -30,-2 -52,82 -57,222 -112,556 -135,825 -16,179 -16,640 0,815 70,786 296,1505 677,2157 790,1348 2136,2256 3674,2477 153,22 317,37 525,50 96,6 499,-4 630,-15zM2205,3921c70,-31 125,-113 125,-186 0,-66 -51,-150 -110,-180 -102,-53 -233,-6 -281,100 -27,58 -24,113 9,176 49,94 162,133 257,90zM3835,2291c56,-25 100,-79 114,-139 35,-145 -96,-276 -241,-241 -186,44 -215,301 -43,380 51,24 119,24 170,0z" android:strokeColor="#00000000"/>
<path android:fillColor="#0096ff"
android:pathData="M5640,11934c-297,-19 -571,-57 -905,-124 -566,-115 -1236,-379 -1770,-698 -441,-263 -792,-538 -1166,-911 -167,-168 -239,-244 -363,-391 -218,-257 -481,-643 -655,-960 -260,-475 -492,-1097 -591,-1585 -55,-274 -72,-386 -112,-730 -17,-150 -17,-920 0,-1070 40,-344 57,-456 112,-730 99,-488 331,-1110 591,-1585 173,-316 431,-693 655,-960 208,-247 507,-546 754,-754 267,-224 644,-482 960,-655 475,-260 1096,-492 1585,-591 222,-45 311,-60 500,-84 290,-38 355,-41 765,-41 410,0 475,3 765,41 189,24 278,39 500,84 566,115 1236,379 1770,698 441,263 792,538 1166,911 167,168 239,244 363,391 218,257 481,643 655,960 260,475 492,1097 591,1585 55,274 72,386 112,730 8,72 13,256 13,535 0,279 -5,463 -13,535 -40,344 -57,456 -112,730 -114,565 -379,1236 -698,1770 -458,768 -1080,1422 -1817,1912 -293,195 -529,326 -860,478 -323,149 -836,317 -1170,385 -216,43 -389,72 -540,90 -49,5 -130,15 -180,21 -86,10 -790,20 -905,13zM6405,11039c1939,-165 3607,-1411 4321,-3228 158,-403 270,-859 319,-1311 22,-199 31,-663 16,-875 -72,-1051 -449,-2023 -1102,-2841 -925,-1159 -2315,-1861 -3786,-1911l-193,-6 0,145 0,145 43,5c70,8 162,55 213,110 70,75 89,125 89,238 0,82 -4,103 -26,151 -15,32 -46,76 -69,99 -53,53 -151,100 -207,100l-43,0 -2,1752 -3,1753 -80,9c-44,5 -81,10 -83,11 -1,1 29,116 67,256 38,140 106,396 151,569 45,173 84,319 87,325 3,8 -31,16 -104,23 -59,6 -235,25 -390,42 -156,16 -409,44 -563,60 -154,17 -406,44 -560,60 -154,17 -406,44 -560,60 -154,17 -407,44 -562,60 -156,17 -449,48 -653,70 -203,22 -395,47 -425,55 -30,8 -56,15 -58,15 -15,0 32,-31 54,-36 16,-4 241,-100 499,-214 589,-260 1802,-797 2079,-921l209,-93 -124,-33c-68,-19 -146,-40 -174,-47 -27,-8 -180,-49 -340,-91 -159,-42 -310,-83 -335,-90 -25,-7 -175,-48 -335,-90 -159,-42 -312,-83 -340,-91 -27,-8 -153,-41 -280,-75 -126,-33 -252,-67 -280,-75 -27,-7 -160,-43 -295,-79 -135,-36 -261,-70 -280,-75 -19,-5 -145,-39 -280,-75 -135,-36 -267,-72 -295,-79 -209,-59 -615,-165 -639,-168 -30,-3 -30,-2 -52,82 -57,222 -112,556 -135,825 -16,179 -16,640 0,815 70,786 296,1505 677,2157 790,1348 2136,2256 3674,2477 153,22 317,37 525,50 96,6 499,-4 630,-15zM2205,3921c70,-31 125,-113 125,-186 0,-66 -51,-150 -110,-180 -102,-53 -233,-6 -281,100 -27,58 -24,113 9,176 49,94 162,133 257,90zM3835,2291c56,-25 100,-79 114,-139 35,-145 -96,-276 -241,-241 -186,44 -215,301 -43,380 51,24 119,24 170,0z" android:strokeColor="#00000000"/>
<path android:fillColor="#0096ff"
android:pathData="M3685,10008c-158,-55 -183,-270 -42,-362 151,-97 344,38 306,214 -25,116 -152,187 -264,148z" android:strokeColor="#00000000"/>
<path android:fillColor="#0096ff"
android:pathData="M8125,10001c-105,-49 -152,-166 -106,-266 60,-133 235,-163 332,-58 34,36 45,59 54,109 25,153 -140,280 -280,215z" android:strokeColor="#00000000"/>
<path android:fillColor="#0096ff"
android:pathData="M2044,8376c-104,-47 -152,-173 -103,-273 84,-174 329,-148 380,40 21,78 -16,169 -89,216 -45,29 -142,38 -188,17z" android:strokeColor="#00000000"/>
<path android:fillColor="#0096ff"
android:pathData="M9739,8367c-163,-86 -133,-331 46,-376 143,-36 280,100 244,244 -32,126 -177,192 -290,132z" android:strokeColor="#00000000"/>
<path android:fillColor="#0096ff"
android:pathData="M9761,3927c-44,-14 -106,-78 -122,-124 -16,-50 -6,-133 23,-176 122,-188 414,-66 367,153 -25,118 -148,185 -268,147z" android:strokeColor="#00000000"/>
<path android:fillColor="#0096ff"
android:pathData="M8124,2296c-103,-46 -151,-171 -105,-272 63,-138 265,-158 348,-34 68,99 44,224 -55,289 -45,29 -142,38 -188,17z" android:strokeColor="#00000000"/>
<path android:fillColor="#949187"
android:pathData="M5640,11934c-297,-19 -571,-57 -905,-124 -566,-115 -1236,-379 -1770,-698 -441,-263 -792,-538 -1166,-911 -167,-168 -239,-244 -363,-391 -218,-257 -481,-643 -655,-960 -260,-475 -492,-1097 -591,-1585 -55,-274 -72,-386 -112,-730 -17,-150 -17,-920 0,-1070 40,-344 57,-456 112,-730 99,-488 331,-1110 591,-1585 173,-316 431,-693 655,-960 208,-247 507,-546 754,-754 267,-224 644,-482 960,-655 475,-260 1096,-492 1585,-591 222,-45 311,-60 500,-84 290,-38 355,-41 765,-41 410,0 475,3 765,41 189,24 278,39 500,84 566,115 1236,379 1770,698 441,263 792,538 1166,911 167,168 239,244 363,391 218,257 481,643 655,960 260,475 492,1097 591,1585 55,274 72,386 112,730 8,72 13,256 13,535 0,279 -5,463 -13,535 -40,344 -57,456 -112,730 -114,565 -379,1236 -698,1770 -458,768 -1080,1422 -1817,1912 -293,195 -529,326 -860,478 -323,149 -836,317 -1170,385 -216,43 -389,72 -540,90 -49,5 -130,15 -180,21 -86,10 -790,20 -905,13zM6405,11039c1939,-165 3607,-1411 4321,-3228 158,-403 270,-859 319,-1311 22,-199 31,-663 16,-875 -72,-1051 -449,-2023 -1102,-2841 -779,-977 -1903,-1640 -3124,-1843 -337,-57 -454,-65 -870,-65 -414,-1 -514,7 -855,65 -1047,177 -2017,687 -2770,1456 -631,643 -1074,1425 -1293,2283 -112,436 -160,826 -160,1285 0,293 10,442 49,720 241,1720 1354,3199 2954,3925 576,261 1166,400 1885,444 96,6 499,-4 630,-15z" android:strokeColor="#00000000"/>
<path android:fillColor="#949187"
android:pathData="M5833,10731c-77,-37 -137,-96 -172,-170 -22,-48 -26,-69 -26,-151 0,-76 5,-104 22,-142 32,-70 103,-141 176,-174 53,-25 74,-29 147,-29 73,0 94,4 147,29 73,33 144,104 176,174 17,38 22,66 22,142 0,82 -4,103 -26,151 -35,74 -95,133 -172,170 -53,25 -74,29 -147,29 -73,0 -94,-4 -147,-29z" android:strokeColor="#00000000"/>
<path android:fillColor="#949187"
android:pathData="M2253,6965c8,-7 28,-17 43,-21 36,-9 101,-37 974,-424 388,-172 959,-425 1270,-562 311,-138 722,-320 914,-405 192,-85 352,-151 356,-146 9,9 103,355 226,823 42,162 78,300 81,306 3,7 -34,15 -104,22 -59,6 -235,25 -390,42 -156,16 -409,44 -563,60 -154,17 -406,44 -560,60 -154,17 -406,44 -560,60 -154,17 -407,44 -562,60 -156,17 -449,48 -653,70 -203,22 -395,47 -425,55 -30,8 -56,15 -58,15 -2,0 3,-7 11,-15z" android:strokeColor="#00000000"/>
<path android:fillColor="#949187"
android:pathData="M1444,6306c-161,-40 -264,-174 -264,-341 0,-143 69,-254 198,-317 60,-30 75,-33 152,-32 105,0 176,29 245,99 181,180 116,485 -123,579 -59,23 -147,28 -208,12z" android:strokeColor="#00000000"/>
<path android:fillColor="#949187"
android:pathData="M10344,6306c-161,-40 -264,-174 -264,-341 0,-143 69,-254 198,-317 60,-30 75,-33 152,-32 105,0 176,29 245,99 181,180 116,485 -123,579 -59,23 -147,28 -208,12z" android:strokeColor="#00000000"/>
<path android:fillColor="#949187"
android:pathData="M5853,1839c-71,-27 -155,-106 -190,-177 -25,-50 -28,-68 -28,-152 0,-79 4,-103 24,-142 37,-75 95,-134 169,-170 59,-29 76,-33 152,-33 73,0 94,4 147,29 73,33 144,104 176,174 17,38 22,66 22,142 0,82 -4,103 -26,151 -35,74 -95,133 -172,170 -53,25 -74,29 -142,28 -55,0 -96,-6 -132,-20z" android:strokeColor="#00000000"/>
<path android:fillColor="#eae6d8"
android:pathData="M5640,11934c-296,-19 -576,-57 -904,-124 -494,-101 -1110,-330 -1586,-591 -316,-173 -693,-431 -960,-655 -194,-164 -521,-483 -660,-644 -104,-121 -109,-127 -176,-210 -535,-666 -942,-1498 -1134,-2320 -17,-74 -35,-153 -40,-175 -12,-51 -45,-238 -59,-330 -10,-71 -20,-153 -43,-350 -17,-150 -17,-920 0,-1070 7,-60 17,-148 23,-195 96,-804 370,-1607 787,-2305 154,-259 368,-561 548,-775 208,-247 507,-546 754,-754 267,-224 644,-482 960,-655 476,-261 1092,-490 1586,-591 215,-44 310,-60 499,-84 290,-38 355,-41 765,-41 410,0 475,3 765,41 189,24 284,40 499,84 495,101 1115,332 1586,591 322,177 693,431 960,655 194,164 521,483 660,644 104,121 109,127 176,210 535,666 942,1498 1134,2320 17,74 35,153 40,175 12,51 45,238 59,330 10,71 20,153 43,350 8,72 13,256 13,535 0,279 -5,463 -13,535 -23,197 -33,279 -43,350 -14,92 -47,279 -59,330 -5,22 -23,101 -40,175 -131,561 -364,1135 -668,1645 -457,767 -1080,1422 -1817,1912 -293,195 -529,326 -860,478 -324,149 -832,316 -1171,385 -210,43 -388,72 -539,90 -49,5 -130,15 -180,21 -86,10 -790,20 -905,13zM6361,11050c815,-63 1568,-304 2259,-723 1040,-630 1836,-1632 2210,-2782 181,-559 260,-1100 247,-1700 -12,-579 -112,-1105 -311,-1645 -71,-195 -80,-215 -174,-415 -687,-1462 -2039,-2516 -3629,-2829 -1318,-260 -2693,13 -3808,757 -396,264 -731,561 -1048,927 -423,490 -760,1081 -967,1700 -181,541 -260,1033 -260,1626 0,582 79,1070 260,1614 203,608 502,1143 927,1660 112,136 498,521 635,634 827,682 1790,1081 2823,1170 186,17 656,20 836,6z" android:strokeColor="#00000000"/>
<path android:fillColor="#eae6d8"
android:pathData="M5873,10746c-89,-29 -163,-95 -210,-186 -24,-48 -27,-67 -28,-145 0,-82 3,-96 33,-157 38,-77 91,-128 172,-167 46,-21 69,-26 140,-26 71,0 94,5 140,26 81,39 134,90 172,167 30,61 33,75 33,157 -1,79 -4,97 -30,148 -34,68 -107,140 -173,169 -59,27 -188,34 -249,14z" android:strokeColor="#00000000"/>
<path android:fillColor="#eae6d8"
android:pathData="M2280,6964c0,-3 21,-14 48,-25 75,-33 1690,-747 2621,-1161 470,-208 857,-376 861,-371 6,7 127,443 150,540 9,38 23,31 -205,92 -82,22 -438,117 -790,211 -1199,321 -1760,471 -2190,586 -236,63 -445,119 -462,124 -18,4 -33,6 -33,4z" android:strokeColor="#00000000"/>
<path android:fillColor="#eae6d8"
android:pathData="M1430,6301c-93,-29 -177,-101 -219,-190 -22,-48 -26,-69 -26,-151 0,-85 3,-101 29,-150 63,-123 177,-192 316,-193 103,-1 180,34 255,115 206,222 46,581 -258,577 -40,0 -83,-4 -97,-8z" android:strokeColor="#00000000"/>
<path android:fillColor="#eae6d8"
android:pathData="M10330,6301c-93,-29 -177,-101 -219,-190 -22,-48 -26,-69 -26,-151 0,-85 3,-101 29,-150 63,-123 177,-192 316,-193 103,-1 180,34 255,115 206,222 46,581 -258,577 -40,0 -83,-4 -97,-8z" android:strokeColor="#00000000"/>
<path android:fillColor="#eae6d8"
android:pathData="M5853,1839c-71,-28 -155,-106 -190,-177 -25,-50 -28,-68 -28,-152 0,-79 4,-103 24,-142 37,-75 95,-134 169,-170 60,-30 75,-33 152,-33 70,1 94,5 140,27 81,38 134,89 172,166 30,61 33,75 33,157 -1,79 -4,97 -30,148 -34,68 -107,140 -173,170 -69,31 -200,34 -269,6z" android:strokeColor="#00000000"/>
</vector>

View File

@ -1,72 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_grey"
tools:context=".MainActivity">
tools:context=".activities.MainActivity">
<!-- TODO: Update blank fragment layout -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/my_toolbar"
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:contextClickable="false"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
android:visibility="visible"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/menu"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:titleTextAppearance="@style/text_style" />
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/my_toolbar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:menu="@menu/menu"
app:navigationIcon="@drawable/ic_logo"
app:title="@string/app_name" />
</com.google.android.material.appbar.AppBarLayout>
<TextView
android:id="@+id/lbl_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin16"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/margin16"
android:textAlignment="center"
android:textAppearance="@style/text_style"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
<View
android:id="@+id/divider2"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="@color/colorAccent"
app:layout_constraintTop_toBottomOf="@+id/lbl_username" />
<TextView
android:id="@+id/selected_acc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginTop="@dimen/margin16"
android:layout_marginEnd="16dp"
android:fontFamily="@font/montserrat"
android:text="@string/timetrack_account"
android:textAppearance="@style/text_style"
android:textColor="@color/logo_white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toEndOf="@id/account_spinner"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/my_toolbar"
app:layout_constraintTop_toBottomOf="@+id/divider2"
app:layout_constraintVertical_bias="0.0" />
<TextView
android:id="@+id/display_acc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="140dp"
android:layout_marginEnd="28dp"
android:text="@string/no_account"
android:textAppearance="@style/text_style"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ToggleButton
<Button
android:id="@+id/button_start_stop"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="55dp"
android:layout_marginStart="@dimen/margin16"
android:layout_marginEnd="@dimen/margin16"
android:layout_marginBottom="@dimen/margin16"
android:background="@drawable/outlined_button_filled"
android:checked="true"
android:background="@color/colorPrimary"
android:enabled="false"
android:text="@string/outside_place"
android:textAppearance="@style/text_style"
android:textOff="@string/stop"
android:textOn="@string/btn_start_text"
android:textColor="@color/logo_white"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -76,16 +90,106 @@
android:id="@+id/account_spinner"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/margin16"
android:background="@color/logo_white"
android:layout_marginEnd="16dp"
android:background="@color/colorPrimary"
android:foreground="@android:drawable/arrow_down_float"
android:foregroundGravity="right|center_horizontal"
android:textAlignment="textEnd"
app:layout_constraintBottom_toTopOf="@+id/display_acc"
app:layout_constraintBottom_toBottomOf="@+id/selected_acc"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/display_description_layout"
style="@style/LoginTextInputLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin16"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/margin16"
android:colorControlNormal="@color/logo_blue"
android:hint="Description"
android:textColorHint="@color/logo_white"
app:boxBackgroundColor="@color/common_google_signin_btn_text_dark_disabled"
app:boxBackgroundMode="outline"
app:boxCornerRadiusBottomEnd="0dp"
app:boxCornerRadiusBottomStart="0dp"
app:layout_constraintBottom_toTopOf="@+id/display_revenue_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/my_toolbar" />
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/selected_acc"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/display_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:inputType="textPersonName"
android:lineSpacingExtra="8sp"
android:textAppearance="@style/text_style"
android:textColor="@color/logo_white"
android:textColorHint="@color/logo_white"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/display_revenue_layout"
style="@style/LoginTextInputLayoutStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin16"
android:layout_marginEnd="@dimen/margin16"
android:colorControlNormal="@color/logo_blue"
android:hint="Revenue"
android:textColorHint="@color/logo_white"
app:boxBackgroundColor="@color/common_google_signin_btn_text_dark_disabled"
app:boxBackgroundMode="outline"
app:boxCornerRadiusTopEnd="0dp"
app:boxCornerRadiusTopStart="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/display_description_layout"
app:layout_constraintVertical_chainStyle="packed">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/display_revenue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:enabled="false"
android:inputType="textPersonName"
android:lineSpacingExtra="8sp"
android:textAppearance="@style/text_style"
android:textColor="@color/logo_white"
android:textColorHint="@color/logo_white"
android:textSize="14sp" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/records_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/button_start_stop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_today" />
<TextView
android:id="@+id/text_today"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/today_records"
android:textAppearance="@style/text_style"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/display_revenue_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -5,77 +5,114 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_grey"
tools:context=".Login">
tools:context=".activities.Login">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/my_toolbar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/ic_logo"
app:title="@string/app_name" />
</com.google.android.material.appbar.AppBarLayout>
<EditText
android:id="@+id/setting_input_username"
style="@style/input_field"
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_username_layout"
style="@style/LoginTextInputLayoutStyle"
android:layout_width="240dp"
android:layout_height="42dp"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:colorControlNormal="@color/logo_blue"
android:ems="10"
android:hint="@string/username"
android:inputType="textPersonName"
android:textAlignment="center"
android:textColor="@color/logo_white"
app:layout_constraintBottom_toTopOf="@+id/input_password"
android:textColorHint="@color/logo_white"
app:boxBackgroundColor="@color/common_google_signin_btn_text_dark_disabled"
app:boxBackgroundMode="outline"
app:layout_constraintBottom_toTopOf="@+id/input_password_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.49"
app:layout_constraintVertical_chainStyle="packed" />
app:layout_constraintVertical_chainStyle="packed">
<EditText
android:id="@+id/input_password"
style="@style/input_field"
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/input_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:textAppearance="@style/text_style"
android:textColor="@color/logo_white"
android:textColorHint="@color/logo_white" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_password_layout"
style="@style/LoginTextInputLayoutStyle"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="32dp"
android:ems="10"
android:hint="@string/password"
android:inputType="textPassword"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@+id/button_create_account"
android:textColorHint="@color/logo_white"
app:boxBackgroundColor="@color/common_google_signin_btn_text_dark_disabled"
app:boxBackgroundMode="outline"
app:layout_constraintBottom_toTopOf="@+id/button_login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/setting_input_username"
app:layout_constraintVertical_bias="0.13" />
app:layout_constraintTop_toBottomOf="@+id/input_username_layout"
app:layout_constraintVertical_bias="0.13">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/input_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:textAppearance="@style/text_style"
android:textColor="@color/logo_white"
android:textColorHint="@color/logo_white" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/button_create_account"
style="@style/Widget.AppCompat.Button"
android:id="@+id/button_login"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_height="55dp"
android:layout_marginTop="32dp"
android:layout_marginBottom="16dp"
android:background="@drawable/outlined_button_filled"
android:backgroundTint="@color/colorAccent"
android:text="@string/login"
android:textAppearance="@style/text_style"
android:textStyle="bold"
app:cornerRadius="8dp"
app:layout_constraintBottom_toTopOf="@+id/button_register"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.502"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/input_password" />
app:layout_constraintTop_toBottomOf="@+id/input_password_layout" />
<Button
android:id="@+id/button_register"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:background="@drawable/outlined_button"
android:layout_height="55dp"
android:backgroundTint="@color/colorPrimaryDark"
android:text="@string/register"
android:textAppearance="@style/text_style"
android:textStyle="bold"
app:cornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_create_account"
app:layout_constraintTop_toBottomOf="@+id/button_login"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,7 +6,7 @@
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/background_grey"
tools:context=".MainActivity" >
tools:context=".activities.MainActivity" >
<fragment
android:id="@+id/HostFragment"

View File

@ -5,7 +5,23 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_grey"
tools:context=".Register">
tools:context=".activities.Register">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/my_toolbar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/ic_logo"
app:title="@string/app_name" />
</com.google.android.material.appbar.AppBarLayout>
<EditText
android:id="@+id/input_password2"
@ -17,12 +33,12 @@
android:hint="@string/confirm_password"
android:inputType="textPassword"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@+id/button_create_account"
app:layout_constraintBottom_toTopOf="@+id/button_login"
app:layout_constraintEnd_toEndOf="@+id/input_email_register"
app:layout_constraintTop_toBottomOf="@+id/input_password" />
app:layout_constraintTop_toBottomOf="@+id/input_password_layout" />
<EditText
android:id="@+id/input_password"
android:id="@+id/input_password_layout"
style="@style/input_field"
android:layout_width="240dp"
android:layout_height="wrap_content"
@ -62,24 +78,26 @@
android:colorControlNormal="@color/logo_blue"
android:ems="10"
android:foregroundTint="@color/colorAccent"
android:hint="@string/username"
android:hint="@string/choose_username"
android:inputType="textPersonName"
android:textAlignment="center"
android:textColor="@color/logo_white"
app:layout_constraintBottom_toTopOf="@+id/input_password"
app:layout_constraintBottom_toTopOf="@+id/input_password_layout"
app:layout_constraintEnd_toEndOf="@+id/input_email_register"
app:layout_constraintTop_toBottomOf="@+id/input_email_register" />
<Button
android:id="@+id/button_create_account"
android:id="@+id/button_login"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin16"
android:layout_marginBottom="175dp"
android:background="@drawable/outlined_button_filled"
android:backgroundTint="@color/logo_blue"
android:text="@string/create_account"
android:textAppearance="@style/text_style"
android:textStyle="bold"
app:cornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/input_email_register"
app:layout_constraintTop_toBottomOf="@+id/input_password2" />

View File

@ -3,17 +3,48 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_grey">
android:background="@color/background_grey"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/my_toolbar"
style="@style/Widget.MaterialComponents.Toolbar.Primary"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:navigationIcon="@drawable/abc_vector_test"
app:title="@string/app_name" />
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" >
android:layout_height="match_parent">
<Button
android:id="@+id/button_submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_marginStart="@dimen/margin16"
android:layout_marginTop="@dimen/margin16"
android:layout_marginEnd="@dimen/margin16"
android:layout_marginBottom="@dimen/margin16"
android:background="@drawable/outlined_button_filled"
android:text="@string/submit" />
<ScrollView
android:id="@+id/settings_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="638dp">
<LinearLayout
android:layout_width="match_parent"
@ -37,7 +68,7 @@
android:background="@color/colorAccent" />
<EditText
android:id="@+id/setting_input_username"
android:id="@+id/input_username_layout"
style="@style/input_field"
android:layout_width="match_parent"
android:layout_height="42dp"
@ -101,17 +132,8 @@
android:textAlignment="center"
android:textColor="@color/logo_white" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin16"
android:layout_marginTop="@dimen/margin16"
android:layout_marginEnd="@dimen/margin16"
android:layout_marginBottom="@dimen/margin16"
android:background="@drawable/outlined_button_filled"
android:text="@string/submit" />
</LinearLayout>
</ScrollView>
</FrameLayout>
</LinearLayout>

View File

@ -1,13 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textfield.TextInputLayout style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Test"
xmlns:android="http://schemas.android.com/apk/res/android">
android:layout_height="42dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="100dp"
android:hint="@string/no_account"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<AutoCompleteTextView
android:id="@+id/filled_exposed_dropdown"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
android:layout_height="59dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" />
</com.google.android.material.textfield.TextInputLayout>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/colorPrimary"
app:cardCornerRadius="4dp"
android:layout_marginBottom="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/recyclerText_from"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textAlignment="textStart"
android:textAppearance="@style/text_style"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/recyclerText_to"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textAlignment="viewStart"
android:textAppearance="@style/text_style"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/recyclerText_total"
app:layout_constraintStart_toEndOf="@+id/recyclerText_from"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/recyclerText_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textAppearance="@style/text_style"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,18 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_favorite"
android:icon="@drawable/ic_logo_gt"
android:title="test"
app:showAsAction="ifRoom" />
<item
android:id="@+id/setting"
android:icon="@android:drawable/btn_star"
android:id="@+id/settings"
android:contentDescription="@string/title_activity_settings"
android:title="@string/title_activity_settings"
app:showAsAction="collapseActionView" />
app:showAsAction="never" />
<item
android:id="@+id/logout"
android:title="@string/logout" />
android:contentDescription="@string/logout"
android:title="@string/logout"
app:showAsAction="never" />
</menu>

View File

@ -7,12 +7,12 @@
<fragment
android:id="@+id/login"
android:name="de.hft.geotracker.Login"
android:name="de.hft.geotracker.activities.Login"
android:label="fragment_login"
tools:layout="@layout/activity_login" />
<activity
android:id="@+id/mainActivity"
android:name="de.hft.geotracker.MainActivity"
android:name="de.hft.geotracker.activities.MainActivity"
android:label="activity_home"
tools:layout="@layout/activity_home" />
</navigation>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<color name="colorPrimary">#272727</color>
<color name="colorPrimaryDark">#272727</color>
<color name="colorAccent">#0096ff</color>
<color name="background_grey">#131313</color>
<color name="logo_blue">#0096ff</color>
<color name="logo_white">#EBE7D9</color>
<color name="mtrl_textinput_default_box_stroke_color" tools:override="true">@color/logo_white</color>
</resources>

View File

@ -8,8 +8,10 @@
<string name="btn_start_text">START</string>
<string name="pause">PAUSE</string>
<string name="stop">STOP</string>
<string name="start">START</string>
<string name="username_email">Your Username or E-Mail</string>
<string name="username">Choose a Username</string>
<string name="username">Username</string>
<string name="choose_username">Choose a Username</string>
<string name="email">E-Mail</string>
<string name="password">Password</string>
<string name="login">Login</string>
@ -35,4 +37,7 @@
<string name="create_account">Create Account</string>
<string name="submit">Submit</string>
<string name="logout">Logout</string>
<string name="hello">Hello</string>
<string name="outside_place">Outside your working place</string>
<string name="today_records">Todays Records:</string>
</resources>

View File

@ -1,7 +1,7 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
@ -20,5 +20,10 @@
<item name="android:fontFamily">@font/montserrat</item>
<item name="android:textSize">18sp</item>
</style>
<style name="LoginTextInputLayoutStyle" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<item name="boxStrokeColor">@color/logo_blue</item>
<item name="boxStrokeWidth">2dp</item>
<item name="hintTextColor">@color/logo_white</item>
</style>
</resources>

View File

@ -8,7 +8,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3'
classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong

View File

@ -1,6 +1,6 @@
#Sun Apr 05 19:25:41 CEST 2020
#Tue Jun 02 21:02:45 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

View File

@ -8,6 +8,7 @@
<processorPath useClasspath="false">
<entry name="$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.projectlombok/lombok/1.18.12/48e4e5d60309ebd833bc528dcf77668eab3cd72c/lombok-1.18.12.jar" />
</processorPath>
<module name="geotime.test" />
<module name="geotime.main" />
</profile>
</annotationProcessing>

View File

@ -5,6 +5,7 @@ RUN ["gradle", "bootJar"]
FROM openjdk:11-jre-slim
WORKDIR /root
ENV TZ=Europe/Berlin
COPY --from=build /root/build/libs/*.jar app.jar
EXPOSE 5000
ENTRYPOINT ["java", "-jar", "app.jar"]

View File

@ -18,6 +18,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.mariadb.jdbc:mariadb-java-client'

View File

@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootApplication
@ -19,4 +20,9 @@ public class GeotimeApplication {
return new BCryptPasswordEncoder();
}
@Bean
public SpelAwareProxyProjectionFactory projectionFactory() {
return new SpelAwareProxyProjectionFactory();
}
}

View File

@ -0,0 +1,68 @@
package de.hft.geotime.controllers;
import de.hft.geotime.entities.RecordType;
import de.hft.geotime.entities.TimeRecord;
import de.hft.geotime.entities.projections.RecordOverviewProjection;
import de.hft.geotime.repositories.RecordRepository;
import de.hft.geotime.repositories.TimetrackAccountRepository;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
@RestController
public class RecordController {
private final RecordRepository recordRepository;
private final TimetrackAccountRepository accountRepository;
private final ProjectionFactory projectionFactory;
public RecordController(RecordRepository recordRepository, TimetrackAccountRepository accountRepository, ProjectionFactory projectionFactory) {
this.recordRepository = recordRepository;
this.accountRepository = accountRepository;
this.projectionFactory = projectionFactory;
}
@GetMapping("/track")
public ResponseEntity<RecordOverviewProjection> track(@RequestParam String account, Authentication authentication) {
if (account == null || account.isEmpty()) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
var selectedAccount = accountRepository.findByUser_UsernameAndName(authentication.getName(), account);
if (selectedAccount == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
var entires = recordRepository.findAllByEnddateIsNull(null);
var collect = entires.get()
.filter(Objects::nonNull)
.filter(timeRecord -> selectedAccount.equals(timeRecord.getAccount()))
.findFirst();
var now = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
if (collect.isPresent()) {
collect.get().setEnddate(LocalDateTime.parse(now));
recordRepository.save(collect.get());
var projection = projectionFactory.createProjection(RecordOverviewProjection.class, collect.get());
return new ResponseEntity<>(projection, HttpStatus.OK);
} else {
var newRecord = new TimeRecord();
newRecord.setType(RecordType.PAID);
newRecord.setStartdate(LocalDateTime.parse(now));
newRecord.setAccount(accountRepository.findByUser_UsernameAndName(authentication.getName(), account));
recordRepository.save(newRecord);
var projection = projectionFactory.createProjection(RecordOverviewProjection.class, newRecord);
return new ResponseEntity<>(projection, HttpStatus.CREATED);
}
}
}

View File

@ -0,0 +1,67 @@
package de.hft.geotime.controllers;
import de.hft.geotime.entities.TimetrackUser;
import de.hft.geotime.entities.projections.UserAllEmbeddedProjection;
import de.hft.geotime.repositories.TimetrackUserRepository;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.rest.webmvc.BasePathAwareController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
@BasePathAwareController
@ResponseBody
public class UserController {
private final TimetrackUserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final ProjectionFactory projectionFactory;
public UserController(TimetrackUserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder, ProjectionFactory projectionFactory) {
this.userRepository = userRepository;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
this.projectionFactory = projectionFactory;
}
@GetMapping("/whoami")
public UserAllEmbeddedProjection getUsername(Authentication authentication) {
TimetrackUser user = userRepository.findFirstByUsername(authentication.getName());
if (user != null) {
return projectionFactory.createProjection(UserAllEmbeddedProjection.class, user);
} else {
return null;
}
}
@PostMapping("/sign-up")
public ResponseEntity<String> signUp(@RequestBody HashMap<String, String> signUpData) {
if (signUpData.get("username") == null
|| signUpData.get("password") == null
|| signUpData.get("firstname") == null
|| signUpData.get("lastname") == null) {
return new ResponseEntity<>("Missing information", HttpStatus.BAD_REQUEST);
}
TimetrackUser newUser = new TimetrackUser();
newUser.setFirstname(signUpData.get("firstname"));
newUser.setLastname(signUpData.get("lastname"));
newUser.setPassword(bCryptPasswordEncoder.encode(signUpData.get("password")));
newUser.setUsername(signUpData.get("username"));
TimetrackUser byUsername = userRepository.findFirstByUsername(newUser.getUsername());
if (byUsername == null) {
userRepository.save(newUser);
return new ResponseEntity<>("Created", HttpStatus.CREATED);
} else {
return new ResponseEntity<>("Username already exists!", HttpStatus.CONFLICT);
}
}
}

View File

@ -0,0 +1,27 @@
package de.hft.geotime.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Location {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private double latitude;
private double longitude;
private int radius;
}

View File

@ -1,4 +1,4 @@
package de.hft.geotime.record;
package de.hft.geotime.entities;
public enum RecordType {

View File

@ -1,4 +1,4 @@
package de.hft.geotime.role;
package de.hft.geotime.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -16,7 +16,7 @@ import javax.persistence.Id;
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
// TODO: Permission List

View File

@ -0,0 +1,43 @@
package de.hft.geotime.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class TimeRecord {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@ManyToOne
@OnDelete(action = OnDeleteAction.CASCADE)
private TimetrackAccount account;
@Column(columnDefinition = "TIMESTAMP")
private LocalDateTime startdate;
@Column(columnDefinition = "TIMESTAMP")
private LocalDateTime enddate;
private RecordType type;
public long getDuration() {
if (enddate == null) {
return 0;
} else {
return startdate.until(enddate, ChronoUnit.MINUTES);
}
}
}

View File

@ -1,9 +1,10 @@
package de.hft.geotime.timetrackaccount;
package de.hft.geotime.entities;
import de.hft.geotime.user.TimetrackUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import javax.persistence.*;
@ -14,12 +15,14 @@ import javax.persistence.*;
public class TimetrackAccount {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@OneToOne
private TimetrackUser timetrackUser; // TimetrackUser Id (Lazy) [REMOVE]
private double revenue;
private String name;
private String description;
@ManyToOne
@OnDelete(action = OnDeleteAction.CASCADE)
private TimetrackUser user;
}

View File

@ -0,0 +1,36 @@
package de.hft.geotime.entities;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class TimetrackUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(unique = true)
private String username;
@JsonIgnore
private String password;
private String firstname;
private String lastname;
@ManyToOne(fetch = FetchType.EAGER)
private Role role;
@ManyToOne
private Location location;
}

View File

@ -0,0 +1,18 @@
package de.hft.geotime.entities.projections;
import de.hft.geotime.entities.TimetrackAccount;
import de.hft.geotime.entities.TimetrackUser;
import org.springframework.data.rest.core.config.Projection;
@Projection(name = "withUser", types = TimetrackAccount.class)
public interface AccountWithUserProjection {
double getRevenue();
String getName();
String getDescription();
TimetrackUser getUser();
}

View File

@ -0,0 +1,27 @@
package de.hft.geotime.entities.projections;
import de.hft.geotime.entities.TimeRecord;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.rest.core.config.Projection;
import java.time.LocalDateTime;
@Projection(name = "overview", types = TimeRecord.class)
public interface RecordOverviewProjection {
LocalDateTime getStartdate();
LocalDateTime getEnddate();
long getDuration();
@Value("#{target.type.name()}")
String getType();
@Value("#{target.account.name}")
String getAccount();
@Value("#{target.account.user.username}")
String getUsername();
}

View File

@ -0,0 +1,23 @@
package de.hft.geotime.entities.projections;
import de.hft.geotime.entities.Location;
import de.hft.geotime.entities.Role;
import de.hft.geotime.entities.TimetrackUser;
import org.springframework.data.rest.core.config.Projection;
@Projection(name = "allEmbedded", types = TimetrackUser.class)
public interface UserAllEmbeddedProjection {
long getId();
String getFirstname();
String getLastname();
String getUsername();
Role getRole();
Location getLocation();
}

View File

@ -0,0 +1,19 @@
package de.hft.geotime.entities.projections;
import de.hft.geotime.entities.TimetrackUser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.rest.core.config.Projection;
@Projection(name = "onlyLocation", types = TimetrackUser.class)
public interface UserOnlyLocationProjection {
@Value("#{target.location.longitude}")
double getLongitude();
@Value("#{target.location.latitude}")
double getLatitude();
@Value("#{target.location.radius}")
int getRadius();
}

View File

@ -0,0 +1,19 @@
package de.hft.geotime.entities.projections;
import de.hft.geotime.entities.TimetrackUser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.rest.core.config.Projection;
@Projection(name = "withRole", types = TimetrackUser.class)
public interface UserWithRoleProjection {
String getFirstname();
String getLastname();
String getUsername();
@Value("#{target.role.name}")
String getRole();
}

View File

@ -1,7 +0,0 @@
package de.hft.geotime.record;
import org.springframework.data.repository.CrudRepository;
public interface RecordRepository extends CrudRepository<TimeRecord, Long> {
}

View File

@ -1,29 +0,0 @@
package de.hft.geotime.record;
import de.hft.geotime.timetrackaccount.TimetrackAccount;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import java.time.Duration;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class TimeRecord {
@Id
private long id;
@OneToOne
private TimetrackAccount account; // TimetrackAccount ID (Lazy)
private Date startdate;
private Date enddate;
private Duration time;
private RecordType type;
}

View File

@ -0,0 +1,14 @@
package de.hft.geotime.repositories;
import de.hft.geotime.entities.Location;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
@RepositoryRestResource(
path = "locations",
itemResourceRel = "locations",
collectionResourceRel = "locations"
)
public interface LocationRepository extends PagingAndSortingRepository<Location, Long> {
}

View File

@ -0,0 +1,58 @@
package de.hft.geotime.repositories;
import de.hft.geotime.entities.TimeRecord;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@RepositoryRestResource(
path = "records",
itemResourceRel = "records",
collectionResourceRel = "records"
)
public interface RecordRepository extends PagingAndSortingRepository<TimeRecord, Long> {
@RestResource(rel = "allBetween", path = "allBetween")
Page<TimeRecord> findAllByStartdateBetween(
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime start,
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime end,
Pageable pageable
);
@RestResource(rel = "allBetweenAndUser", path = "allBetweenAndUser")
Page<TimeRecord> findAllByStartdateBetweenAndAccount_User_Username(
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime start,
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime end,
String username,
Pageable pageable
);
@RestResource(rel = "allForUser", path = "allForUser")
Page<TimeRecord> findAllByAccount_User_Username(String username, Pageable pageable);
@RestResource(rel = "allForUserAndAccount", path = "allForUserAndAccount")
Page<TimeRecord> findAllByAccount_User_UsernameAndAccount_Name(String username, String account, Pageable pageable);
@RestResource(rel = "allFrom", path = "allFrom")
Page<TimeRecord> findAllByStartdateGreaterThanEqual(
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime date,
Pageable pageable
);
@Query("SELECT record from TimeRecord record " +
"where record.account.user.username = :#{principal} " +
"AND record.enddate > current_date " +
"AND record.enddate < current_date+1"
)
Page<TimeRecord> today(Pageable pageable);
@RestResource(rel = "openEntries", path = "openEntries")
Page<TimeRecord> findAllByEnddateIsNull(Pageable pageable);
}

View File

@ -1,5 +1,6 @@
package de.hft.geotime.role;
package de.hft.geotime.repositories;
import de.hft.geotime.entities.Role;
import org.springframework.data.repository.CrudRepository;
public interface RoleRepository extends CrudRepository<Role, Long> {

View File

@ -0,0 +1,23 @@
package de.hft.geotime.repositories;
import de.hft.geotime.entities.TimetrackAccount;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
@RepositoryRestResource(
path = "accounts",
itemResourceRel = "accounts",
collectionResourceRel = "accounts"
)
public interface TimetrackAccountRepository extends PagingAndSortingRepository<TimetrackAccount, Long> {
@RestResource(rel = "findByUsernameAndName", path = "findByUsernameAndName")
TimetrackAccount findByUser_UsernameAndName(String username, String account);
@RestResource(rel = "findByUsername", path = "findByUsername")
Page<TimetrackAccount> findAllByUser_Username(String username, Pageable pageable);
}

View File

@ -0,0 +1,20 @@
package de.hft.geotime.repositories;
import de.hft.geotime.entities.TimetrackUser;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import javax.websocket.server.PathParam;
@RepositoryRestResource(
path = "users",
itemResourceRel = "users",
collectionResourceRel = "users"
)
public interface TimetrackUserRepository extends PagingAndSortingRepository<TimetrackUser, Long> {
@RestResource(path = "byUsername", rel = "byUsername")
TimetrackUser findFirstByUsername(@PathParam("username") String username);
}

View File

@ -2,7 +2,6 @@ package de.hft.geotime.security;
import com.auth0.jwt.JWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.hft.geotime.user.TimetrackUser;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
@ -16,6 +15,7 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import static com.auth0.jwt.algorithms.Algorithm.HMAC512;
import static de.hft.geotime.security.SecurityConstants.*;
@ -32,16 +32,18 @@ public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilte
HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
TimetrackUser creds = new ObjectMapper().readValue(req.getInputStream(), TimetrackUser.class);
HashMap creds = new ObjectMapper().readValue(req.getInputStream(), HashMap.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
creds.get("username"),
creds.get("password"),
new ArrayList<>()
)
);
} catch (IOException e) {
throw new RuntimeException(e);
logger.info("Unsuccessful login attempt: " + e.getMessage());
res.setStatus(HttpServletResponse.SC_FORBIDDEN);
return null;
}
}
@ -51,6 +53,7 @@ public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilte
HttpServletResponse res,
FilterChain chain,
Authentication auth) {
res.setHeader("Access-Control-Expose-Headers", "Authorization");
String token = JWT.create()
.withSubject(((User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))

View File

@ -0,0 +1,16 @@
package de.hft.geotime.security;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser {
private String password;
private String username;
}

View File

@ -0,0 +1,14 @@
package de.hft.geotime.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.spel.spi.EvaluationContextExtension;
@Configuration
class SecurityConfiguration {
@Bean
EvaluationContextExtension securityExtension() {
return new SecurityEvaluationContextExtension();
}
}

View File

@ -5,5 +5,5 @@ public class SecurityConstants {
public static final long EXPIRATION_TIME = 864_000_000; // 10 days
public static final String TOKEN_PREFIX = "Bearer ";
public static final String HEADER_STRING = "Authorization";
public static final String SIGN_UP_URL = "/user/sign-up";
public static final String SIGN_UP_URL = "/sign-up";
}

View File

@ -0,0 +1,21 @@
package de.hft.geotime.security;
import org.springframework.data.spel.spi.EvaluationContextExtension;
import org.springframework.security.access.expression.SecurityExpressionRoot;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityEvaluationContextExtension implements EvaluationContextExtension {
@Override
public String getExtensionId() {
return "security";
}
@Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new SecurityExpressionRoot(authentication) {
};
}
}

View File

@ -1,5 +1,7 @@
package de.hft.geotime.user;
package de.hft.geotime.security;
import de.hft.geotime.entities.TimetrackUser;
import de.hft.geotime.repositories.TimetrackUserRepository;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;

View File

@ -1,6 +1,5 @@
package de.hft.geotime.security;
import de.hft.geotime.user.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
@ -44,8 +43,10 @@ public class WebSecurity extends WebSecurityConfigurerAdapter {
@Bean
CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration().applyPermitDefaultValues();
configuration.addAllowedMethod("*");
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
source.registerCorsConfiguration("/**", configuration);
return source;
}
}

View File

@ -1,7 +0,0 @@
package de.hft.geotime.timetrackaccount;
import org.springframework.data.repository.CrudRepository;
public interface TimetrackAccountRepository extends CrudRepository<TimetrackAccount, Long> {
}

View File

@ -1,32 +0,0 @@
package de.hft.geotime.user;
import de.hft.geotime.role.Role;
import de.hft.geotime.timetrackaccount.TimetrackAccount;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.UniqueElements;
import javax.persistence.*;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class TimetrackUser {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@UniqueElements
private String username;
private String password; // strip
private String firstname;
private String lastname;
@OneToOne
private Role role; // Projection (String)
@OneToMany
private List<TimetrackAccount> timetrackAccounts; // Lazy List
}

View File

@ -1,11 +0,0 @@
package de.hft.geotime.user;
import org.springframework.data.repository.CrudRepository;
import javax.websocket.server.PathParam;
public interface TimetrackUserRepository extends CrudRepository<TimetrackUser, Long> {
TimetrackUser findFirstByUsername(@PathParam("username") String username);
}

View File

@ -1,35 +0,0 @@
package de.hft.geotime.user;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
@RestController
@RequestMapping("/user")
public class UserController {
private TimetrackUserRepository userRepository;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public UserController(TimetrackUserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userRepository = userRepository;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
@GetMapping
public String getUsername(Authentication authentication) {
TimetrackUser timetrackUser = userRepository.findFirstByUsername(authentication.getName());
return "Welcome back " + timetrackUser.getFirstname() + " " + timetrackUser.getLastname();
}
// TODO: implement register, maybe move to another class
@PostMapping("/sign-up")
public HashMap<String, Object> signUp(@RequestBody HashMap<String, Object> payload) {
return payload;
// user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
// userRepository.save(user);
}
}

View File

@ -4,4 +4,5 @@ spring.datasource.username=sa
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.datasource.initialization-mode=always
spring.h2.console.path=/h2-console

View File

@ -2,5 +2,4 @@ spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mariadb://db:3306/geotime
spring.datasource.username=root
spring.datasource.password=supersecure
spring.datasource.initialization-mode=always
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver

View File

@ -1,18 +1,41 @@
SET FOREIGN_KEY_CHECKS=0;
DELETE FROM timetrack_user;
DELETE FROM role;
DELETE FROM location;
DELETE FROM timetrack_user;
DELETE FROM timetrack_account;
INSERT INTO role (id, `name`) VALUES
(1, 'Admin');
INSERT INTO location (id, latitude, longitude, radius) VALUES
(1, 48.804372, 9.521177, 50);
/* password is the firstname in lowercase e.g. marcel or tobias
https://bcrypt-generator.com/ with 10 rounds
*/
INSERT INTO timetrack_user (id, firstname, lastname, password, username, role_id) VALUES
(1, 'Marcel', 'Schwarz' ,'$2y$10$pDBv7dEaAiNs5Kr1.8g4XuTFx48zGxJu77rei4TlO.sDOF2yHWxo.', 'scma', 1),
(2, 'Tobias', 'Wieck' ,'$2y$10$Fxj5cGrZblGKjIExvS/MquEE0lgyYo1ILxPgPR2vSiaaLKkqJ.C.u', 'wito', 1),
(3, 'Tim', 'Zieger' ,'$2y$10$pYGHZhoaelceImO7aIN4nOkWJBp.oqNGFYaRAonHkYF4u9ljqPelC', 'ziti', 1),
(4, 'Simon', 'Kellner' ,'$2y$10$Puzm/Nr/Dyq3nQxlkXGIfubS5JPtXJSOf2e6mrQ6HhVYQN9YiQQsC', 'kesi', 1);
INSERT INTO timetrack_user (id, firstname, lastname, password, username, role_id, location_id) VALUES
(1, 'Marcel', 'Schwarz' ,'$2y$10$pDBv7dEaAiNs5Kr1.8g4XuTFx48zGxJu77rei4TlO.sDOF2yHWxo.', 'scma', 1, 1),
(2, 'Tobias', 'Wieck' ,'$2y$10$Fxj5cGrZblGKjIExvS/MquEE0lgyYo1ILxPgPR2vSiaaLKkqJ.C.u', 'wito', 1, 1),
(3, 'Tim', 'Zieger' ,'$2y$10$pYGHZhoaelceImO7aIN4nOkWJBp.oqNGFYaRAonHkYF4u9ljqPelC', 'ziti', 1, 1),
(4, 'Simon', 'Kellner' ,'$2y$10$Puzm/Nr/Dyq3nQxlkXGIfubS5JPtXJSOf2e6mrQ6HhVYQN9YiQQsC', 'kesi', 1, 1);
INSERT INTO timetrack_account (id, description, `name`, revenue, user_id) VALUES
(1, 'Gleitzeit Marcel', 'Primary', 16.0, 1),
(2, 'Festgeld Marcel', 'Secondary', 25.0, 1),
(3, 'Festgeld Tim', 'Primary', 25.0, 3);
INSERT INTO time_record (id, enddate, startdate, `type`, account_id) VALUES
(1, '2020-05-10 16:00:00', '2020-05-10 12:00:00', 0, 1),
(2, '2020-05-09 16:00:00', '2020-05-09 12:00:00', 1, 1),
(3, '2020-05-20 16:00:00', '2020-05-20 00:00:00', 1, 2),
(4, '2020-05-11 16:00:00', '2020-05-11 12:00:00', 1, 1),
(5, '2020-05-30 16:00:00', '2020-05-30 12:00:00', 1, 1),
(6, '2020-05-30 23:00:00', '2020-05-30 22:00:00', 1, 1),
(7, '2020-05-31 01:00:00', '2020-05-31 00:00:00', 1, 1),
(8, '2020-05-31 04:00:00', '2020-05-31 02:00:00', 1, 1),
(9, '2020-05-31 16:00:00', '2020-05-31 12:00:00', 1, 1),
(10, '2020-06-14 16:00:00', '2020-06-14 12:00:00', 1, 1),
(11, '2020-06-15 16:00:00', '2020-06-15 12:00:00', 1, 1);
SET FOREIGN_KEY_CHECKS=1;

View File

@ -12,6 +12,7 @@ services:
backend:
container_name: qbc_backend
restart: always
build:
context: ./backend
ports:
@ -25,7 +26,10 @@ services:
build:
context: ./sql
volumes:
- "./sql/db-data:/var/lib/mysql"
- "qbc-db-data:/var/lib/mysql"
environment:
MYSQL_DATABASE: geotime
MYSQL_ROOT_PASSWORD: supersecure
MYSQL_ROOT_PASSWORD: supersecure
volumes:
qbc-db-data:

View File

@ -24,6 +24,121 @@
\usepackage{wrapfig}
\newcommand*{\source}[1]{\par\raggedleft\footnotesize Quelle:~#1} %source command fuer bildunterschriften
% Code
\usepackage{listings}
\usepackage{xcolor}
\definecolor{dkgreen}{rgb}{0,0.6,0}
\definecolor{gray}{rgb}{0.5,0.5,0.5}
\definecolor{mauve}{rgb}{0.58,0,0.82}
\lstset{literate=%
{Ö}{{\"O}}1
{Ä}{{\"A}}1
{Ü}{{\"U}}1
{ß}{{\ss}}1
{ü}{{\"u}}1
{ä}{{\"a}}1
{ö}{{\"o}}1
}
\lstset{
frame=tblr,
frameround=tttt,
aboveskip=3mm,
belowskip=3mm,
showstringspaces=false,
columns=flexible,
basicstyle={\small\ttfamily},
numbers=none,
numberstyle=\tiny\color{gray},
keywordstyle=\color{blue},
commentstyle=\color{dkgreen},
stringstyle=\color{mauve},
breaklines=true,
breakatwhitespace=true,
tabsize=3,
xleftmargin=1.0ex,
xrightmargin=1.0ex
}
\lstdefinelanguage{docker}{
keywords={FROM, RUN, COPY, ADD, ENTRYPOINT, CMD, ENV, ARG, WORKDIR, EXPOSE, LABEL, USER, VOLUME, STOPSIGNAL, ONBUILD, MAINTAINER},
keywordstyle=\color{blue}\bfseries,
identifierstyle=\color{black},
sensitive=false,
comment=[l]{\#},
commentstyle=\color{purple}\ttfamily,
stringstyle=\color{red}\ttfamily,
morestring=[b]',
morestring=[b]",
}
\lstdefinelanguage{docker-compose}{
keywords={VERSION, SERVICES, CONTAINER\_NAME, BUILD, CONTEXT, PORTS, DEPENDS\_ON, RESTART, VOLUMES, ENVIRONMENT},
keywordstyle=\color{blue}\bfseries,
identifierstyle=\color{black},
sensitive=false,
comment=[l]{\#},
commentstyle=\color{purple}\ttfamily,
stringstyle=\color{red}\ttfamily,
morestring=[b]',
morestring=[b]",
}
\lstdefinelanguage{Kotlin}{
comment=[l]{//},
commentstyle={\color{gray}\ttfamily},
emph={delegate, filter, first, firstOrNull, forEach, lazy, map, mapNotNull, println, return@},
emphstyle={\color{mauve}},
identifierstyle=\color{black},
keywords={abstract, actual, as, as?, break, by, class, companion, continue, data, do, dynamic, else, enum, expect, false, final, for, fun, get, if, import, in, interface, internal, is, null, object, override, package, private, public, return, set, super, suspend, this, throw, true, try, typealias, val, var, vararg, when, where, while},
keywordstyle={\color{blue}\bfseries},
morecomment=[s]{/*}{*/},
morestring=[b]",
morestring=[s]{"""*}{*"""},
ndkeywords={@Deprecated, @JvmField, @JvmName, @JvmOverloads, @JvmStatic, @JvmSynthetic, Array, Byte, Double, Float, Int, Integer, Iterable, Long, Runnable, Short, String},
ndkeywordstyle={\color{orange}\bfseries},
sensitive=true,
stringstyle={\color{dkgreen}\ttfamily},
}
\colorlet{punct}{red!60!black}
\definecolor{delim}{RGB}{20,105,176}
\colorlet{numb}{magenta!60!black}
\lstdefinelanguage{json}{
basicstyle=\normalfont\ttfamily,
stepnumber=1,
numbersep=8pt,
showstringspaces=false,
breaklines=true,
literate=
*{0}{{{\color{numb}0}}}{1}
{1}{{{\color{numb}1}}}{1}
{2}{{{\color{numb}2}}}{1}
{3}{{{\color{numb}3}}}{1}
{4}{{{\color{numb}4}}}{1}
{5}{{{\color{numb}5}}}{1}
{6}{{{\color{numb}6}}}{1}
{7}{{{\color{numb}7}}}{1}
{8}{{{\color{numb}8}}}{1}
{9}{{{\color{numb}9}}}{1}
{:}{{{\color{punct}{:}}}}{1}
{,}{{{\color{punct}{,}}}}{1}
{\{}{{{\color{delim}{\{}}}}{1}
{\}}{{{\color{delim}{\}}}}}{1}
{[}{{{\color{delim}{[}}}}{1}
{]}{{{\color{delim}{]}}}}{1},
}
\lstdefinelanguage{JavaScript}{
keywords={typeof, new, true, false, catch, function, return, null, catch, switch, var, if, in, while, do, else, case, break},
keywordstyle=\color{blue}\bfseries,
ndkeywords={class, export, boolean, throw, implements, import, this},
ndkeywordstyle=\color{darkgray}\bfseries,
identifierstyle=\color{black},
sensitive=false,
comment=[l]{//},
morecomment=[s]{/*}{*/},
commentstyle=\color{purple}\ttfamily,
stringstyle=\color{red}\ttfamily,
morestring=[b]',
morestring=[b]"
}
\usepackage{setspace}
\setstretch{1.2} %Zeilenabstand
\setlength\parindent{0pt} %keine Paragrapheneinrueckung
@ -51,20 +166,46 @@
\include{parts/titlepage}
\include{parts/abstract}
\tableofcontents
\listoffigures
\lstlistoflistings
\include{parts/einleitung}
\include{parts/projektidee}
\include{parts/projektplanung}
\include{parts/design}
\include{parts/entwicklungsumgebung}
\include{parts/backend}
\include{parts/dev-setup}
\include{parts/frontend}
\include{parts/android}
\chapter{Vollständiger Application Stack}
\begin{figure}[H]
\centering
\includegraphics[width=0.9\linewidth]{img/ApplicationStack}
\caption{Application Stack}
\end{figure}
Das Deployment der Geo Timetracking Application ist in drei große Schichten aufgeteilt. Zunächst wäre hier die Backend Schicht, die Schicht der Datenhaltung und der API. Dieser Teil der Anwendung braucht am meisten Schutz, da er der wichtigste ist und dort alle Daten gespeichert werden. Der Zugriff auf die Datenbank ist nur auf das Backend beschränkt. Um nun die Applikation zu Nutzen gibt es zwei Möglichkeiten: Eine Android App oder ein Webbrowser.\\
Die Android App implementiert die View Schicht selbst und fragt nur für Daten den Backend-Dienst an. Diese Anfragen gehen zunächst an den Server, der die App hostet und werden dann von dem darauf laufenden Docker Deamon an den entsprechenden Container weitergeleitet.\\
Beim Zugriff über den Webbrowser funktioniert die Kommunikation geringfügig anders. Zunächst wird vom Client der nginx Container nach dem statischen Teil der Website gefragt, dieser lädt dann über ähnliche Anfragen wie in der Android App die Daten vom Backend. Das global gesprochene Protokoll ist hierbei immer HTTP.
\include{parts/projektbericht}
\chapter{Projektjournal}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth]{img/Clockify_Summary-Overview.pdf}
\end{figure}
Dieser Report zeigt eine Übersicht der geleisteten Arbeit jedes Gruppenmitglieds. Der vollständige Report ist separat angehängt. Dort kann jede Aktivität auf Issue Ebene genau nachvollzogen werden. Da lediglich die Issuenummern angegeben wurden, können die eigentlich dahinter liegenden Aufgaben auf GitLab\footnote{\url{https://gitlab.com/marcel.schwarz/2020ss-qbc-geofence-timetracking/-/issues?scope=all\&utf8=\%E2\%9C\%93\&state=all}} eingesehen werden.
\chapter{Projektfazit und Ausblick}
Bei dem Projekt im Rahmen von Ubiquitous/Pervasive Computing konnten wir Bekanntes anwenden und Neues lernen. Wir alle konnten uns gut einbringen und zusammen auf unser gemeinsames Ziel hinarbeiten. Im Rückblick auf die vergangenen fünf Sprints lässt sich sagen, dass diese erfolgreich verlaufen sind. Die Verteilung der Aufgaben war gleichmäßig und funktionierte reibungslos. Die Idee des Projekts konnte vollständig umgesetzt werden, zudem konnten anfangs nicht geplante Features umgesetzt werden. Hierzu zählen z.B. tagesübergreifende Time Records. Wir alle sind mit dem Ergebnis unserer Arbeit zufrieden und können das Projekt als erfolgreich bezeichnen.\\
Ebenso sehen wir ein großes Potential in der Weiterentwicklung unseres Endprodukts. Hier haben wir Ideen wie: Zuordnung der Benutzer in Gruppen, Benutzerprofile mit Daten über den Benutzer und dessen Tätigkeit oder auch Zuweisung von Kernarbeitszeit und Zeitrahmen, um Timetracking nur in einem festgelegten Zeitfenster zu erlauben. Mit ein paar Verbesserungen könnte unser Produkt von kleinen Unternehmen verwendet werden, die ein auf Vertrauen basiertes Zeitmeldesystem suchen.
\end{document}

View File

@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2020-06-09T17:16:25.356Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" etag="hh3mw3zn5PIgEuO83UB7" version="13.2.0" type="device"><diagram id="5YaEyfn9u8j8nLohUPBC" name="Page-1">7Vpbc6M2FP41fqwHkAH7MbazbWeS6e54urvpS0cGBbQLiBFyYu+vrwTiIoED6xg7TZvMeNBBCOk753znYk/AKt7/SmEa3hMfRRPL8PcTsJ5YluO6/FMIDoUAOKAQBBT7hcisBRv8A0mhIaU77KNMmcgIiRhOVaFHkgR5TJFBSsmzOu2RROpbUxiglmDjwagt/YJ9FhbSueXW8t8QDsLyzaazKO7EsJwsT5KF0CfPDRG4nYAVJYQVV/F+hSKBXYlL8dyHI3erjVGUsCEPfDLvAXn8/QtdeQ6z/1qyb5//+MUpVnmC0U4eWG6WHUoEKNklPhKLGBOwfA4xQ5sUeuLuM1c5l4UsjvjI5JePOIpWJCKUjxOS8ElL+QZEGdof3bpZAcINCZEYMXrgU+QDMwmhtKGFHD7XCpnNpSxsKAPYUgilEQTVyjVO/EJC9ROwlTCNiVvGKPmOSuHEAu5c/J8HUdNWITVBG9MKPgVTYyxMzfm/whYrACRylX83kDO7rNEaDznQAgr5nMXkkFAWkoAkMLqtpUsVynrOHSGpBPAbYuwgKRnuGFHh5XjRw1f5fD54EIOpa5fj9b55d32Qo2KzYocvq4AfiOyoh144uTw4gzRArNdf2zqlKIIMP6kbObt+QL9hwywtotcj3gulLFOCE4bo7RPHJJOIVxFEoOjDLKzU11CLShvS7iO4RdFHkmGGScLFHhKL8xvCFTCPdnfahC1hjMSNCTcRDsQNJqxjCeWoWqfpaxPhI+Z8vi62nIojxvtApAfTOPMgmuZPpRRnaAoTnxLs/52GYqevd09X9U7QxWsdzjlapHBbuud7ZjjmZ3VgLLBMtlmae4Vxk6YRxzpXgW4gHBE2QNEdnNfSla7SGPt+TgldfKrSxBkUVPHlQVVYkz6tTvocK5a7PWHmFG7cY5ZT49SWo4fGnZoXxaCkxWN82ken/VR/RsJdDCTcIphfi3AXLaf7zC8s4wMl3AcS/y3mFabGXObMHeYYzmhpxey6aUVt+Q+K4Y+eVZS1Z7+VH1HpZay83GbDzDc8qCYBly2h9/2NGjrQSg9e2Fzb0M0WkPeQYrgWQXnz6Y5/riGDW5ihNwmonvN0MEdXQTJeddzGs80kiX8j2jMiMYlglmFvWHXRGw3LyNuIu3UUPhJ5f5LVzkk186FUY3TbQEPHXeV6KRvMSPINH0X50QhOjmpiMz0bKw4qn2o2obSFqr7eQbPdcqECiNZC3FbgoTEtL4+y4xuu8v7yPXpzrGe+5biaGxQ7qJ2i0sEr/KRdGK4J523KZX/GAdruBJe/20LAtbhfKimP2VELGHbbqserBWYtjVSR1Njstgli71chQGuzVg0rRR0XLc0GtAR7A0lnQHB7IsKJ0UfVSX9cQdE233mp+PMGlxKtk6u1CwWXhRZc9Ebp4OCiGXCr/3+m4GLpwcW2X97Xy8FonOBSvvNI9ZZb3kX6wRdsB1tD2xPWVfvBVrs/0dLVf6UhDH/sKJp6JE534sHXRzEtU7Vst8wyethsZo+kbtARtN67J4Lyu/q37YlgQKX6vyeexxNNx7qyHw74Hu60bHJoMjm1lJam2eOP/flj6zcCRv43ZsdicFK5uGpOqffEzFNzSktvDIyUU+o/K5i/Kkfkw/qXRcX0+udZ4PYf</diagram></mxfile>

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 KiB

Some files were not shown because too many files have changed in this diff Show More