/* * 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 } }