/*
* Copyright (c) 2023 Samson Achiaga
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.certified.audionote.ui
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.media.MediaPlayer
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.DatePicker
import android.widget.TimePicker
import androidx.core.content.FileProvider
import androidx.core.content.res.ResourcesCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.navigation.fragment.navArgs
import com.certified.audionote.R
import com.certified.audionote.databinding.DialogEditReminderBinding
import com.certified.audionote.databinding.FragmentEditNoteBinding
import com.certified.audionote.model.Note
import com.certified.audionote.utils.Extensions.safeNavigate
import com.certified.audionote.utils.Extensions.showToast
import com.certified.audionote.utils.ReminderAvailableState
import com.certified.audionote.utils.ReminderCompletionState
import com.certified.audionote.utils.cancelAlarm
import com.certified.audionote.utils.currentDate
import com.certified.audionote.utils.formatReminderDate
import com.certified.audionote.utils.startAlarm
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timerx.*
import java.io.File
import java.io.IOException
import java.util.Calendar
import java.util.concurrent.TimeUnit
@AndroidEntryPoint
class EditNoteFragment : Fragment(), DatePickerDialog.OnDateSetListener,
TimePickerDialog.OnTimeSetListener {
private var _binding: FragmentEditNoteBinding? = null
private val binding get() = _binding!!
private val viewModel: NotesViewModel by viewModels()
private lateinit var navController: NavController
private val args: EditNoteFragmentArgs by navArgs()
private lateinit var _note: Note
private var isPlayingRecord = false
private var pickedDateTime: Calendar? = null
private val currentDateTime by lazy { currentDate() }
private var mediaPlayer: MediaPlayer? = null
private var file: File? = null
private var stopWatch: Stopwatch? = null
private var timer: Timer? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentEditNoteBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
navController = Navigation.findNavController(view)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
args.note.let {
_note = it
binding.note = it
}
binding.btnBack.setOnClickListener {
navController.safeNavigate(EditNoteFragmentDirections.actionEditNoteFragmentToHomeFragment())
}
binding.cardAddReminder.setOnClickListener {
if (viewModel.reminderAvailableState.value == ReminderAvailableState.NO_REMINDER)
pickDate()
else
openEditReminderDialog()
}
binding.btnShare.setOnClickListener { shareNote() }
binding.btnDelete.setOnClickListener { launchDeleteNoteDialog(requireContext()) }
binding.btnRecord.setOnClickListener { playPauseRecord() }
binding.fabUpdateNote.setOnClickListener { updateNote() }
setup()
}
private fun setup() {
lifecycleScope.launch {
file = File(_note.filePath)
Log.d("TAG", "onViewCreated: ${file!!.name}")
}
viewModel.apply {
if (_note.reminder != null) {
_reminderAvailableState.value = ReminderAvailableState.HAS_REMINDER
if (currentDate().timeInMillis > args.note.reminder!!) {
_reminderCompletionState.value = ReminderCompletionState.COMPLETED
} else {
_reminderCompletionState.value = ReminderCompletionState.ONGOING
}
}
}
}
override fun onResume() {
super.onResume()
updateStatusBarColor(args.note.color)
}
override fun onDestroyView() {
super.onDestroyView()
if (isPlayingRecord)
mediaPlayer?.apply {
stop()
release()
}
mediaPlayer = null
timer?.apply {
stop()
reset()
}
timer = null
stopWatch?.apply {
stop()
reset()
}
stopWatch = null
_binding = null
}
private fun playPauseRecord() {
binding.apply {
if (!isPlayingRecord)
btnRecord.setImageDrawable(
ResourcesCompat.getDrawable(
resources,
R.drawable.ic_audio_playing, null
)
)
.run {
if (timer == null)
startPlayingRecording()
else
continuePlayingRecording()
isPlayingRecord = true
}
else
btnRecord.setImageDrawable(
ResourcesCompat.getDrawable(
resources,
R.drawable.ic_audio_not_playing, null
)
)
.run {
if ((timer?.remainingTimeInMillis?.div(1000)) != 0L)
pausePlayingRecording()
else
stopPlayingRecording()
isPlayingRecord = false
}
}
}
private fun updateNote() {
val note = _note.copy(
title = binding.etNoteTitle.text.toString().trim(),
description = binding.etNoteDescription.text.toString().trim(),
lastModificationDate = currentDate().timeInMillis
)
if (note.title.isNotBlank()) {
viewModel.updateNote(note)
if (pickedDateTime?.timeInMillis != null && pickedDateTime?.timeInMillis != currentDateTime.timeInMillis)
startAlarm(requireContext(), pickedDateTime!!.timeInMillis, note)
navController.safeNavigate(EditNoteFragmentDirections.actionEditNoteFragmentToHomeFragment())
} else {
showToast(requireContext().getString(R.string.title_required))
binding.etNoteTitle.requestFocus()
}
}
override fun onDateSet(
p0: DatePicker?,
p1: Int,
p2: Int,
p3: Int
) {
pickedDateTime = currentDate()
pickedDateTime!!.set(p1, p2, p3)
val hourOfDay = currentDateTime.get(Calendar.HOUR_OF_DAY)
val minuteOfDay = currentDateTime.get(Calendar.MINUTE)
val timePickerDialog =
TimePickerDialog(requireContext(), this, hourOfDay, minuteOfDay, false)
timePickerDialog.setOnDismissListener {
viewModel._reminderAvailableState.value = ReminderAvailableState.HAS_REMINDER
viewModel._reminderCompletionState.value = ReminderCompletionState.ONGOING
_note.reminder = pickedDateTime!!.timeInMillis
binding.tvReminderDate.text = formatReminderDate(pickedDateTime!!.timeInMillis)
}
timePickerDialog.show()
}
override fun onTimeSet(
p0: TimePicker?,
p1: Int,
p2: Int
) {
pickedDateTime!!.set(Calendar.HOUR_OF_DAY, p1)
pickedDateTime!!.set(Calendar.MINUTE, p2)
if (pickedDateTime!!.timeInMillis <= currentDate().timeInMillis) {
pickedDateTime!!.run {
set(Calendar.DAY_OF_MONTH, currentDateTime.get(Calendar.DAY_OF_MONTH) + 1)
set(Calendar.YEAR, currentDateTime.get(Calendar.YEAR))
set(Calendar.MONTH, currentDateTime.get(Calendar.MONTH))
}
}
}
private fun pickDate() {
val startYear = currentDateTime.get(Calendar.YEAR)
val startMonth = currentDateTime.get(Calendar.MONTH)
val startDay = currentDateTime.get(Calendar.DAY_OF_MONTH)
val datePickerDialog =
DatePickerDialog(requireContext(), this, startYear, startMonth, startDay)
datePickerDialog.show()
}
private fun openEditReminderDialog() {
val view = DialogEditReminderBinding.inflate(layoutInflater)
val bottomSheetDialog = BottomSheetDialog(requireContext())
view.apply {
note = _note
btnDeleteReminder.setOnClickListener {
viewModel._reminderAvailableState.value = ReminderAvailableState.NO_REMINDER
_note.reminder = null
cancelAlarm(requireContext(), _note.id)
bottomSheetDialog.dismiss()
}
btnModifyReminder.setOnClickListener {
bottomSheetDialog.dismiss()
pickDate()
}
}
bottomSheetDialog.edgeToEdgeEnabled
bottomSheetDialog.setContentView(view.root)
bottomSheetDialog.show()
}
private fun launchDeleteNoteDialog(context: Context) {
val materialDialog = MaterialAlertDialogBuilder(context)
materialDialog.apply {
setTitle(context.getString(R.string.delete_note))
setMessage("${context.getString(R.string.confirm_deletion)} ${_note.title}?")
setNegativeButton(context.getString(R.string.no)) { dialog, _ -> dialog?.dismiss() }
setPositiveButton(context.getString(R.string.yes)) { _, _ ->
viewModel.deleteNote(_note)
lifecycleScope.launch(Dispatchers.IO) { file?.delete() }
navController.safeNavigate(EditNoteFragmentDirections.actionEditNoteFragmentToHomeFragment())
}
show()
}
}
private fun startPlayingRecording() {
timer = buildTimer {
startTime(_note.audioLength, TimeUnit.SECONDS)
startFormat(if (_note.audioLength >= 3600000L) "HH:MM:SS" else "MM:SS")
onTick { millis, time -> binding.tvTimer.text = time }
actionWhen(0, TimeUnit.SECONDS) {
binding.btnRecord.setImageDrawable(
ResourcesCompat.getDrawable(
resources,
R.drawable.ic_audio_not_playing,
null
)
).run {
isPlayingRecord = false
stopPlayingRecording()
}
}
}
mediaPlayer = MediaPlayer()
try {
mediaPlayer?.apply {
setDataSource(file?.absolutePath)
prepare()
start()
}
timer!!.start()
} catch (e: IOException) {
e.printStackTrace()
Log.d("TAG", "startPlayingRecording: ${e.localizedMessage}")
showToast(requireContext().getString(R.string.error_occurred))
}
}
private fun pausePlayingRecording() {
mediaPlayer?.pause()
timer?.stop()
}
private fun continuePlayingRecording() {
mediaPlayer?.start()
timer?.start()
}
private fun stopPlayingRecording() {
mediaPlayer?.apply {
stop()
release()
}
timer?.apply {
reset()
stop()
}
timer = null
}
private fun shareNote() {
if (file == null) {
showToast(requireContext().getString(R.string.file_not_found))
return
}
try {
val uri = FileProvider.getUriForFile(
requireContext(),
"com.certified.audionote.provider",
file!!
)
Intent(Intent.ACTION_SEND).apply {
type = "*/*"
putExtra(Intent.EXTRA_STREAM, uri)
startActivity(Intent.createChooser(this, "Share using"))
}
} catch (t: Throwable) {
showToast(requireContext().getString(R.string.error_occurred))
Log.d("TAG", "shareNote: ${t.localizedMessage}")
}
}
private fun updateStatusBarColor(color: Int) {
val window = requireActivity().window
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
// window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.statusBarColor = color
}
}