Browse code

Initial commit (losing old history)

Dario Rodriguez authored on 08/04/2026 17:25:38
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,383 @@
1
+/*
2
+ * Copyright (c) 2023 Samson Achiaga
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+package com.certified.audionote.ui
18
+
19
+import android.app.DatePickerDialog
20
+import android.app.TimePickerDialog
21
+import android.content.Context
22
+import android.content.Intent
23
+import android.media.MediaPlayer
24
+import android.os.Bundle
25
+import android.util.Log
26
+import android.view.LayoutInflater
27
+import android.view.View
28
+import android.view.ViewGroup
29
+import android.view.WindowManager
30
+import android.widget.DatePicker
31
+import android.widget.TimePicker
32
+import androidx.core.content.FileProvider
33
+import androidx.core.content.res.ResourcesCompat
34
+import androidx.fragment.app.Fragment
35
+import androidx.fragment.app.viewModels
36
+import androidx.lifecycle.lifecycleScope
37
+import androidx.navigation.NavController
38
+import androidx.navigation.Navigation
39
+import androidx.navigation.fragment.navArgs
40
+import com.certified.audionote.R
41
+import com.certified.audionote.databinding.DialogEditReminderBinding
42
+import com.certified.audionote.databinding.FragmentEditNoteBinding
43
+import com.certified.audionote.model.Note
44
+import com.certified.audionote.utils.Extensions.safeNavigate
45
+import com.certified.audionote.utils.Extensions.showToast
46
+import com.certified.audionote.utils.ReminderAvailableState
47
+import com.certified.audionote.utils.ReminderCompletionState
48
+import com.certified.audionote.utils.cancelAlarm
49
+import com.certified.audionote.utils.currentDate
50
+import com.certified.audionote.utils.formatReminderDate
51
+import com.certified.audionote.utils.startAlarm
52
+import com.google.android.material.bottomsheet.BottomSheetDialog
53
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
54
+import dagger.hilt.android.AndroidEntryPoint
55
+import kotlinx.coroutines.Dispatchers
56
+import kotlinx.coroutines.launch
57
+import timerx.*
58
+import java.io.File
59
+import java.io.IOException
60
+import java.util.Calendar
61
+import java.util.concurrent.TimeUnit
62
+
63
+@AndroidEntryPoint
64
+class EditNoteFragment : Fragment(), DatePickerDialog.OnDateSetListener,
65
+    TimePickerDialog.OnTimeSetListener {
66
+    
67
+    private var _binding: FragmentEditNoteBinding? = null
68
+    private val binding get() = _binding!!
69
+    
70
+    private val viewModel: NotesViewModel by viewModels()
71
+    private lateinit var navController: NavController
72
+    
73
+    private val args: EditNoteFragmentArgs by navArgs()
74
+    private lateinit var _note: Note
75
+    private var isPlayingRecord = false
76
+    private var pickedDateTime: Calendar? = null
77
+    private val currentDateTime by lazy { currentDate() }
78
+    private var mediaPlayer: MediaPlayer? = null
79
+    private var file: File? = null
80
+    private var stopWatch: Stopwatch? = null
81
+    private var timer: Timer? = null
82
+    
83
+    override fun onCreateView(
84
+        inflater: LayoutInflater,
85
+        container: ViewGroup?,
86
+        savedInstanceState: Bundle?
87
+    ): View {
88
+        // Inflate the layout for this fragment
89
+        _binding = FragmentEditNoteBinding.inflate(layoutInflater, container, false)
90
+        return binding.root
91
+    }
92
+    
93
+    override fun onViewCreated(
94
+        view: View,
95
+        savedInstanceState: Bundle?
96
+    ) {
97
+        super.onViewCreated(view, savedInstanceState)
98
+        
99
+        navController = Navigation.findNavController(view)
100
+        
101
+        binding.lifecycleOwner = viewLifecycleOwner
102
+        binding.viewModel = viewModel
103
+        args.note.let {
104
+            _note = it
105
+            binding.note = it
106
+        }
107
+        
108
+        binding.btnBack.setOnClickListener {
109
+            navController.safeNavigate(EditNoteFragmentDirections.actionEditNoteFragmentToHomeFragment())
110
+        }
111
+        binding.cardAddReminder.setOnClickListener {
112
+            if (viewModel.reminderAvailableState.value == ReminderAvailableState.NO_REMINDER)
113
+                pickDate()
114
+            else
115
+                openEditReminderDialog()
116
+        }
117
+        binding.btnShare.setOnClickListener { shareNote() }
118
+        binding.btnDelete.setOnClickListener { launchDeleteNoteDialog(requireContext()) }
119
+        binding.btnRecord.setOnClickListener { playPauseRecord() }
120
+        binding.fabUpdateNote.setOnClickListener { updateNote() }
121
+        
122
+        setup()
123
+    }
124
+    
125
+    private fun setup() {
126
+        lifecycleScope.launch {
127
+            file = File(_note.filePath)
128
+            Log.d("TAG", "onViewCreated: ${file!!.name}")
129
+        }
130
+        viewModel.apply {
131
+            if (_note.reminder != null) {
132
+                _reminderAvailableState.value = ReminderAvailableState.HAS_REMINDER
133
+                if (currentDate().timeInMillis > args.note.reminder!!) {
134
+                    _reminderCompletionState.value = ReminderCompletionState.COMPLETED
135
+                } else {
136
+                    _reminderCompletionState.value = ReminderCompletionState.ONGOING
137
+                }
138
+            }
139
+        }
140
+    }
141
+    
142
+    override fun onResume() {
143
+        super.onResume()
144
+        updateStatusBarColor(args.note.color)
145
+    }
146
+    
147
+    override fun onDestroyView() {
148
+        super.onDestroyView()
149
+        if (isPlayingRecord)
150
+            mediaPlayer?.apply {
151
+                stop()
152
+                release()
153
+            }
154
+        mediaPlayer = null
155
+        timer?.apply {
156
+            stop()
157
+            reset()
158
+        }
159
+        timer = null
160
+        stopWatch?.apply {
161
+            stop()
162
+            reset()
163
+        }
164
+        stopWatch = null
165
+        _binding = null
166
+    }
167
+    
168
+    private fun playPauseRecord() {
169
+        binding.apply {
170
+            if (!isPlayingRecord)
171
+                btnRecord.setImageDrawable(
172
+                    ResourcesCompat.getDrawable(
173
+                        resources,
174
+                        R.drawable.ic_audio_playing, null
175
+                    )
176
+                )
177
+                    .run {
178
+                        if (timer == null)
179
+                            startPlayingRecording()
180
+                        else
181
+                            continuePlayingRecording()
182
+                        isPlayingRecord = true
183
+                    }
184
+            else
185
+                btnRecord.setImageDrawable(
186
+                    ResourcesCompat.getDrawable(
187
+                        resources,
188
+                        R.drawable.ic_audio_not_playing, null
189
+                    )
190
+                )
191
+                    .run {
192
+                        if ((timer?.remainingTimeInMillis?.div(1000)) != 0L)
193
+                            pausePlayingRecording()
194
+                        else
195
+                            stopPlayingRecording()
196
+                        isPlayingRecord = false
197
+                    }
198
+        }
199
+    }
200
+    
201
+    private fun updateNote() {
202
+        val note = _note.copy(
203
+            title = binding.etNoteTitle.text.toString().trim(),
204
+            description = binding.etNoteDescription.text.toString().trim(),
205
+            lastModificationDate = currentDate().timeInMillis
206
+        )
207
+        if (note.title.isNotBlank()) {
208
+            viewModel.updateNote(note)
209
+            if (pickedDateTime?.timeInMillis != null && pickedDateTime?.timeInMillis != currentDateTime.timeInMillis)
210
+                startAlarm(requireContext(), pickedDateTime!!.timeInMillis, note)
211
+            navController.safeNavigate(EditNoteFragmentDirections.actionEditNoteFragmentToHomeFragment())
212
+        } else {
213
+            showToast(requireContext().getString(R.string.title_required))
214
+            binding.etNoteTitle.requestFocus()
215
+        }
216
+    }
217
+    
218
+    override fun onDateSet(
219
+        p0: DatePicker?,
220
+        p1: Int,
221
+        p2: Int,
222
+        p3: Int
223
+    ) {
224
+        pickedDateTime = currentDate()
225
+        pickedDateTime!!.set(p1, p2, p3)
226
+        val hourOfDay = currentDateTime.get(Calendar.HOUR_OF_DAY)
227
+        val minuteOfDay = currentDateTime.get(Calendar.MINUTE)
228
+        val timePickerDialog =
229
+            TimePickerDialog(requireContext(), this, hourOfDay, minuteOfDay, false)
230
+        timePickerDialog.setOnDismissListener {
231
+            viewModel._reminderAvailableState.value = ReminderAvailableState.HAS_REMINDER
232
+            viewModel._reminderCompletionState.value = ReminderCompletionState.ONGOING
233
+            _note.reminder = pickedDateTime!!.timeInMillis
234
+            binding.tvReminderDate.text = formatReminderDate(pickedDateTime!!.timeInMillis)
235
+        }
236
+        timePickerDialog.show()
237
+    }
238
+    
239
+    override fun onTimeSet(
240
+        p0: TimePicker?,
241
+        p1: Int,
242
+        p2: Int
243
+    ) {
244
+        pickedDateTime!!.set(Calendar.HOUR_OF_DAY, p1)
245
+        pickedDateTime!!.set(Calendar.MINUTE, p2)
246
+        if (pickedDateTime!!.timeInMillis <= currentDate().timeInMillis) {
247
+            pickedDateTime!!.run {
248
+                set(Calendar.DAY_OF_MONTH, currentDateTime.get(Calendar.DAY_OF_MONTH) + 1)
249
+                set(Calendar.YEAR, currentDateTime.get(Calendar.YEAR))
250
+                set(Calendar.MONTH, currentDateTime.get(Calendar.MONTH))
251
+            }
252
+        }
253
+    }
254
+    
255
+    private fun pickDate() {
256
+        val startYear = currentDateTime.get(Calendar.YEAR)
257
+        val startMonth = currentDateTime.get(Calendar.MONTH)
258
+        val startDay = currentDateTime.get(Calendar.DAY_OF_MONTH)
259
+        val datePickerDialog =
260
+            DatePickerDialog(requireContext(), this, startYear, startMonth, startDay)
261
+        datePickerDialog.show()
262
+    }
263
+    
264
+    private fun openEditReminderDialog() {
265
+        val view = DialogEditReminderBinding.inflate(layoutInflater)
266
+        val bottomSheetDialog = BottomSheetDialog(requireContext())
267
+        view.apply {
268
+            note = _note
269
+            btnDeleteReminder.setOnClickListener {
270
+                viewModel._reminderAvailableState.value = ReminderAvailableState.NO_REMINDER
271
+                _note.reminder = null
272
+                cancelAlarm(requireContext(), _note.id)
273
+                bottomSheetDialog.dismiss()
274
+            }
275
+            btnModifyReminder.setOnClickListener {
276
+                bottomSheetDialog.dismiss()
277
+                pickDate()
278
+            }
279
+        }
280
+        bottomSheetDialog.edgeToEdgeEnabled
281
+        bottomSheetDialog.setContentView(view.root)
282
+        bottomSheetDialog.show()
283
+    }
284
+    
285
+    private fun launchDeleteNoteDialog(context: Context) {
286
+        val materialDialog = MaterialAlertDialogBuilder(context)
287
+        materialDialog.apply {
288
+            setTitle(context.getString(R.string.delete_note))
289
+            setMessage("${context.getString(R.string.confirm_deletion)} ${_note.title}?")
290
+            setNegativeButton(context.getString(R.string.no)) { dialog, _ -> dialog?.dismiss() }
291
+            setPositiveButton(context.getString(R.string.yes)) { _, _ ->
292
+                viewModel.deleteNote(_note)
293
+                lifecycleScope.launch(Dispatchers.IO) { file?.delete() }
294
+                navController.safeNavigate(EditNoteFragmentDirections.actionEditNoteFragmentToHomeFragment())
295
+            }
296
+            show()
297
+        }
298
+    }
299
+    
300
+    private fun startPlayingRecording() {
301
+        timer = buildTimer {
302
+            startTime(_note.audioLength, TimeUnit.SECONDS)
303
+            startFormat(if (_note.audioLength >= 3600000L) "HH:MM:SS" else "MM:SS")
304
+            onTick { millis, time -> binding.tvTimer.text = time }
305
+            actionWhen(0, TimeUnit.SECONDS) {
306
+                binding.btnRecord.setImageDrawable(
307
+                    ResourcesCompat.getDrawable(
308
+                        resources,
309
+                        R.drawable.ic_audio_not_playing,
310
+                        null
311
+                    )
312
+                ).run {
313
+                    isPlayingRecord = false
314
+                    stopPlayingRecording()
315
+                }
316
+            }
317
+        }
318
+        mediaPlayer = MediaPlayer()
319
+        try {
320
+            mediaPlayer?.apply {
321
+                setDataSource(file?.absolutePath)
322
+                prepare()
323
+                start()
324
+            }
325
+            timer!!.start()
326
+        } catch (e: IOException) {
327
+            e.printStackTrace()
328
+            Log.d("TAG", "startPlayingRecording: ${e.localizedMessage}")
329
+            showToast(requireContext().getString(R.string.error_occurred))
330
+        }
331
+    }
332
+    
333
+    private fun pausePlayingRecording() {
334
+        mediaPlayer?.pause()
335
+        timer?.stop()
336
+    }
337
+    
338
+    private fun continuePlayingRecording() {
339
+        mediaPlayer?.start()
340
+        timer?.start()
341
+    }
342
+    
343
+    private fun stopPlayingRecording() {
344
+        mediaPlayer?.apply {
345
+            stop()
346
+            release()
347
+        }
348
+        timer?.apply {
349
+            reset()
350
+            stop()
351
+        }
352
+        timer = null
353
+    }
354
+    
355
+    private fun shareNote() {
356
+        if (file == null) {
357
+            showToast(requireContext().getString(R.string.file_not_found))
358
+            return
359
+        }
360
+        try {
361
+            val uri = FileProvider.getUriForFile(
362
+                requireContext(),
363
+                "com.certified.audionote.provider",
364
+                file!!
365
+            )
366
+            Intent(Intent.ACTION_SEND).apply {
367
+                type = "*/*"
368
+                putExtra(Intent.EXTRA_STREAM, uri)
369
+                startActivity(Intent.createChooser(this, "Share using"))
370
+            }
371
+        } catch (t: Throwable) {
372
+            showToast(requireContext().getString(R.string.error_occurred))
373
+            Log.d("TAG", "shareNote: ${t.localizedMessage}")
374
+        }
375
+    }
376
+    
377
+    private fun updateStatusBarColor(color: Int) {
378
+        val window = requireActivity().window
379
+        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
380
+//        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
381
+        window.statusBarColor = color
382
+    }
383
+}
0 384
\ No newline at end of file