Skip to content

Commit

Permalink
Add location field to note editor and display in note details
Browse files Browse the repository at this point in the history
  • Loading branch information
profiluefter committed May 23, 2021
1 parent 598e3db commit a7e8a6f
Show file tree
Hide file tree
Showing 36 changed files with 210 additions and 61 deletions.
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.profiluefter.profinote">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

<application
android:name=".NotianApplication"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package me.profiluefter.profinote.activities

import android.Manifest
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.pm.PackageManager
import android.graphics.Color
import android.location.Criteria
import android.location.LocationListener
import android.location.LocationManager
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.getSystemService
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
Expand All @@ -15,41 +23,42 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.transition.Slide
import com.google.android.material.transition.MaterialContainerTransform
import dagger.hilt.android.AndroidEntryPoint
import me.profiluefter.profinote.R
import me.profiluefter.profinote.databinding.FragmentNoteEditorBinding
import me.profiluefter.profinote.models.MainViewModel
import me.profiluefter.profinote.models.NoteEditorViewModel
import me.profiluefter.profinote.models.NoteEditorViewModelFactory
import me.profiluefter.profinote.themeColor
import java.util.*

@AndroidEntryPoint
class NoteEditorFragment : Fragment() {
private val args by navArgs<NoteEditorFragmentArgs>()

private val viewModel: MainViewModel by activityViewModels()
private val editor: NoteEditorViewModel by viewModels {
NoteEditorViewModelFactory(
args.note
)
}
private val editor: NoteEditorViewModel by viewModels()

private val gpsRequestCode = 1337

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = DataBindingUtil.inflate<FragmentNoteEditorBinding>(
inflater,
R.layout.fragment_note_editor,
container,
false
inflater,
R.layout.fragment_note_editor,
container,
false
).apply {
editor.setInitialNote(args.note)

lifecycleOwner = this@NoteEditorFragment
fragment = this@NoteEditorFragment
layoutViewModel = this@NoteEditorFragment.editor
}.also { binding ->

enterTransition = MaterialContainerTransform().apply {
startView = requireActivity().findViewById(R.id.floatingActionButton)
endView = binding.root
endView = root
duration = resources.getInteger(R.integer.notian_animation_time).toLong()
scrimColor = Color.TRANSPARENT
containerColor = requireContext().themeColor(R.attr.colorSurface)
Expand All @@ -58,10 +67,50 @@ class NoteEditorFragment : Fragment() {
}
returnTransition = Slide().apply {
duration = resources.getInteger(R.integer.notian_animation_time).toLong()
addTarget(binding.root)
addTarget(root)
}

noteEditorLocationContainer.setEndIconOnClickListener {
val permission = requireContext().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)

if (permission != PackageManager.PERMISSION_GRANTED) {
requireActivity().requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
gpsRequestCode
)
} else {
searchGPS()
}
}
}.root

@Suppress("JoinDeclarationAndAssignment") // IntelliJ Bug
@SuppressLint("MissingPermission") // Only called if permission is granted
private fun searchGPS() {
val locationManager = requireContext().getSystemService<LocationManager>()!!

lateinit var locationListener: LocationListener
locationListener = LocationListener {
Log.i("NoteEditorFragment", "Received location $it")
editor.receivedGPS(it.latitude, it.longitude)
locationManager.removeUpdates(locationListener)
}

val provider = locationManager.getBestProvider(Criteria().apply {
this.isCostAllowed = false
this.accuracy = Criteria.ACCURACY_FINE
}, true)!!
Log.i("NoteEditorFragment", "Using provider $provider")
locationManager.requestLocationUpdates(provider, 0L, 0.0f, locationListener)
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode != gpsRequestCode) return

if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
searchGPS()
}

fun saveNote() {
if (editor.localID == 0)
viewModel.addNote(editor.note)
Expand All @@ -73,23 +122,23 @@ class NoteEditorFragment : Fragment() {
fun openTimePicker(view: View) {
val calendar = Calendar.getInstance()
val dialog = TimePickerDialog(
requireContext(),
{ _, hour, minute -> editor.setTime(hour, minute) },
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
false
requireContext(),
{ _, hour, minute -> editor.setTime(hour, minute) },
calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE),
false
)
dialog.show()
}

fun openDatePicker(view: View) {
val calendar = Calendar.getInstance()
val dialog = DatePickerDialog(
requireContext(),
{ _, year, month, day -> editor.setDate(day, month + 1, year) },
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
requireContext(),
{ _, year, month, day -> editor.setDate(day, month + 1, year) },
calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH),
calendar.get(Calendar.DAY_OF_MONTH)
)
dialog.show()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@ package me.profiluefter.profinote.models

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import me.profiluefter.profinote.data.GeocodingService
import me.profiluefter.profinote.data.entities.*
import javax.inject.Inject

class NoteEditorViewModelFactory(private val note: Note) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (!modelClass.isAssignableFrom(NoteEditorViewModel::class.java))
throw IllegalArgumentException("Unknown ViewModel")
return NoteEditorViewModel(note) as T
}
}

