diff --git a/app/build.gradle b/app/build.gradle index 53c125c..5928442 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-parcelize' + id 'kotlin-kapt' } android { @@ -37,15 +38,23 @@ android { } dependencies { + def room_version = "2.4.0" - implementation 'androidx.core:core-ktx:1.7.0' - implementation 'androidx.appcompat:appcompat:1.4.0' + implementation "androidx.room:room-runtime:$room_version" + kapt "androidx.room:room-compiler:$room_version" + + implementation 'androidx.core:core-ktx:1.6.0' + implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'androidx.constraintlayout:constraintlayout:2.1.0' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' - implementation "androidx.media:media:1.2.0" - testImplementation 'junit:junit:4.+' + + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + + implementation('androidx.fragment:fragment-ktx:1.4.0-alpha02') + implementation 'com.google.android.gms:play-services-location:18.0.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 69ebd0a..1718214 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + diff --git a/app/src/main/java/com/itis/androidlabproject/App.kt b/app/src/main/java/com/itis/androidlabproject/App.kt new file mode 100644 index 0000000..79fa856 --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/App.kt @@ -0,0 +1,33 @@ +package com.itis.androidlabproject + +import android.app.Application +import androidx.room.Room +import com.itis.androidlabproject.data.AppDatabase +import com.itis.androidlabproject.data.dao.TaskDao + +class App : Application() { + lateinit var database: AppDatabase + lateinit var taskDao: TaskDao + + companion object { + private lateinit var instance: App + + fun getInstance(): App { + return instance + } + } + + override fun onCreate() { + super.onCreate() + instance = this + database = Room.databaseBuilder( + applicationContext, + AppDatabase::class.java, + "app-db" + ).run { + allowMainThreadQueries() + build() + } + taskDao = database.taskDao() + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/MainActivity.kt b/app/src/main/java/com/itis/androidlabproject/MainActivity.kt deleted file mode 100644 index d85e6eb..0000000 --- a/app/src/main/java/com/itis/androidlabproject/MainActivity.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.itis.androidlabproject - -import androidx.appcompat.app.AppCompatActivity -import android.os.Bundle - -class MainActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/itis/androidlabproject/adapter/TaskAdapter.kt b/app/src/main/java/com/itis/androidlabproject/adapter/TaskAdapter.kt new file mode 100644 index 0000000..70acb05 --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/adapter/TaskAdapter.kt @@ -0,0 +1,46 @@ +package com.itis.androidlabproject.adapter + +import android.os.Bundle +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.itis.androidlabproject.callbacks.TaskDiffItemCallback +import com.itis.androidlabproject.models.Task +import java.util.ArrayList + +class TaskAdapter( + private val actionChoose: (Int) -> (Unit), + private val actionDelete: (Int) -> (Unit) +) : ListAdapter(TaskDiffItemCallback()) { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): TaskHolder = TaskHolder.create(parent, actionChoose, actionDelete) + + override fun onBindViewHolder( + holder: TaskHolder, + position: Int) = holder.bind(getItem(position)) + + override fun onBindViewHolder( + holder: TaskHolder, + position: Int, + payloads: MutableList + ) { + if (payloads.isEmpty()) { + super.onBindViewHolder(holder, position, payloads) + } else { + payloads.last().takeIf { + it is Bundle + }?.let { + holder.updateFields(it as Bundle) + } + } + } + + override fun submitList(list: MutableList?) { + super.submitList( + if (list == null) null + else ArrayList(list) + ) + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/adapter/TaskHolder.kt b/app/src/main/java/com/itis/androidlabproject/adapter/TaskHolder.kt new file mode 100644 index 0000000..b5af9f2 --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/adapter/TaskHolder.kt @@ -0,0 +1,67 @@ +package com.itis.androidlabproject.adapter + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.itis.androidlabproject.databinding.TaskListItemBinding +import com.itis.androidlabproject.models.DateToStringConverter.convertDateToString +import com.itis.androidlabproject.models.Task + +class TaskHolder( + private val binding: TaskListItemBinding, + private val actionChoose: (Int) -> (Unit), + private val actionDelete: (Int) -> (Unit) +) : RecyclerView.ViewHolder(binding.root) { + private var task: Task? = null + + fun bind(item: Task) { + this.task = item + with(binding) { + tvTaskTitle.text = item.title + tvTaskDate.text = convertDateToString(item.date) + + itemView.setOnClickListener { + actionChoose(item.id) + } + ivDelete.setOnClickListener { + actionDelete(item.id) + } + } + } + + fun updateFields(bundle: Bundle?) { + bundle?.run { + getString("TITLE")?.also { + updateTitle(it) + } + getString("DATE")?.also { + updateDate(it) + } + } + } + + private fun updateTitle(title: String) { + binding.tvTaskTitle.text = title + } + + private fun updateDate(date: String) { + binding.tvTaskDate.text = date + } + + companion object { + fun create( + parent: ViewGroup, + actionChoose: (Int) -> Unit, + actionDelete: (Int) -> Unit + ) = TaskHolder( + TaskListItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ), + actionChoose, + actionDelete + ) + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/callbacks/TaskDiffItemCallback.kt b/app/src/main/java/com/itis/androidlabproject/callbacks/TaskDiffItemCallback.kt new file mode 100644 index 0000000..87a20f5 --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/callbacks/TaskDiffItemCallback.kt @@ -0,0 +1,57 @@ +package com.itis.androidlabproject.callbacks + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.core.os.bundleOf +import androidx.recyclerview.widget.DiffUtil +import com.itis.androidlabproject.models.DateToStringConverter +import com.itis.androidlabproject.models.Task + +class TaskDiffItemCallback : DiffUtil.ItemCallback(){ + override fun areItemsTheSame( + oldItem: Task, + newItem: Task + ): Boolean = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: Task, + newItem: Task + ): Boolean { + return oldItem.equals(newItem) + } + + override fun getChangePayload( + oldItem: Task, + newItem: Task + ): Any? { + val bundle = Bundle().apply { + if (oldItem.title != newItem.title) { + putString("TITLE", newItem.title) + } + + if (oldItem.description != newItem.description) { + putString("DESCRIPTION", newItem.description) + } + + if (oldItem.date != newItem.date) { + putString("DATE", DateToStringConverter.convertDateToString(newItem.date)) + } + + if (oldItem.latitude != newItem.latitude) { + newItem.latitude?.let { + putDouble("LATITUDE", it) + } + } + + if (oldItem.longitude != newItem.longitude) { + newItem.longitude?.let { + putDouble("LONGITUDE", it) + } + } + } + + + if (bundle.isEmpty) return null + return bundle + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/data/AppDatabase.kt b/app/src/main/java/com/itis/androidlabproject/data/AppDatabase.kt new file mode 100644 index 0000000..add815f --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/data/AppDatabase.kt @@ -0,0 +1,44 @@ +package com.itis.androidlabproject.data + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import com.itis.androidlabproject.data.dao.TaskDao +import com.itis.androidlabproject.models.DateConverter +import com.itis.androidlabproject.models.Task + +@Database( + entities = [Task::class], + version = 1, + exportSchema = false +) +@TypeConverters(DateConverter::class) +abstract class AppDatabase : RoomDatabase() { + abstract fun taskDao(): TaskDao + + companion object { + private const val DB_NAME = "task.db" + + @Volatile + private var instance: AppDatabase? = null + private val LOCK = Any() + + operator fun invoke(context: Context?) = Companion.instance ?: synchronized(LOCK) { + buildDatabase(context) + } + + private fun buildDatabase(context: Context) { + instance = Room.databaseBuilder( + context, + AppDatabase::class.java, + DB_NAME + ).run { + allowMainThreadQueries() + fallbackToDestructiveMigration() + build() + } + } + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/data/dao/TaskDao.kt b/app/src/main/java/com/itis/androidlabproject/data/dao/TaskDao.kt new file mode 100644 index 0000000..3c1934a --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/data/dao/TaskDao.kt @@ -0,0 +1,40 @@ +package com.itis.androidlabproject.data.dao + +import androidx.lifecycle.LiveData +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import com.itis.androidlabproject.models.Task + +interface TaskDao { + @Query("SELECT * FROM Task") + fun getAll(): List + + @Query("SELECT * FROM Task") + fun getAllLiveData(): LiveData> + + @Query("SELECT * FROM Task WHERE id IN (:todoIds)") + fun loadAllByIds(todoIds: IntArray): List + + @Query("SELECT * FROM Task WHERE title = :title LIMIT 1") + fun findByName(title: String): Task + + @Query("SELECT * FROM Task WHERE id = :id LIMIT 1") + fun findById(id: Int): Task + + @Insert + fun insert(task: Task) + + @Update + fun update(task: Task) + + @Delete + fun delete(task: Task) + + @Query("DELETE FROM Task") + fun deleteAllTasks() + + @Query("DELETE FROM task WHERE id=:id") + fun deleteTaskById(id: Int) +} diff --git a/app/src/main/java/com/itis/androidlabproject/extentions/ActivityExt.kt b/app/src/main/java/com/itis/androidlabproject/extentions/ActivityExt.kt new file mode 100644 index 0000000..86b200e --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/extentions/ActivityExt.kt @@ -0,0 +1,9 @@ +package com.itis.androidlabproject.extentions + +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment + +fun AppCompatActivity.findController (id: Int) : NavController { + return (supportFragmentManager.findFragmentById(id) as NavHostFragment).navController +} diff --git a/app/src/main/java/com/itis/androidlabproject/fragments/TaskDetailsFragment.kt b/app/src/main/java/com/itis/androidlabproject/fragments/TaskDetailsFragment.kt new file mode 100644 index 0000000..9beaec5 --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/fragments/TaskDetailsFragment.kt @@ -0,0 +1,274 @@ +package com.itis.androidlabproject.fragments + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.location.LocationManager +import android.os.Bundle +import android.provider.Settings +import android.view.MenuItem +import android.view.View +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices +import com.google.android.material.snackbar.Snackbar +import com.itis.androidlabproject.R +import com.itis.androidlabproject.data.AppDatabase +import com.itis.androidlabproject.databinding.FragmentDetailsTaskBinding +import com.itis.androidlabproject.fragments.date_picker.DatePickerFragment +import com.itis.androidlabproject.models.DateToStringConverter +import com.itis.androidlabproject.models.Task +import com.itis.androidlabproject.view.MainActivity +import java.util.* + +class TaskDetailsFragment : Fragment(R.layout.fragment_details_task) { + private var binding: FragmentDetailsTaskBinding? = null + private var database: AppDatabase? = null + private var client: FusedLocationProviderClient? = null + private var calendar: Calendar? = null + private var currentTaskId: Int? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + client = LocationServices.getFusedLocationProviderClient(activity) + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + binding = FragmentDetailsTaskBinding.bind(view) + database = AppDatabase.invoke(context) as AppDatabase + + binding?.apply { + toolBar.apply { + setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) + setNavigationOnClickListener { + (activity as? MainActivity)?.onBackPressed() + } + setOnMenuItemClickListener { onOptionsItemSelected(it) } + } + + btnChooseDate.setOnClickListener { + showDatePicker() + } + } + + checkIfTaskExists() + setLocation() + } + + private fun setLocation() { + if (checkPermissions() == true) { + getCurrentLocation() + } else { + requestPermissions( + arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ), REQUEST_CODE + ) + } + } + + private fun checkPermissions(): Boolean? { + activity?.apply { + return (ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) + == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + == PackageManager.PERMISSION_GRANTED) + } + return null + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + + if (requestCode == REQUEST_CODE && grantResults.isNotEmpty() && + grantResults[0] == PackageManager.PERMISSION_GRANTED + ) { + getCurrentLocation() + } else { + returnToTaskListFragment() + } + } + + @SuppressLint("MissingPermission") + private fun getCurrentLocation() { + val locationManager = + activity?.getSystemService(Context.LOCATION_SERVICE) as LocationManager + if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) or + locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) + ) { + if (checkPermissions() == true) { + client?.lastLocation?.addOnCompleteListener { + val location = it.result + if (location != null) { + binding?.etLongitude?.setText(location.longitude.toString()) + binding?.etLatitude?.setText(location.latitude.toString()) + } + } + } + } else { + startActivity( + Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.icon_save -> { + saveTask() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + private fun checkIfTaskExists() { + arguments?.getInt("ARG_TASK_ID")?.let { + currentTaskId = it + setTaskEditingView(it) + } + } + + private fun setTaskEditingView(id: Int) { + val task = database?.taskDao()?.findById(id) + binding?.apply { + etTitle.setText(task?.title) + etDesc.setText(task?.description) + task?.date?.let { + calendar = Calendar.getInstance() + calendar?.time = it + tvDate.text = DateToStringConverter.convertDateToString(it) + tvDate.visibility = View.VISIBLE + } + } + } + + private fun showDatePicker() { + calendar = Calendar.getInstance() + val datePickerFragment = DatePickerFragment() + val supportFragmentManager = requireActivity().supportFragmentManager + + supportFragmentManager.setFragmentResultListener( + "REQUEST_KEY", + viewLifecycleOwner + ) { resultKey, bundle -> + if (resultKey == "REQUEST_KEY") { + calendar?.timeInMillis = bundle.getLong("SELECTED_DATE") + setDate(calendar) + } + } + datePickerFragment.show(supportFragmentManager, "DatePickerDialog") + } + + private fun setDate(calendar: Calendar?) { + binding?.apply { + tvDate.text = DateToStringConverter.convertDateToString(calendar?.time) + tvDate.visibility = View.VISIBLE + } + } + + private fun saveTask() { + if (currentTaskId == null && isTaskCorrect()) { + addTask() + } else { + currentTaskId?.let { + updateTask(it) + } + } + returnToTaskListFragment() + } + + private fun addTask() { + binding?.apply { + database?.taskDao()?.insert( + Task( + null, + etTitle.text.toString(), + etDesc.text.toString(), + calendar?.time, + etLongitude.text as? Double, + etLatitude.text as? Double + ) + ) + } + showMessage("Задача сохранена") + returnToTaskListFragment() + } + + private fun updateTask(id: Int) { + val task = database?.taskDao()?.findById(id) + binding?.apply { + if (isTaskCorrect()) { + binding?.run { + task?.let { task -> + task.title = etTitle.text.toString() + task.description = etDesc.text.toString() + calendar?.also { + task.date = it.time + } + database?.taskDao()?.update(task) + showMessage("Задача обновлена") + returnToTaskListFragment() + } + } + } + } + returnToTaskListFragment() + } + + private fun isTaskCorrect(): Boolean { + binding?.run { + if (etTitle.text.toString().isEmpty()) { + showMessage("Нет названия") + return false + } + if (etDesc.text.toString().isEmpty()) { + showMessage("Нет описания") + return false + } + } + return true + } + + + private fun showMessage(message: String) { + Snackbar.make( + requireActivity().findViewById(R.id.container), + message, + Snackbar.LENGTH_LONG + ).show() + } + + private fun returnToTaskListFragment() { + (activity as? MainActivity)?.onBackPressed() + } + + override fun onDestroy() { + super.onDestroy() + binding = null + database = null + + } + + companion object { + private const val REQUEST_CODE = 1 + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/fragments/TaskListFragment.kt b/app/src/main/java/com/itis/androidlabproject/fragments/TaskListFragment.kt new file mode 100644 index 0000000..a18146b --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/fragments/TaskListFragment.kt @@ -0,0 +1,125 @@ +package com.itis.androidlabproject.fragments + +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import androidx.fragment.app.Fragment +import androidx.navigation.NavOptions +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.snackbar.Snackbar +import com.itis.androidlabproject.R +import com.itis.androidlabproject.adapter.TaskAdapter +import com.itis.androidlabproject.data.AppDatabase +import com.itis.androidlabproject.databinding.FragmentTaskListBinding +import com.itis.androidlabproject.item_decorator.SpaceItemDecorator +import com.itis.androidlabproject.models.Task + +class TaskListFragment : Fragment(R.layout.fragment_task_list) { + private var binding: FragmentTaskListBinding? = null + private var database: AppDatabase? = null + private var taskAdapter: TaskAdapter? = null + private var tasks: List? = null + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentTaskListBinding.bind(view) + database = AppDatabase.invoke(context) as AppDatabase + taskAdapter = TaskAdapter({showTaskFragment(it)}, {deleteTask(it)}) + + binding?.apply { + toolBar.setOnMenuItemClickListener { + onOptionsItemSelected(it) + } + fabAdd.setOnClickListener { + showTaskFragment(null) + } + rvTasks.run { + adapter = taskAdapter + addItemDecoration( + DividerItemDecoration(context, RecyclerView.VERTICAL) + ) + addItemDecoration( + SpaceItemDecorator(context) + ) + } + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.icon_delete_all -> { + deleteAllTasks() + true + } + else ->super.onOptionsItemSelected(item) + } + } + + private fun deleteAllTasks() { + database?.taskDao()?.deleteAllTasks() + updateTasks() + showMessage("Задачи удалены") + } + + private fun deleteTask(id: Int) { + database?.taskDao()?.deleteTaskById(id) + updateTasks() + showMessage("Задача удалена") + } + + private fun updateTasks() { + tasks = database?.taskDao()?.getAll() + binding?.apply { + if (tasks.isNullOrEmpty()) { + rvTasks.visibility = View.GONE + noTasksAdded.visibility = View.VISIBLE + } else { + rvTasks.visibility = View.VISIBLE + noTasksAdded.visibility = View.GONE + } + } + taskAdapter?.submitList(ArrayList(tasks)) + } + + private fun showTaskFragment(id: Int?) { + var bundle: Bundle? = null + id?.also { + bundle = Bundle().apply { + putInt("ARG_TASK_ID", id) + } + } + val options = NavOptions.Builder() + .setLaunchSingleTop(true) + .setEnterAnim(R.anim.enter_from_right) + .setExitAnim(R.anim.exit_to_left) + .setPopEnterAnim(R.anim.enter_from_left) + .setPopExitAnim(R.anim.exit_to_right) + .build() + + findNavController().navigate( + R.id.action_taskListFragment_to_taskDetailsFragment, + bundle, + options + ) + } + + private fun showMessage(message: String) { + Snackbar.make( + requireActivity().findViewById(R.id.container), + message, + Snackbar.LENGTH_LONG + ).show() + } + + override fun onDestroy() { + super.onDestroy() + binding = null + database = null + taskAdapter = null + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/fragments/date_picker/DatePickerFragment.kt b/app/src/main/java/com/itis/androidlabproject/fragments/date_picker/DatePickerFragment.kt new file mode 100644 index 0000000..d96b647 --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/fragments/date_picker/DatePickerFragment.kt @@ -0,0 +1,35 @@ +package com.itis.androidlabproject.fragments.date_picker + +import android.app.DatePickerDialog +import android.app.Dialog +import android.os.Bundle +import android.widget.DatePicker +import androidx.core.os.bundleOf +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.setFragmentResult +import androidx.fragment.app.Fragment +import java.util.* + +class DatePickerFragment : DialogFragment(), DatePickerDialog.OnDateSetListener { + private val calendar = Calendar.getInstance() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + var year = calendar.get(Calendar.YEAR) + var month = calendar.get(Calendar.MONTH) + var dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH) + + return DatePickerDialog(requireActivity(), this, year, month, dayOfMonth) + } + + override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) { + calendar.set(Calendar.YEAR, year) + calendar.set(Calendar.MONTH, month) + calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth) + + val bundle = bundleOf( + "SELECTED_DATE" to calendar.timeInMillis + ) + + this.setFragmentResult("REQUEST_KEY", bundle) + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/item_decorator/SpaceItemDecorator.kt b/app/src/main/java/com/itis/androidlabproject/item_decorator/SpaceItemDecorator.kt new file mode 100644 index 0000000..aeae2a9 --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/item_decorator/SpaceItemDecorator.kt @@ -0,0 +1,51 @@ +package com.itis.androidlabproject.item_decorator + +import android.content.Context +import android.graphics.Rect +import android.util.TypedValue +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +class SpaceItemDecorator( + context: Context, + spacing: Float = 16f +) : RecyclerView.ItemDecoration() { + + private val spacingPx: Int = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + spacing, + context.resources.displayMetrics + ).toInt() + + override fun getItemOffsets( + outRect: Rect, + view: View, + parent: RecyclerView, + state: RecyclerView.State + ) { + val spacingMiddle = (spacingPx * 0.25).toInt() + val spacingTopBottom = (spacingPx * 0.5).toInt() + val viewHolder = parent.getChildViewHolder(view) + + val currentPosition = parent.getChildAdapterPosition(view).takeIf { + it != RecyclerView.NO_POSITION + } ?: viewHolder.oldPosition + + when (currentPosition) { + 0 -> { + outRect.top = spacingTopBottom + outRect.bottom = spacingMiddle + } + state.itemCount - 1 -> { + outRect.top = spacingMiddle + outRect.bottom = spacingTopBottom + } + else -> { + outRect.top = spacingMiddle + outRect.bottom = spacingMiddle + } + } + outRect.left = spacingPx + outRect.right = spacingPx + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/models/DateConverter.kt b/app/src/main/java/com/itis/androidlabproject/models/DateConverter.kt new file mode 100644 index 0000000..f6f6441 --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/models/DateConverter.kt @@ -0,0 +1,18 @@ +package com.itis.androidlabproject.models + +import androidx.room.TypeConverter +import java.util.* + +class DateConverter { + @TypeConverter + fun dateToTimestamp(date: Date?): Long? { + return date?.time?.toLong() + } + + @TypeConverter + fun timestampToDate(timestamp: Long?): Date? { + return timestamp?.let { + Date(it) + } + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/models/DateToStringConverter.kt b/app/src/main/java/com/itis/androidlabproject/models/DateToStringConverter.kt new file mode 100644 index 0000000..7270d9a --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/models/DateToStringConverter.kt @@ -0,0 +1,14 @@ +package com.itis.androidlabproject.models + +import java.text.SimpleDateFormat +import java.util.* + +object DateToStringConverter { + + fun convertDateToString(date: Date?): String{ + val dateFormat = SimpleDateFormat("EEE, MMM d, yyyy", Locale.getDefault()) + return date.let { + dateFormat.format(it) + } ?: "Дата не выбрана" + } +} diff --git a/app/src/main/java/com/itis/androidlabproject/models/Task.kt b/app/src/main/java/com/itis/androidlabproject/models/Task.kt new file mode 100644 index 0000000..432dffe --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/models/Task.kt @@ -0,0 +1,27 @@ +package com.itis.androidlabproject.models + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import java.util.* + +@Entity(tableName = "task") +class Task( + @PrimaryKey(autoGenerate = true) + var id: Int?, + + @ColumnInfo(name = "title") + var title: String?, + + @ColumnInfo(name = "description") + var description: String?, + + @ColumnInfo(name = "date") + var date: Date?, + + @ColumnInfo(name = "longitude") + var longitude: Double?, + + @ColumnInfo(name = "latitude") + var latitude: Double? +) diff --git a/app/src/main/java/com/itis/androidlabproject/view/MainActivity.kt b/app/src/main/java/com/itis/androidlabproject/view/MainActivity.kt new file mode 100644 index 0000000..757a7ae --- /dev/null +++ b/app/src/main/java/com/itis/androidlabproject/view/MainActivity.kt @@ -0,0 +1,30 @@ +package com.itis.androidlabproject.view + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.NavController +import androidx.navigation.findNavController +import com.itis.androidlabproject.databinding.ActivityMainBinding + +class MainActivity : AppCompatActivity() { + private var controller: NavController? = null + private var binding: ActivityMainBinding? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater).also { + setContentView(binding?.root) + } + controller = binding?.container?.id?.let { findNavController(it) } + } + + override fun onBackPressed() { + supportFragmentManager.run { + if (backStackEntryCount == 0) { + super.onBackPressed() + } else { + popBackStack() + } + } + } +} diff --git a/app/src/main/res/anim/enter_from_left.xml b/app/src/main/res/anim/enter_from_left.xml new file mode 100644 index 0000000..0826b75 --- /dev/null +++ b/app/src/main/res/anim/enter_from_left.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/anim/enter_from_right.xml b/app/src/main/res/anim/enter_from_right.xml new file mode 100644 index 0000000..d424f42 --- /dev/null +++ b/app/src/main/res/anim/enter_from_right.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/anim/exit_to_left.xml b/app/src/main/res/anim/exit_to_left.xml new file mode 100644 index 0000000..9c2ad05 --- /dev/null +++ b/app/src/main/res/anim/exit_to_left.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/anim/exit_to_right.xml b/app/src/main/res/anim/exit_to_right.xml new file mode 100644 index 0000000..7becffd --- /dev/null +++ b/app/src/main/res/anim/exit_to_right.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/drawable-v24/done.xml b/app/src/main/res/drawable-v24/done.xml new file mode 100644 index 0000000..f5c43ba --- /dev/null +++ b/app/src/main/res/drawable-v24/done.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/delete.xml b/app/src/main/res/drawable/delete.xml new file mode 100644 index 0000000..3c4030b --- /dev/null +++ b/app/src/main/res/drawable/delete.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml new file mode 100644 index 0000000..2a31b2e --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_arrow_back_24.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 4fa45b0..eae5a44 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,15 +4,19 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".MainActivity"> + android:orientation="vertical" + tools:context=".view.MainActivity"> - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:navGraph="@navigation/nav_graph" /> diff --git a/app/src/main/res/layout/fragment_details_task.xml b/app/src/main/res/layout/fragment_details_task.xml new file mode 100644 index 0000000..388540f --- /dev/null +++ b/app/src/main/res/layout/fragment_details_task.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + +