Compare commits

..

1 Commits

Author SHA1 Message Date
e4f427e9ff Propagate Roles from Repository 2020-05-09 18:37:26 +02:00
170 changed files with 1917 additions and 16139 deletions

2
.gitattributes vendored
View File

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

View File

@ -8,10 +8,6 @@ build-vue:
- docker build --pull -t vue .
rules:
- if: $CI_MERGE_REQUEST_ID
changes:
- frontend/**/*
when: always
- when: never
build-backend:
image: docker:latest
@ -23,41 +19,11 @@ build-backend:
- docker build --pull -t backend .
rules:
- if: $CI_MERGE_REQUEST_ID
changes:
- backend/**/*
when: always
- when: never
build-android:
image: docker:latest
stage: build
services:
- docker:dind
script:
- 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
image: debian
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"
- echo "To be done"
rules:
- if: $CI_MERGE_REQUEST_ID
changes:
- documentation/**/*
when: always
- when: never

View File

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

View File

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

View File

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

View File

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

View File

@ -26,10 +26,6 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
@ -37,12 +33,10 @@ 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.3.0'
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
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 'com.google.android.material:material:1.2.0-alpha06'
implementation 'androidx.preference:preference:1.1.1'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
@ -51,7 +45,4 @@ dependencies {
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
implementation "android.arch.navigation:navigation-fragment-ktx:2.2.2"
implementation "android.arch.navigation:navigation-ui-ktx:2.2.2"
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
implementation 'com.squareup.retrofit2:converter-gson:2.8.1'
}

View File