class NoteEditorViewModel(note: Note) : ViewModel() {
@HiltViewModel
class NoteEditorViewModel @Inject constructor(private val geocodingService: GeocodingService) : ViewModel() {
fun setTime(hour: Int, minute: Int) {
time.value = formatTime(hour, minute)
}
Expand All @@ -23,19 +19,40 @@ class NoteEditorViewModel(note: Note) : ViewModel() {
date.value = formatDate(day, month, year)
}

val localID: Int = note.localID
val title: MutableLiveData<String> = MutableLiveData(note.title)
val done: MutableLiveData<Boolean> = MutableLiveData(note.done)
val date: MutableLiveData<String> = MutableLiveData(note.date)
val time: MutableLiveData<String> = MutableLiveData(note.time)
val description: MutableLiveData<String> = MutableLiveData(note.description)
fun receivedGPS(latitude: Double, longitude: Double) {
viewModelScope.launch {
val location = geocodingService.reverse(latitude, longitude)
this@NoteEditorViewModel.location.value = location
this@NoteEditorViewModel.longitude.value = longitude
this@NoteEditorViewModel.latitude.value = latitude
}
}

fun setInitialNote(note: Note) {
localID = note.localID
title.value = note.title
done.value = note.done
date.value = note.date
time.value = note.time
description.value = note.description
}

var localID: Int? = null
val title: MutableLiveData<String> = MutableLiveData()
val done: MutableLiveData<Boolean> = MutableLiveData()
val date: MutableLiveData<String> = MutableLiveData()
val time: MutableLiveData<String> = MutableLiveData()
val description: MutableLiveData<String> = MutableLiveData()
val latitude: MutableLiveData<Double> = MutableLiveData()
val longitude: MutableLiveData<Double> = MutableLiveData()
val location: MutableLiveData<String> = MutableLiveData()

val note: Note
get() {
val (day, month, year) = date.value!!.split(".").map { it.toInt() }
val (hour, minute) = time.value!!.split(":").map { it.toInt() }
return Note(
localID,
localID!!,
title.value!!,
done.value!!,
minute,
Expand All @@ -44,9 +61,9 @@ class NoteEditorViewModel(note: Note) : ViewModel() {
month,
year,
description.value!!,
0.0,
0.0,
"",
latitude.value!!,
longitude.value!!,
location.value!!
)
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/baseline_my_location_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>
46 changes: 44 additions & 2 deletions app/src/main/res/layout/fragment_note_details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

<data>

<import type="android.view.View" />

<import type="me.profiluefter.profinote.data.entities.NoteKt" />

<variable
Expand Down Expand Up @@ -73,12 +75,52 @@
app:layout_constraintTop_toTopOf="@+id/note_details_date"
tools:text="@tools:sample/date/hhmm" />

<TextView
android:id="@+id/note_details_longitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@{Double.toString(note.longitude)}"
app:layout_constraintBottom_toBottomOf="@+id/note_details_date"
app:layout_constraintEnd_toStartOf="@+id/note_details_latitude"
app:layout_constraintTop_toTopOf="@+id/note_details_date"
app:layout_constraintVertical_bias="1.0"
tools:text="@tools:sample/us_phones" />

<TextView
android:id="@+id/note_details_latitude"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@{Double.toString(note.latitude)}"
app:layout_constraintBottom_toBottomOf="@+id/note_details_date"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/note_details_date"
tools:text="@tools:sample/us_phones" />

<TextView
android:id="@+id/note_details_location"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:drawableStart="@drawable/baseline_my_location_24"
android:gravity="center_vertical"
android:singleLine="true"
android:text="@{note.address}"
android:visibility="@{note.address.empty ? View.GONE : View.VISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/note_details_date"
tools:text="@tools:sample/cities" />

<TextView
android:id="@+id/note_details_description"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:scrollbars="vertical"
Expand All @@ -87,7 +129,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/note_details_date"
app:layout_constraintTop_toBottomOf="@+id/note_details_location"
tools:text="@tools:sample/lorem/random[0]" />

</androidx.constraintlayout.widget.ConstraintLayout>
Expand Down
27 changes: 25 additions & 2 deletions app/src/main/res/layout/fragment_note_editor.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,29 @@
app:layout_constraintStart_toEndOf="@+id/noteEditorDate"
app:layout_constraintTop_toBottomOf="@+id/noteEditorTitleContainer" />

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/noteEditorLocationContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
app:endIconMode="custom"
app:endIconDrawable="@drawable/baseline_my_location_24"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/noteEditorDate">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/noteEditorLocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/note_editor_location"
android:inputType="textPostalAddress"
android:text="@={layoutViewModel.location}" />
</com.google.android.material.textfield.TextInputLayout>

<com.google.android.material.textfield.TextInputLayout
android:id="@+id/noteEditorDescriptionContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
Expand All @@ -97,7 +120,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/noteEditorTime"
app:layout_constraintTop_toBottomOf="@+id/noteEditorLocationContainer"
app:layout_constraintVertical_bias="0.0">

<com.google.android.material.textfield.TextInputEditText
Expand All @@ -118,7 +141,7 @@
android:backgroundTint="?attr/colorPrimary"
android:clickable="true"
android:visibility="invisible"
fabHidden="@{layoutViewModel.title.empty || layoutViewModel.description.empty}"
fabHidden="@{layoutViewModel.title.empty || layoutViewModel.description.empty || layoutViewModel.location.empty}"
android:contentDescription="@string/note_editor_save_button"
android:focusable="true"
android:onClick="@{() -> fragment.saveNote()}"
Expand Down
Loading

0 comments on commit a7e8a6f

Please sign in to comment.