diff --git a/android/.idea/jarRepositories.xml b/android/.idea/jarRepositories.xml new file mode 100644 index 0000000..a5f05cd --- /dev/null +++ b/android/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml index b6ea2b1..7bfef59 100644 --- a/android/.idea/misc.xml +++ b/android/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/android/app/build.gradle b/android/app/build.gradle index 6dbdb6b..41e998a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -37,9 +37,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.2.0' + implementation 'androidx.core:core-ktx:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'com.google.android.material:material:1.1.0' implementation "com.google.android.gms:play-services-location:17.0.0" implementation 'androidx.preference:preference:1.1.1' diff --git a/android/app/src/main/java/de/hft/geotracker/RecordsAdapter.kt b/android/app/src/main/java/de/hft/geotracker/RecordsAdapter.kt new file mode 100644 index 0000000..1df65dc --- /dev/null +++ b/android/app/src/main/java/de/hft/geotracker/RecordsAdapter.kt @@ -0,0 +1,44 @@ +package de.hft.geotracker + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.TextView +import androidx.cardview.widget.CardView +import androidx.recyclerview.widget.RecyclerView +import de.hft.geotracker.activities.RecordEntry + +class RecordsAdapter : RecyclerView.Adapter() { + var data = listOf() + 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(R.id.recyclerText_from) + val textTo = itemView.findViewById(R.id.recyclerText_to) + val textTotal = itemView.findViewById(R.id.recyclerText_total) +} \ No newline at end of file diff --git a/android/app/src/main/java/de/hft/geotracker/activities/Login.kt b/android/app/src/main/java/de/hft/geotracker/activities/Login.kt index e1e02a0..895ce8f 100644 --- a/android/app/src/main/java/de/hft/geotracker/activities/Login.kt +++ b/android/app/src/main/java/de/hft/geotracker/activities/Login.kt @@ -54,7 +54,7 @@ class Login : AppCompatActivity() { .build() service = retrofit.create(GeofenceService::class.java) - login = findViewById(R.id.button_create_account) + login = findViewById(R.id.button_login) login.setOnClickListener { intent = Intent(this, MainActivity::class.java) login() diff --git a/android/app/src/main/java/de/hft/geotracker/activities/MainActivity.kt b/android/app/src/main/java/de/hft/geotracker/activities/MainActivity.kt index ccaf3bf..ddb55c9 100644 --- a/android/app/src/main/java/de/hft/geotracker/activities/MainActivity.kt +++ b/android/app/src/main/java/de/hft/geotracker/activities/MainActivity.kt @@ -1,8 +1,12 @@ 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 @@ -12,12 +16,14 @@ 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 kotlinx.android.synthetic.main.dropdown_menu.* import okhttp3.OkHttpClient import retrofit2.Call import retrofit2.Callback @@ -34,6 +40,7 @@ class MainActivity : AppCompatActivity() { 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 @@ -65,19 +72,20 @@ class MainActivity : AppCompatActivity() { ?.apply() getSharedPreferences("LOCATION", Context.MODE_PRIVATE) .registerOnSharedPreferenceChangeListener { sharedPreferences, key -> - val btnState = sharedPreferences.getBoolean("ENABLED", false) + val isInside = sharedPreferences.getBoolean("ENABLED", false) - println("Buttonstate: $btnState") - if (btnState) { + 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 { + } else { button_start_stop?.setBackgroundColor(resources.getColor(R.color.colorPrimaryDark)) if (running) { - button_start_stop.toggle() callStartStop() } + button_start_stop?.text = getString(R.string.outside_place) } - button_start_stop.isEnabled = btnState + button_start_stop.isEnabled = isInside } //JWToken lesen @@ -102,37 +110,35 @@ class MainActivity : AppCompatActivity() { val retrofit = builder.build() service = retrofit.create(GeofenceService::class.java) showUsername() - - //Get Timetrack Accounts - val accountNames = mutableListOf() -// accountNames.add("None") - val call = service.getAccounts() - call.enqueue(object: Callback { - override fun onResponse( - call: Call, - response: Response - ) { - if (response.isSuccessful) { - accounts = response.body()!!.accounts - accounts.entries.forEach { - accountNames.add(it.name + "") - } - initializeDropdown(accountNames) - } - } - override fun onFailure(call: Call, t: Throwable) { - accountNames.add("None") - initializeDropdown(accountNames) - Toast.makeText(this@MainActivity, "You dont have any Timetrack Accounts ", Toast.LENGTH_LONG) - .show() - } - }) + updateRecyclerView() actionButton = findViewById(R.id.button_start_stop) + actionButton.setBackgroundColor(resources.getColor(R.color.colorPrimaryDark)) actionButton.setOnClickListener { - callStartStop() + 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 -> { @@ -141,6 +147,9 @@ class MainActivity : AppCompatActivity() { true } R.id.logout -> { + if (running) { + callStartStop() + } deleteFile("JWToken") startActivity(Intent(this, Login::class.java)) println("Logout pressed") @@ -149,30 +158,76 @@ class MainActivity : AppCompatActivity() { 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() + + val call = service.getTodaysRecords() + call.enqueue(object: Callback { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + val entries = response.body()!!.records.entries + if (!entries.isEmpty()) { + entries.forEach { + recordList.add(RecordEntry(it.startdate.substring(11, 16) + , it.enddate.substring(11, 16) + , it.duration)) + } + if (running) { + recordList.add(RecordEntry(workingSince!!, "PENDING", -1)) + } + adapter.data = recordList + recView.layoutManager = layoutManager + recView.adapter = adapter + } else { + println("No Records!") + } + + } else { + println("Response for todays records was not successful") + } + } + override fun onFailure(call: Call, 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 { override fun onResponse(call: Call, response: Response) { - latitude.text = response.body()?.startdate - longitude.text = response.body()?.enddate + workingSince = response.body()?.startdate?.substring(11, 16) + updateRecyclerView() println("Tracking event successful!") } override fun onFailure(call: Call, t: Throwable) { println("Problem at start tracking: " + t.message) } }) + } else { + println("Accounts list is emty") } println("StartStop pressed: $running") - //ToDO call /track Endpoint } private fun showUsername() { @@ -182,13 +237,16 @@ class MainActivity : AppCompatActivity() { 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 - println("Body: " + 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) + initializeGeofence(location.latitude, location.longitude, location.radius) } } else { println("Response not successful: ${response.code()}") @@ -199,6 +257,41 @@ class MainActivity : AppCompatActivity() { println("Response 'whoami' failed. " + t.message) } }) + + + + } + private fun getTimetrackAccounts(user: String) { + val accountNames = mutableListOf() +// accountNames.add("None") + val call = service.getAccounts(user) + call.enqueue(object: Callback { + override fun onResponse( + call: Call, + response: Response + ) { + 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, t: Throwable) { + println("Failed to get accounts") + } + }) } private fun initializeDropdown(accountNames: MutableList) { val spinner: Spinner = findViewById(R.id.account_spinner) @@ -218,8 +311,6 @@ class MainActivity : AppCompatActivity() { display_description.setText(accounts.entries.get(position).description) display_revenue.setText(accounts.entries.get(position).revenue.toString()) } else { - display_description.visibility = View.GONE - display_description_layout.visibility = View.GONE display_revenue.visibility = View.GONE display_revenue_layout.visibility = View.GONE } @@ -237,6 +328,13 @@ class MainActivity : AppCompatActivity() { .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") @@ -266,6 +364,16 @@ class MainActivity : AppCompatActivity() { 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, @@ -285,3 +393,11 @@ class MainActivity : AppCompatActivity() { } } + +class RecordEntry(from: String, to: String, duration: Int) { + val from = from + val to = to + val duration = duration +} + + diff --git a/android/app/src/main/java/de/hft/geotracker/activities/Register.kt b/android/app/src/main/java/de/hft/geotracker/activities/Register.kt index faa0ffd..6fbfd19 100644 --- a/android/app/src/main/java/de/hft/geotracker/activities/Register.kt +++ b/android/app/src/main/java/de/hft/geotracker/activities/Register.kt @@ -3,6 +3,7 @@ package de.hft.geotracker.activities import android.content.Intent import android.os.Bundle import android.widget.TextView +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import de.hft.geotracker.R @@ -11,14 +12,15 @@ class Register : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_register) - reg = findViewById(R.id.button_create_account) + reg = findViewById(R.id.button_login) reg.setOnClickListener { createAccount() } } private fun createAccount() { - startActivity(Intent(this, MainActivity::class.java)) + Toast.makeText(this@Register, "Not yet implemented!", Toast.LENGTH_LONG) + .show() } diff --git a/android/app/src/main/java/de/hft/geotracker/activities/Settings.kt b/android/app/src/main/java/de/hft/geotracker/activities/Settings.kt index 16e2f70..438e020 100644 --- a/android/app/src/main/java/de/hft/geotracker/activities/Settings.kt +++ b/android/app/src/main/java/de/hft/geotracker/activities/Settings.kt @@ -1,6 +1,8 @@ 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.* @@ -9,10 +11,14 @@ class Settings : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.settings_activity) + setContentView(R.layout.activity_settings) my_toolbar.setNavigationOnClickListener { onBackPressed() } + findViewById(R.id.button_submit).setOnClickListener { + Toast.makeText(this@Settings, "Not yet implemented!", Toast.LENGTH_LONG) + .show() + } } } \ No newline at end of file diff --git a/android/app/src/main/java/de/hft/geotracker/retrofit/EmbeddedRecords.kt b/android/app/src/main/java/de/hft/geotracker/retrofit/EmbeddedRecords.kt new file mode 100644 index 0000000..503d782 --- /dev/null +++ b/android/app/src/main/java/de/hft/geotracker/retrofit/EmbeddedRecords.kt @@ -0,0 +1,8 @@ +package de.hft.geotracker.retrofit + +import com.google.gson.annotations.SerializedName + +class EmbeddedRecords(records: ValuesRecordsArray) { + @SerializedName("_embedded") + var records = records +} \ No newline at end of file diff --git a/android/app/src/main/java/de/hft/geotracker/retrofit/GeofenceService.kt b/android/app/src/main/java/de/hft/geotracker/retrofit/GeofenceService.kt index d437f99..9839b51 100644 --- a/android/app/src/main/java/de/hft/geotracker/retrofit/GeofenceService.kt +++ b/android/app/src/main/java/de/hft/geotracker/retrofit/GeofenceService.kt @@ -1,10 +1,7 @@ package de.hft.geotracker.retrofit import retrofit2.Call -import retrofit2.http.Body -import retrofit2.http.GET -import retrofit2.http.POST -import retrofit2.http.Query +import retrofit2.http.* interface GeofenceService { @POST("/login") @@ -13,9 +10,12 @@ interface GeofenceService { @GET("whoami") fun getUser(): Call - @GET("accounts") - fun getAccounts(): Call + @GET("accounts/search/findByUsername") + fun getAccounts(@Query("username") username : String): Call @GET("track") fun triggerTracking(@Query("account") account: String): Call + + @GET("records/search/today") + fun getTodaysRecords(): Call } \ No newline at end of file diff --git a/android/app/src/main/java/de/hft/geotracker/retrofit/ValuesRecordEntry.kt b/android/app/src/main/java/de/hft/geotracker/retrofit/ValuesRecordEntry.kt new file mode 100644 index 0000000..8e9848b --- /dev/null +++ b/android/app/src/main/java/de/hft/geotracker/retrofit/ValuesRecordEntry.kt @@ -0,0 +1,22 @@ +package de.hft.geotracker.retrofit + +import com.google.gson.annotations.SerializedName + +class ValuesRecordEntry( + start: String, + end: String, + type: String, + duration: Int +) { + @SerializedName("startdate") + var startdate = start + + @SerializedName("enddate") + var enddate = end + + @SerializedName("type") + var type = type + + @SerializedName("duration") + var duration = duration +} \ No newline at end of file diff --git a/android/app/src/main/java/de/hft/geotracker/retrofit/ValuesRecordsArray.kt b/android/app/src/main/java/de/hft/geotracker/retrofit/ValuesRecordsArray.kt new file mode 100644 index 0000000..2339bc4 --- /dev/null +++ b/android/app/src/main/java/de/hft/geotracker/retrofit/ValuesRecordsArray.kt @@ -0,0 +1,8 @@ +package de.hft.geotracker.retrofit + +import com.google.gson.annotations.SerializedName + +class ValuesRecordsArray(entries: Array) { + @SerializedName("records") + var entries = entries +} \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_home.xml b/android/app/src/main/res/layout/activity_home.xml index f267e14..933c2d7 100644 --- a/android/app/src/main/res/layout/activity_home.xml +++ b/android/app/src/main/res/layout/activity_home.xml @@ -68,23 +68,20 @@ app:layout_constraintTop_toBottomOf="@+id/divider2" app:layout_constraintVertical_bias="0.0" /> - @@ -99,8 +96,7 @@ android:foregroundGravity="right|center_horizontal" android:textAlignment="textEnd" app:layout_constraintBottom_toBottomOf="@+id/selected_acc" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@+id/selected_acc" /> + app:layout_constraintEnd_toEndOf="parent" /> - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/text_today" /> - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_login.xml b/android/app/src/main/res/layout/activity_login.xml index d496f1a..ca2e95f 100644 --- a/android/app/src/main/res/layout/activity_login.xml +++ b/android/app/src/main/res/layout/activity_login.xml @@ -63,7 +63,7 @@ android:textColorHint="@color/logo_white" app:boxBackgroundColor="@color/common_google_signin_btn_text_dark_disabled" app:boxBackgroundMode="outline" - app:layout_constraintBottom_toTopOf="@+id/button_create_account" + app:layout_constraintBottom_toTopOf="@+id/button_login" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" @@ -81,7 +81,7 @@