@ -1,38 +1,30 @@
<?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=".activities.Register" />
<activity android:name=".Register"></activity>
<activity
android:name=".activities.Settings"
android:name=".Settings"
android:label="@string/title_activity_settings" />
<activity android:name=".activities.Login">
<activity android:name=".Login">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activities.MainActivity">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<receiver android:name=".GeofenceBroadcastReceiver"/>
</application>
</manifest>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,26 +1,24 @@
package de.hft.geotracker.activities
package de.hft.geotracker
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_login)
reg = findViewById(R.id.button_create_account)
reg.setOnClickListener {
createAccount()
}
}
private fun createAccount() {
Toast.makeText(this@Register, "Not yet implemented!", Toast.LENGTH_LONG)
.show()
var intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,86 +1,72 @@
<?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=".activities.MainActivity">
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
<!-- TODO: Update blank fragment layout -->
<androidx.appcompat.widget.Toolbar
android:id="@+id/my_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:contextClickable="false"
android:elevation="4dp"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
android:visibility="visible"
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:menu="@menu/menu"
app:navigationIcon="@drawable/ic_logo"
app:title="@string/app_name" />
</com.google.android.material.appbar.AppBarLayout>
<TextView
android:id="@+id/lbl_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin16"
android:layout_marginTop="8dp"
android:layout_marginEnd="@dimen/margin16"
android:textAlignment="center"
android:textAppearance="@style/text_style"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
<View
android:id="@+id/divider2"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="@color/colorAccent"
app:layout_constraintTop_toBottomOf="@+id/lbl_username" />
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/menu"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
app:titleTextAppearance="@style/text_style" />
<TextView
android:id="@+id/selected_acc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="@dimen/margin16"
android:layout_marginEnd="16dp"
android:layout_marginTop="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="@id/account_spinner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider2"
app:layout_constraintTop_toBottomOf="@+id/my_toolbar"
app:layout_constraintVertical_bias="0.0" />
<Button
<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
android:id="@+id/button_start_stop"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin16"
android:layout_marginEnd="@dimen/margin16"
android:layout_marginBottom="@dimen/margin16"
android:background="@color/colorPrimary"
android:enabled="false"
android:text="@string/outside_place"
android:background="@drawable/outlined_button_filled"
android:checked="true"
android:textAppearance="@style/text_style"
android:textColor="@color/logo_white"
android:textOff="@string/stop"
android:textOn="@string/btn_start_text"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@ -90,106 +76,16 @@
android:id="@+id/account_spinner"
android:layout_width="180dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:background="@color/colorPrimary"
android:foreground="@android:drawable/arrow_down_float"
android:foregroundGravity="right|center_horizontal"
android:layout_marginEnd="@dimen/margin16"
android:background="@color/logo_white"
android:textAlignment="textEnd"
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_constraintBottom_toTopOf="@+id/display_acc"
app:layout_constraintEnd_toEndOf="parent"
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>
app:layout_constraintTop_toBottomOf="@+id/my_toolbar" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/records_list"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toTopOf="@+id/button_start_stop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_today" />
<TextView
android:id="@+id/text_today"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/today_records"
android:textAppearance="@style/text_style"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/display_revenue_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -5,114 +5,77 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_grey"
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>
tools:context=".Login">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_username_layout"
style="@style/LoginTextInputLayoutStyle"
<EditText
android:id="@+id/setting_input_username"
style="@style/input_field"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_height="42dp"
android:layout_marginBottom="16dp"
android:colorControlNormal="@color/logo_blue"
android:ems="10"
android:hint="@string/username"
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"
android:inputType="textPersonName"
android:textAlignment="center"
android:textColor="@color/logo_white"
app:layout_constraintBottom_toTopOf="@+id/input_password"
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" />
<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"
<EditText
android:id="@+id/input_password"
style="@style/input_field"
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:textColorHint="@color/logo_white"
app:boxBackgroundColor="@color/common_google_signin_btn_text_dark_disabled"
app:boxBackgroundMode="outline"
app:layout_constraintBottom_toTopOf="@+id/button_login"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@+id/button_create_account"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
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>
app:layout_constraintTop_toBottomOf="@+id/setting_input_username"
app:layout_constraintVertical_bias="0.13" />
<Button
android:id="@+id/button_login"
style="@style/Widget.MaterialComponents.Button"
android:id="@+id/button_create_account"
style="@style/Widget.AppCompat.Button"
android:layout_width="240dp"
android:layout_height="55dp"
android:layout_height="wrap_content"
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_layout" />
app:layout_constraintTop_toBottomOf="@+id/input_password" />
<Button
android:id="@+id/button_register"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="240dp"
android:layout_height="55dp"
android:backgroundTint="@color/colorPrimaryDark"
android:layout_height="wrap_content"
android:background="@drawable/outlined_button"
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_login"
app:layout_constraintTop_toBottomOf="@+id/button_create_account"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

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

View File

@ -5,23 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background_grey"
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>
tools:context=".Register">
<EditText
android:id="@+id/input_password2"
@ -33,12 +17,12 @@
android:hint="@string/confirm_password"
android:inputType="textPassword"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="@+id/button_login"
app:layout_constraintBottom_toTopOf="@+id/button_create_account"
app:layout_constraintEnd_toEndOf="@+id/input_email_register"
app:layout_constraintTop_toBottomOf="@+id/input_password_layout" />
app:layout_constraintTop_toBottomOf="@+id/input_password" />
<EditText
android:id="@+id/input_password_layout"
android:id="@+id/input_password"
style="@style/input_field"
android:layout_width="240dp"
android:layout_height="wrap_content"
@ -78,26 +62,24 @@
android:colorControlNormal="@color/logo_blue"
android:ems="10"
android:foregroundTint="@color/colorAccent"
android:hint="@string/choose_username"
android:hint="@string/username"
android:inputType="textPersonName"
android:textAlignment="center"
android:textColor="@color/logo_white"
app:layout_constraintBottom_toTopOf="@+id/input_password_layout"
app:layout_constraintBottom_toTopOf="@+id/input_password"
app:layout_constraintEnd_toEndOf="@+id/input_email_register"
app:layout_constraintTop_toBottomOf="@+id/input_email_register" />
<Button
android:id="@+id/button_login"
style="@style/Widget.MaterialComponents.Button"
android:id="@+id/button_create_account"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin16"
android:layout_marginBottom="175dp"
android:backgroundTint="@color/logo_blue"
android:background="@drawable/outlined_button_filled"
android:text="@string/create_account"
android:textAppearance="@style/text_style"
android:textStyle="bold"
app:cornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/input_email_register"
app:layout_constraintTop_toBottomOf="@+id/input_password2" />

View File

@ -1,19 +1,13 @@
<?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="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">
android:layout_height="wrap_content"
android:hint="Test"
xmlns:android="http://schemas.android.com/apk/res/android">
<AutoCompleteTextView
android:id="@+id/filled_exposed_dropdown"
android:layout_width="match_parent"
android:layout_height="59dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" />
android:layout_height="wrap_content"/>
</com.google.android.material.textfield.TextInputLayout>

View File

@ -3,48 +3,17 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
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>
android:background="@color/background_grey">
<FrameLayout
android:id="@+id/settings"
android:layout_width="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" />
android:layout_height="match_parent" >
<ScrollView
android:id="@+id/settings_scroll_view"
android:layout_width="match_parent"
android:layout_height="638dp">
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
@ -68,7 +37,7 @@
android:background="@color/colorAccent" />
<EditText
android:id="@+id/input_username_layout"
android:id="@+id/setting_input_username"
style="@style/input_field"
android:layout_width="match_parent"
android:layout_height="42dp"
@ -132,8 +101,17 @@
android:textAlignment="center"
android:textColor="@color/logo_white" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin16"
android:layout_marginTop="@dimen/margin16"
android:layout_marginEnd="@dimen/margin16"
android:layout_marginBottom="@dimen/margin16"
android:background="@drawable/outlined_button_filled"
android:text="@string/submit" />
</LinearLayout>
</ScrollView>
</FrameLayout>
</LinearLayout>

View File

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

View File

@ -1,15 +1,18 @@
<?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/settings"
android:contentDescription="@string/title_activity_settings"
android:id="@+id/setting"
android:icon="@android:drawable/btn_star"
android:title="@string/title_activity_settings"
app:showAsAction="never" />
app:showAsAction="collapseActionView" />
<item
android:id="@+id/logout"
android:contentDescription="@string/logout"
android:title="@string/logout"
app:showAsAction="never" />
android:title="@string/logout" />
</menu>

View File

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

View File

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

View File

@ -8,10 +8,8 @@
<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">Username</string>
<string name="choose_username">Choose a Username</string>
<string name="username">Choose a Username</string>
<string name="email">E-Mail</string>
<string name="password">Password</string>
<string name="login">Login</string>
@ -37,7 +35,4 @@
<string name="create_account">Create Account</string>
<string name="submit">Submit</string>
<string name="logout">Logout</string>
<string name="hello">Hello</string>
<string name="outside_place">Outside your working place</string>
<string name="today_records">Todays Records:</string>
</resources>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,6 @@ 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
@ -20,9 +19,4 @@ public class GeotimeApplication {
return new BCryptPasswordEncoder();
}
@Bean
public SpelAwareProxyProjectionFactory projectionFactory() {
return new SpelAwareProxyProjectionFactory();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@ 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;
@ -15,7 +16,6 @@ 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,18 +32,16 @@ public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilte
HttpServletRequest req,
HttpServletResponse res) throws AuthenticationException {
try {
HashMap creds = new ObjectMapper().readValue(req.getInputStream(), HashMap.class);
TimetrackUser creds = new ObjectMapper().readValue(req.getInputStream(), TimetrackUser.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
creds.get("username"),
creds.get("password"),
creds.getUsername(),
creds.getPassword(),
new ArrayList<>()
)
);
} catch (IOException e) {
logger.info("Unsuccessful login attempt: " + e.getMessage());
res.setStatus(HttpServletResponse.SC_FORBIDDEN);
return null;
throw new RuntimeException(e);
}
}
@ -53,7 +51,6 @@ public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilte
HttpServletResponse res,
FilterChain chain,
Authentication auth) {
res.setHeader("Access-Control-Expose-Headers", "Authorization");
String token = JWT.create()
.withSubject(((User) auth.getPrincipal()).getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))

