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,342 @@
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.Manifest
20
+import android.app.DatePickerDialog
21
+import android.app.TimePickerDialog
22
+import android.content.pm.PackageManager
23
+import android.media.MediaRecorder
24
+import android.os.Bundle
25
+import android.view.LayoutInflater
26
+import android.view.View
27
+import android.view.ViewGroup
28
+import android.view.WindowManager
29
+import android.widget.DatePicker
30
+import android.widget.TimePicker
31
+import androidx.activity.result.contract.ActivityResultContracts
32
+import androidx.core.content.ContextCompat
33
+import androidx.core.content.res.ResourcesCompat
34
+import androidx.fragment.app.Fragment
35
+import androidx.fragment.app.viewModels
36
+import androidx.navigation.NavController
37
+import androidx.navigation.Navigation
38
+import androidx.navigation.fragment.navArgs
39
+import com.certified.audionote.R
40
+import com.certified.audionote.databinding.DialogEditReminderBinding
41
+import com.certified.audionote.databinding.FragmentAddNoteBinding
42
+import com.certified.audionote.model.Note
43
+import com.certified.audionote.utils.Extensions.safeNavigate
44
+import com.certified.audionote.utils.Extensions.showKeyboardFor
45
+import com.certified.audionote.utils.Extensions.showToast
46
+import com.certified.audionote.utils.ReminderAvailableState
47
+import com.certified.audionote.utils.cancelAlarm
48
+import com.certified.audionote.utils.currentDate
49
+import com.certified.audionote.utils.filePath
50
+import com.certified.audionote.utils.formatReminderDate
51
+import com.certified.audionote.utils.roundOffDecimal
52
+import com.certified.audionote.utils.startAlarm
53
+import com.google.android.material.bottomsheet.BottomSheetDialog
54
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
55
+import dagger.hilt.android.AndroidEntryPoint
56
+import timerx.*
57
+import java.io.File
58
+import java.io.IOException
59
+import java.util.Calendar
60
+import java.util.concurrent.TimeUnit
61
+
62
+@AndroidEntryPoint
63
+class AddNoteFragment : Fragment(), DatePickerDialog.OnDateSetListener,
64
+    TimePickerDialog.OnTimeSetListener {
65
+
66
+    private var _binding: FragmentAddNoteBinding? = null
67
+    private val binding get() = _binding!!
68
+
69
+    private val viewModel: NotesViewModel by viewModels()
70
+    private lateinit var navController: NavController
71
+
72
+    private val args: EditNoteFragmentArgs by navArgs()
73
+    private lateinit var _note: Note
74
+    private var isRecording = false
75
+    private var pickedDateTime: Calendar? = null
76
+    private val currentDateTime by lazy { currentDate() }
77
+    private var mediaRecorder: MediaRecorder? = null
78
+    private var file: File? = null
79
+    private var stopWatch: Stopwatch? = null
80
+    private var timer: Timer? = null
81
+
82
+    private val requestAudioRecordingPermissionLauncher =
83
+        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
84
+            if (!isGranted) MaterialAlertDialogBuilder(requireContext()).apply {
85
+                setTitle(getString(R.string.audio_record_permission))
86
+                setMessage(getString(R.string.permission_required))
87
+                setPositiveButton(getString(R.string.ok)) { dialog, _ -> dialog.dismiss() }
88
+                show()
89
+            }
90
+            else startRecording()
91
+        }
92
+
93
+    override fun onCreateView(
94
+        inflater: LayoutInflater, container: ViewGroup?,
95
+        savedInstanceState: Bundle?
96
+    ): View {
97
+        // Inflate the layout for this fragment
98
+        _binding = FragmentAddNoteBinding.inflate(layoutInflater, container, false)
99
+        return binding.root
100
+    }
101
+
102
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
103
+        super.onViewCreated(view, savedInstanceState)
104
+
105
+        navController = Navigation.findNavController(view)
106
+
107
+        binding.lifecycleOwner = viewLifecycleOwner
108
+        binding.viewModel = viewModel
109
+        args.note.let {
110
+            _note = it
111
+            binding.note = it
112
+        }
113
+
114
+        binding.btnBack.setOnClickListener {
115
+            if (binding.etNoteTitle.text.toString().isNotBlank())
116
+                saveNote()
117
+            else
118
+                try {
119
+                    file?.delete()
120
+                } catch (e: Exception) {
121
+                    e.printStackTrace()
122
+                }
123
+            navController.safeNavigate(AddNoteFragmentDirections.actionAddNoteFragmentToHomeFragment())
124
+        }
125
+        binding.cardAddReminder.setOnClickListener {
126
+            if (viewModel.reminderAvailableState.value == ReminderAvailableState.NO_REMINDER)
127
+                pickDate()
128
+            else
129
+                openEditReminderDialog()
130
+        }
131
+        binding.btnRecord.setOnClickListener { recordAudio() }
132
+        binding.fabSaveNote.setOnClickListener {
133
+            if (binding.etNoteTitle.text.toString().isNotBlank())
134
+                saveNote()
135
+            else {
136
+                showToast(requireContext().getString(R.string.title_required))
137
+                binding.etNoteTitle.requestFocus()
138
+            }
139
+        }
140
+    }
141
+
142
+    override fun onResume() {
143
+        super.onResume()
144
+        binding.etNoteTitle.apply {
145
+            requestFocus()
146
+            showKeyboardFor(requireContext())
147
+        }
148
+        updateStatusBarColor(binding.note!!.color)
149
+    }
150
+
151
+    override fun onDestroyView() {
152
+        super.onDestroyView()
153
+        mediaRecorder = null
154
+        timer?.apply {
155
+            stop()
156
+            reset()
157
+        }
158
+        timer = null
159
+        stopWatch?.apply {
160
+            stop()
161
+            reset()
162
+        }
163
+        stopWatch = null
164
+        _binding = null
165
+    }
166
+
167
+    private fun recordAudio() {
168
+        binding.apply {
169
+            if (!isRecording) {
170
+                if (ContextCompat.checkSelfPermission(
171
+                        requireContext(), Manifest.permission.RECORD_AUDIO
172
+                    ) == PackageManager.PERMISSION_GRANTED
173
+                ) {
174
+                    btnRecord.setImageDrawable(
175
+                        ResourcesCompat.getDrawable(
176
+                            resources,
177
+                            R.drawable.ic_mic_recording,
178
+                            null
179
+                        )
180
+                    ).run {
181
+                        try {
182
+                            file?.delete()
183
+                        } catch (e: Exception) {
184
+                            e.printStackTrace()
185
+                        }
186
+                        isRecording = true
187
+                        startRecording()
188
+                    }
189
+                } else if (shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO))
190
+                    MaterialAlertDialogBuilder(requireContext()).apply {
191
+                        setTitle(getString(R.string.audio_record_permission))
192
+                        setMessage(getString(R.string.permission_required))
193
+                        setPositiveButton(getString(R.string.ok)) { dialog, _ -> dialog.dismiss() }
194
+                        show()
195
+                    }
196
+                else requestAudioRecordingPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
197
+            } else {
198
+                btnRecord.setImageDrawable(
199
+                    ResourcesCompat.getDrawable(
200
+                        resources, R.drawable.ic_mic_not_recording, null
201
+                    )
202
+                )
203
+                    .run {
204
+                        isRecording = false
205
+                        stopRecording()
206
+                    }
207
+            }
208
+        }
209
+    }
210
+
211
+    override fun onDateSet(p0: DatePicker?, p1: Int, p2: Int, p3: Int) {
212
+        pickedDateTime = currentDate()
213
+        pickedDateTime!!.set(p1, p2, p3)
214
+        val hourOfDay = currentDateTime.get(Calendar.HOUR_OF_DAY)
215
+        val minuteOfDay = currentDateTime.get(Calendar.MINUTE)
216
+        val timePickerDialog =
217
+            TimePickerDialog(requireContext(), this, hourOfDay, minuteOfDay, false)
218
+        timePickerDialog.setOnDismissListener {
219
+            viewModel._reminderAvailableState.value = ReminderAvailableState.HAS_REMINDER
220
+            _note.reminder = pickedDateTime!!.timeInMillis
221
+            binding.tvReminderDate.text = formatReminderDate(pickedDateTime!!.timeInMillis)
222
+        }
223
+        timePickerDialog.show()
224
+    }
225
+
226
+    override fun onTimeSet(p0: TimePicker?, p1: Int, p2: Int) {
227
+        pickedDateTime!!.set(Calendar.HOUR_OF_DAY, p1)
228
+        pickedDateTime!!.set(Calendar.MINUTE, p2)
229
+        if (pickedDateTime!!.timeInMillis <= currentDate().timeInMillis) {
230
+            pickedDateTime!!.run {
231
+                set(Calendar.DAY_OF_MONTH, currentDateTime.get(Calendar.DAY_OF_MONTH) + 1)
232
+                set(Calendar.YEAR, currentDateTime.get(Calendar.YEAR))
233
+                set(Calendar.MONTH, currentDateTime.get(Calendar.MONTH))
234
+            }
235
+        }
236
+    }
237
+
238
+    private fun pickDate() {
239
+        val startYear = currentDateTime.get(Calendar.YEAR)
240
+        val startMonth = currentDateTime.get(Calendar.MONTH)
241
+        val startDay = currentDateTime.get(Calendar.DAY_OF_MONTH)
242
+        val datePickerDialog =
243
+            DatePickerDialog(requireContext(), this, startYear, startMonth, startDay)
244
+        datePickerDialog.show()
245
+    }
246
+
247
+    private fun openEditReminderDialog() {
248
+        val view = DialogEditReminderBinding.inflate(layoutInflater)
249
+        val bottomSheetDialog = BottomSheetDialog(requireContext())
250
+        view.apply {
251
+            note = _note
252
+            btnDeleteReminder.setOnClickListener {
253
+                viewModel._reminderAvailableState.value = ReminderAvailableState.NO_REMINDER
254
+                _note.reminder = null
255
+                bottomSheetDialog.dismiss()
256
+            }
257
+            btnModifyReminder.setOnClickListener {
258
+                bottomSheetDialog.dismiss()
259
+                pickDate()
260
+            }
261
+        }
262
+        bottomSheetDialog.edgeToEdgeEnabled
263
+        bottomSheetDialog.setContentView(view.root)
264
+        bottomSheetDialog.show()
265
+    }
266
+
267
+    private fun saveNote() {
268
+        binding.apply {
269
+            stopRecording()
270
+            if (_note.audioLength <= 0) {
271
+                showToast(requireContext().getString(R.string.record_note_before_saving))
272
+                return
273
+            }
274
+            val note = _note.copy(
275
+                title = etNoteTitle.text.toString().trim(),
276
+                description = etNoteDescription.text.toString().trim()
277
+            )
278
+            this@AddNoteFragment.viewModel.insertNote(note)
279
+            showToast(requireContext().getString(R.string.note_saved))
280
+            if (pickedDateTime?.timeInMillis != null && pickedDateTime!!.timeInMillis <= currentDateTime.timeInMillis)
281
+                startAlarm(requireContext(), pickedDateTime!!.timeInMillis, note)
282
+            navController.safeNavigate(AddNoteFragmentDirections.actionAddNoteFragmentToHomeFragment())
283
+        }
284
+    }
285
+
286
+    private fun startRecording() {
287
+        val filePath = filePath(requireActivity())
288
+        val fileName = "${System.currentTimeMillis()}.3gp"
289
+        _note.filePath = "$filePath/$fileName"
290
+
291
+        stopWatch = buildStopwatch {
292
+            startFormat("MM:SS")
293
+            onTick { millis: Long, time: CharSequence->
294
+                binding.tvTimer.text = time
295
+            }
296
+            changeFormatWhen(1, TimeUnit.HOURS, "HH:MM:SS")
297
+        }
298
+        
299
+
300
+        mediaRecorder = MediaRecorder().apply {
301
+            setAudioSource(MediaRecorder.AudioSource.MIC)
302
+            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
303
+            setOutputFile("$filePath/$fileName")
304
+            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
305
+            try {
306
+                prepare()
307
+                start()
308
+                stopWatch!!.start()
309
+                file = File("$filePath/$fileName")
310
+            } catch (e: IOException) {
311
+                showToast(requireContext().getString(R.string.error_occurred))
312
+            }
313
+        }
314
+    }
315
+
316
+    private fun stopRecording() {
317
+        mediaRecorder?.apply {
318
+            stop()
319
+            release()
320
+        }
321
+        mediaRecorder = null
322
+        stopWatch?.apply {
323
+            stop()
324
+            _note.audioLength = stopWatch!!.currentTimeInMillis / 1000
325
+            reset()
326
+        }
327
+        stopWatch = null
328
+        if (_note.audioLength <= 0)
329
+            return
330
+        file = File(_note.filePath)
331
+        val fileByte = (file!!.readBytes().size.toDouble() / 1048576.00)
332
+        val fileSize = roundOffDecimal(fileByte)
333
+        _note.size = fileSize
334
+    }
335
+
336
+    private fun updateStatusBarColor(color: Int) {
337
+        val window = requireActivity().window
338
+        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
339
+//        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
340
+        window.statusBarColor = color
341
+    }
342
+}
0 343
\ No newline at end of file