Compare commits
233 Commits
53-impleme
...
master
Author | SHA1 | Date | |
---|---|---|---|
d123c773a0 | |||
e225fb0c43 | |||
3b2959aff2 | |||
c35ebe2735 | |||
|
8156081e04 | ||
|
d7492ad5a1 | ||
|
564e4feb86 | ||
bd9a23a0b0 | |||
50f8e36800 | |||
6a00a258a2 | |||
0d6df49493 | |||
4188c15b4b | |||
ee874ac51e | |||
9c145c0d9c | |||
|
76530bc09f | ||
4974ae0e10 | |||
1d9740c9bc | |||
|
2152fa61f7 | ||
6feed2ec2a | |||
|
605dcb7f51 | ||
e0cab7f5b3 | |||
|
6defc2c353 | ||
6f42a97b9e | |||
b79a997bf8 | |||
56bfd038e4 | |||
9382624d3f | |||
8de701c19f | |||
1f7d044ccc | |||
fa82e80f13 | |||
f525316400 | |||
ad833f3831 | |||
d46411c4e4 | |||
fffde13440 | |||
7b4887ecac | |||
ff1afd7c32 | |||
|
f9ce4d4db8 | ||
|
d9ee892c76 | ||
7efd7ec891 | |||
99e5005831 | |||
e38a3c4ce0 | |||
9255bce492 | |||
d3a0f3ad64 | |||
89d64102bd | |||
9e4584cf12 | |||
8c7eb8431c | |||
dd110e04fb | |||
814bd585c2 | |||
cdb87ea4c9 | |||
207f300e66 | |||
95d5025c0a | |||
f7c6914f4d | |||
41b53998de | |||
88bc4517fe | |||
38db18a492 | |||
cc41db8f16 | |||
92da1e539e | |||
d0cae8af87 | |||
1fce986a2d | |||
|
f7ad0c43ea | ||
|
b2b884731a | ||
|
722f9cd465 | ||
|
8f0f6c2d8f | ||
|
40f7f218a2 | ||
|
de3b086a45 | ||
|
089df551a7 | ||
|
dade003d84 | ||
|
5b2d6a7cfe | ||
|
1cc688bed0 | ||
53d5321541 | |||
6f996d2784 | |||
25c9c23b0b | |||
1f5d8deb66 | |||
5d14d379db | |||
1d24f210f1 | |||
6cdd8d9ce1 | |||
62bac89e98 | |||
2a66c34356 | |||
|
acf9736f92 | ||
8a5904d513 | |||
edd03c5a7e | |||
|
5e13d103d0 | ||
|
d235c8ddc0 | ||
|
23aa33209a | ||
|
8e73cc6e2c | ||
|
c304632d6e | ||
|
aab586a4c8 | ||
|
6a42650c40 | ||
|
08ffe71433 | ||
|
2ed09692f8 | ||
|
20ab2931f2 | ||
|
c11d57de2e | ||
|
b0759a64b7 | ||
|
a80acb0797 | ||
|
b379a750dc | ||
|
8c75cf414f | ||
|
5597afb4b0 | ||
|
e30aa5798a | ||
|
3e772d989e | ||
|
7954bf7c04 | ||
|
e2c698fd08 | ||
|
8e54997b87 | ||
|
f2397c6c92 | ||
|
04c9b7b55f | ||
daf542c527 | |||
0a0bc34e38 | |||
207b22afa1 | |||
|
09d206c279 | ||
|
b07e5cd357 | ||
918cf67b1f | |||
9a8faa3f0d | |||
8f2b697f05 | |||
179af69564 | |||
ca4da8c993 | |||
5060da28ca | |||
61fccccd33 | |||
0c1550c7a7 | |||
b602db1bd9 | |||
cf00b314d3 | |||
bd1795fb7e | |||
f3952c1ea8 | |||
17a759ce1e | |||
|
ea4493f955 | ||
|
ea53de257e | ||
|
f3b9a0879b | ||
|
3c4cbaf126 | ||
|
f509ad0d9a | ||
|
04abba8c1c | ||
|
d1910ea10d | ||
|
8521832315 | ||
613c5cf0ac | |||
161f5c7054 | |||
77c6b85eec | |||
75d2cb6670 | |||
|
19d01b1502 | ||
|
5ace5c4317 | ||
|
c52daee815 | ||
|
dad0594854 | ||
|
2a1eb3feef | ||
|
1fbed97fb2 | ||
|
73187542cd | ||
|
08b996308f | ||
|
95aaac09c8 | ||
|
8e2a8208f3 | ||
|
f41a2af656 | ||
|
5cd2defd3d | ||
|
6bd11577e0 | ||
|
57e8541c0e | ||
|
d5ef0004f6 | ||
|
5908de5650 | ||
|
6334b62e2b | ||
|
c124818fa6 | ||
|
6907710f51 | ||
|
881d01535d | ||
50446e0654 | |||
4e560b9d36 | |||
|
0d4eb694c9 | ||
|
8992dd028b | ||
b1320fb11e | |||
214d5b9218 | |||
|
0047556180 | ||
|
52e862afbe | ||
b20f2e8a61 | |||
781ec85851 | |||
|
83bd086792 | ||
|
2a68777f67 | ||
|
edd7317aa4 | ||
|
3a04ff8823 | ||
|
70e3a60930 | ||
|
841a9545e6 | ||
|
4b2fe41d9f | ||
|
736e571471 | ||
|
7488b5bb03 | ||
|
030b8c47d3 | ||
ed284da1ac | |||
5e161d9c9d | |||
5eb6efcffe | |||
9f8fd0af1e | |||
3310062138 | |||
a6c4cc48d9 | |||
|
3d1f2fd741 | ||
|
a1072f40b3 | ||
04d4d9563b | |||
24b6ff4618 | |||
0f700d87b0 | |||
|
0aa257dbaf | ||
|
65fc373f97 | ||
|
10a1d3a3fa | ||
|
5d366e3c8b | ||
|
4c34ac3497 | ||
|
5d98afb5c4 | ||
|
b673c0767f | ||
|
5085cd45ce | ||
af3d5686d7 | |||
8a79d863f6 | |||
|
fc813f14db | ||
|
efa7587702 | ||
6e4200cea6 | |||
4c81923977 | |||
|
3dd71a11eb | ||
|
3c42d55881 | ||
|
9326b88973 | ||
8b2e26042c | |||
f8f9f12dcc | |||
|
8d3d820286 | ||
|
b0b33bfbb3 | ||
0e2f880755 | |||
71ee4b9bd9 | |||
89ba108078 | |||
d47733cc6f | |||
0d95e5f8f6 | |||
bd89747020 | |||
055abd95a4 | |||
3a48734544 | |||
96671d5350 | |||
4c05c668cb | |||
8dc4604f61 | |||
75e7d7f11e | |||
ae796fe3ab | |||
ad0d279b82 | |||
d4f39f27ae | |||
|
b4d6b5dd0c | ||
7cd4d52138 | |||
3ca73361f0 | |||
|
859fd1d5ec | ||
|
e6834a18e6 | ||
d66ecc43b7 | |||
e83bfd6e5a | |||
48fd0cfdc0 | |||
22df05010b | |||
798ca8b168 | |||
f186a89bb8 | |||
6abdab2cf6 | |||
8e7f9c06d3 |
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.pptx filter=lfs diff=lfs merge=lfs -text
|
||||
*.apk filter=lfs diff=lfs merge=lfs -text
|
@ -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
|
||||
|
@ -1 +1,2 @@
|
||||
# UBC - Timetracking with Geofences
|
||||
# UBC SS2020 - Geo Timetracking - Team TacocaT
|
||||
![Geo Timetracking](other-artifacts/Product-Flyer.png)
|
25
android/.idea/jarRepositories.xml
Normal 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>
|
@ -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
@ -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
|
@ -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'
|
||||
}
|
||||
|
@ -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>
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
108
android/app/src/main/java/de/hft/geotracker/activities/Login.kt
Normal 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()}")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package de.hft.geotracker.retrofit
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class EmbeddedAccounts(accounts: ValuesTimetrackAccounts) {
|
||||
@SerializedName("_embedded")
|
||||
var accounts = accounts
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package de.hft.geotracker.retrofit
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class EmbeddedRecords(records: ValuesRecordsArray) {
|
||||
@SerializedName("_embedded")
|
||||
var records = records
|
||||
}
|
@ -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>
|
||||
}
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
61
android/app/src/main/res/drawable/ic_logo.xml
Normal 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>
|
@ -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: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:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||
app:titleTextAppearance="@style/text_style" />
|
||||
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>
|
@ -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>
|
@ -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"
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
@ -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>
|
55
android/app/src/main/res/layout/text_item_view.xml
Normal 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>
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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"]
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
27
backend/src/main/java/de/hft/geotime/entities/Location.java
Normal 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;
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package de.hft.geotime.record;
|
||||
package de.hft.geotime.entities;
|
||||
|
||||
public enum RecordType {
|
||||
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package de.hft.geotime.record;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface RecordRepository extends CrudRepository<TimeRecord, Long> {
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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> {
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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> {
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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))
|
||||
|
@ -2,11 +2,8 @@ package de.hft.geotime.security;
|
||||
|
||||
import com.auth0.jwt.JWT;
|
||||
import com.auth0.jwt.algorithms.Algorithm;
|
||||
import de.hft.geotime.user.TimetrackUser;
|
||||
import de.hft.geotime.user.TimetrackUserRepository;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
|
||||
@ -15,18 +12,14 @@ import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static de.hft.geotime.security.SecurityConstants.*;
|
||||
|
||||
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
|
||||
|
||||
private final TimetrackUserRepository userRepository;
|
||||
|
||||
public JWTAuthorizationFilter(AuthenticationManager authManager, TimetrackUserRepository userRepository) {
|
||||
public JWTAuthorizationFilter(AuthenticationManager authManager) {
|
||||
super(authManager);
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -48,17 +41,13 @@ public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
|
||||
String token = request.getHeader(HEADER_STRING);
|
||||
if (token != null) {
|
||||
// parse the token.
|
||||
String username = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
|
||||
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
|
||||
.build()
|
||||
.verify(token.replace(TOKEN_PREFIX, ""))
|
||||
.getSubject();
|
||||
|
||||
TimetrackUser user = userRepository.findFirstByUsername(username);
|
||||
SimpleGrantedAuthority role = new SimpleGrantedAuthority(user.getRole().getName());
|
||||
|
||||
if (username != null) {
|
||||
List<SimpleGrantedAuthority> authorityList = Collections.singletonList(role);
|
||||
return new UsernamePasswordAuthenticationToken(username, null, authorityList);
|
||||
if (user != null) {
|
||||
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
16
backend/src/main/java/de/hft/geotime/security/LoginUser.java
Normal 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;
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
@ -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) {
|
||||
};
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
package de.hft.geotime.user;
|
||||
package de.hft.geotime.security;
|
||||
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
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;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
@Service
|
||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
@ -24,17 +25,7 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
if (timetrackUser == null) {
|
||||
throw new UsernameNotFoundException(username);
|
||||
}
|
||||
System.out.println("Loaded user "
|
||||
+ timetrackUser.getFirstname()
|
||||
+ " "
|
||||
+ timetrackUser.getLastname()
|
||||
+ " with role: "
|
||||
+ timetrackUser.getRole().getName()
|
||||
);
|
||||
return new User(
|
||||
timetrackUser.getUsername(),
|
||||
timetrackUser.getPassword(),
|
||||
Arrays.asList(new SimpleGrantedAuthority(timetrackUser.getRole().getName()))
|
||||
);
|
||||
System.out.println("Loaded user " + timetrackUser.getFirstname() + " " + timetrackUser.getLastname());
|
||||
return new User(timetrackUser.getUsername(), timetrackUser.getPassword(), Collections.emptyList());
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package de.hft.geotime.security;
|
||||
|
||||
import de.hft.geotime.user.TimetrackUserRepository;
|
||||
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;
|
||||
@ -20,12 +18,10 @@ import static de.hft.geotime.security.SecurityConstants.SIGN_UP_URL;
|
||||
public class WebSecurity extends WebSecurityConfigurerAdapter {
|
||||
private final UserDetailsServiceImpl userDetailsService;
|
||||
private final BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||
private final TimetrackUserRepository userRepository;
|
||||
|
||||
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder, TimetrackUserRepository userRepository) {
|
||||
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -35,7 +31,7 @@ public class WebSecurity extends WebSecurityConfigurerAdapter {
|
||||
.anyRequest().authenticated()
|
||||
.and()
|
||||
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
|
||||
.addFilter(new JWTAuthorizationFilter(authenticationManager(), userRepository))
|
||||
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
|
||||
// this disables session creation on Spring Security
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||
}
|
||||
@ -47,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;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package de.hft.geotime.timetrackaccount;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
public interface TimetrackAccountRepository extends CrudRepository<TimetrackAccount, Long> {
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -1,40 +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 final TimetrackUserRepository userRepository;
|
||||
private final 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()
|
||||
+ " roles from Auth: "
|
||||
+ authentication.getAuthorities();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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
|
@ -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;
|
@ -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
|
||||
|
||||
volumes:
|
||||
qbc-db-data:
|
@ -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/dev-setup}
|
||||
\include{parts/backend}
|
||||
|
||||
\include{parts/projektbericht}
|
||||
\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.
|
||||
|
||||
\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}
|
||||
|
1
documentation/img/ApplicationStack.drawio
Normal 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>
|
BIN
documentation/img/ApplicationStack.pdf
Normal file
BIN
documentation/img/Clockify_Summary-Overview.pdf
Normal file
BIN
documentation/img/android/btn_no_fence.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
documentation/img/android/btn_outside.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
documentation/img/android/btn_start.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
documentation/img/android/btn_stop.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
documentation/img/android/confirm_stop.JPG
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
documentation/img/android/login.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
documentation/img/android/main.png
Normal file
After Width: | Height: | Size: 106 KiB |
BIN
documentation/img/android/main_menu.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
documentation/img/android/main_recording.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
documentation/img/android/register.png
Normal file
After Width: | Height: | Size: 76 KiB |
BIN
documentation/img/android/settings.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
documentation/img/backend/er-modell.png
Normal file
After Width: | Height: | Size: 409 KiB |