View File

@ -2,8 +2,11 @@ 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;
@ -12,14 +15,18 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static de.hft.geotime.security.SecurityConstants.*;
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
private final TimetrackUserRepository userRepository;
public JWTAuthorizationFilter(AuthenticationManager authManager, TimetrackUserRepository userRepository) {
super(authManager);
this.userRepository = userRepository;
}
@Override
@ -41,13 +48,17 @@ public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
String username = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
.build()
.verify(token.replace(TOKEN_PREFIX, ""))
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
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);
}
return null;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
package de.hft.geotime.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;
@ -18,10 +20,12 @@ 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) {
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder, TimetrackUserRepository userRepository) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
this.userRepository = userRepository;
}
@Override
@ -31,7 +35,7 @@ public class WebSecurity extends WebSecurityConfigurerAdapter {
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager(), userRepository))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@ -43,10 +47,8 @@ public class WebSecurity extends WebSecurityConfigurerAdapter {
@Bean
CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration().applyPermitDefaultValues();
configuration.addAllowedMethod("*");
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,40 @@
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);
}
}

View File

@ -1,14 +1,13 @@
package de.hft.geotime.security;
package de.hft.geotime.user;
import de.hft.geotime.entities.TimetrackUser;
import de.hft.geotime.repositories.TimetrackUserRepository;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Collections;
import java.util.Arrays;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@ -25,7 +24,17 @@ public class UserDetailsServiceImpl implements UserDetailsService {
if (timetrackUser == null) {
throw new UsernameNotFoundException(username);
}
System.out.println("Loaded user " + timetrackUser.getFirstname() + " " + timetrackUser.getLastname());
return new User(timetrackUser.getUsername(), timetrackUser.getPassword(), Collections.emptyList());
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()))
);
}
}

View File

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

View File

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

View File

@ -1,41 +1,18 @@
SET FOREIGN_KEY_CHECKS=0;
DELETE FROM role;
DELETE FROM location;
DELETE FROM timetrack_user;
DELETE FROM timetrack_account;
DELETE FROM role;
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, 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);
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);
SET FOREIGN_KEY_CHECKS=1;

View File

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

View File

@ -24,121 +24,6 @@
\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
@ -166,46 +51,20 @@
\include{parts/titlepage}
\include{parts/abstract}
\tableofcontents
\listoffigures
\lstlistoflistings
\include{parts/einleitung}
\include{parts/projektplanung}
\include{parts/projektidee}
\include{parts/entwicklungsumgebung}
\include{parts/design}
\include{parts/backend}
\include{parts/dev-setup}
\include{parts/frontend}
\include{parts/android}
\chapter{Vollständiger Application Stack}
\begin{figure}[H]
\centering
\includegraphics[width=0.9\linewidth]{img/ApplicationStack}
\caption{Application Stack}
\end{figure}
Das Deployment der Geo Timetracking Application ist in drei große Schichten aufgeteilt. Zunächst wäre hier die Backend Schicht, die Schicht der Datenhaltung und der API. Dieser Teil der Anwendung braucht am meisten Schutz, da er der wichtigste ist und dort alle Daten gespeichert werden. Der Zugriff auf die Datenbank ist nur auf das Backend beschränkt. Um nun die Applikation zu Nutzen gibt es zwei Möglichkeiten: Eine Android App oder ein Webbrowser.\\
Die Android App implementiert die View Schicht selbst und fragt nur für Daten den Backend-Dienst an. Diese Anfragen gehen zunächst an den Server, der die App hostet und werden dann von dem darauf laufenden Docker Deamon an den entsprechenden Container weitergeleitet.\\
Beim Zugriff über den Webbrowser funktioniert die Kommunikation geringfügig anders. Zunächst wird vom Client der nginx Container nach dem statischen Teil der Website gefragt, dieser lädt dann über ähnliche Anfragen wie in der Android App die Daten vom Backend. Das global gesprochene Protokoll ist hierbei immer HTTP.
\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.
\include{parts/projektbericht}
\end{document}

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 409 KiB

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