diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/contracts/PermissionContract.kt b/app/src/main/java/dev/arkbuilders/arkmemo/contracts/PermissionContract.kt index f7e98852..1dda411c 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/contracts/PermissionContract.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/contracts/PermissionContract.kt @@ -14,7 +14,9 @@ class PermissionContract : ActivityResultContract() { override fun createIntent( context: Context, input: String, - ) = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse(input)) + ) = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse(input)).apply { + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + } @RequiresApi(Build.VERSION_CODES.R) override fun parseResult( diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/models/NoteType.kt b/app/src/main/java/dev/arkbuilders/arkmemo/models/NoteType.kt new file mode 100644 index 00000000..5b57d02f --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/arkmemo/models/NoteType.kt @@ -0,0 +1,7 @@ +package dev.arkbuilders.arkmemo.models + +enum class NoteType { + TEXT, + VOICE, + GRAPHIC, +} diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepo.kt index 2f3b60d1..3b10f9d8 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/NotesRepo.kt @@ -1,5 +1,6 @@ package dev.arkbuilders.arkmemo.repo +import dev.arkbuilders.arklib.ResourceId import dev.arkbuilders.arkmemo.models.SaveNoteResult interface NotesRepo { @@ -15,4 +16,6 @@ interface NotesRepo { suspend fun delete(note: Note) suspend fun delete(notes: List) + + suspend fun findNote(id: ResourceId): Note? } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt index 21f908cd..08627a40 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/graphics/GraphicNotesRepo.kt @@ -8,6 +8,7 @@ import android.graphics.Canvas import android.os.Environment import android.util.Log import dagger.hilt.android.qualifiers.ApplicationContext +import dev.arkbuilders.arklib.ResourceId import dev.arkbuilders.arklib.computeId import dev.arkbuilders.arklib.data.index.Resource import dev.arkbuilders.arkmemo.R @@ -116,33 +117,20 @@ class GraphicNotesRepo private suspend fun readStorage() = withContext(iODispatcher) { root.listFiles(SVG_EXT) { path -> - val svg = SVG.parse(path) - if (svg == null) { - Log.w(GRAPHICS_REPO, "Skipping invalid SVG: " + path) - } - val size = path.fileSize() - val id = computeId(size, path) - val resource = - Resource( - id = id, - name = path.fileName.name, - extension = path.extension, - modified = path.getLastModifiedTime(), - ) - - val userNoteProperties = helper.readProperties(id, "") - val bitmap = exportBitmapFromSvg(fileName = id.toString(), svg = svg) - - GraphicNote( - title = userNoteProperties.title, - description = userNoteProperties.description, - svg = svg, - resource = resource, - thumb = bitmap, - ) + path.toGraphicNote(helper = helper) }.filter { graphicNote -> graphicNote.svg != null } } + override suspend fun findNote(id: ResourceId): GraphicNote? { + return withContext(iODispatcher) { + root.listFiles(SVG_EXT) { path -> + path.toGraphicNote(helper = helper) + }.firstOrNull { graphicNote -> + graphicNote.svg != null && id == graphicNote.resource?.id + } + } + } + private fun exportBitmapFromSvg( fileName: String, svg: SVG?, @@ -204,6 +192,33 @@ class GraphicNotesRepo return null } } + + private fun Path.toGraphicNote(helper: NotesRepoHelper): GraphicNote { + val svg = SVG.parse(this) + if (svg == null) { + Log.w(GRAPHICS_REPO, "Skipping invalid SVG: " + this) + } + val size = this.fileSize() + val id = computeId(size, this) + val resource = + Resource( + id = id, + name = this.fileName.name, + extension = this.extension, + modified = this.getLastModifiedTime(), + ) + + val userNoteProperties = helper.readProperties(id, "") + val bitmap = exportBitmapFromSvg(fileName = id.toString(), svg = svg) + + return GraphicNote( + title = userNoteProperties.title, + description = userNoteProperties.description, + svg = svg, + resource = resource, + thumb = bitmap, + ) + } } private const val GRAPHICS_REPO = "GraphicNotesRepo" diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt index 67ba6b80..f52e25fe 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/text/TextNotesRepo.kt @@ -1,6 +1,7 @@ package dev.arkbuilders.arkmemo.repo.text import android.util.Log +import dev.arkbuilders.arklib.ResourceId import dev.arkbuilders.arklib.computeId import dev.arkbuilders.arklib.data.index.Resource import dev.arkbuilders.arkmemo.di.IO_DISPATCHER @@ -56,6 +57,10 @@ class TextNotesRepo readStorage() } + override suspend fun findNote(id: ResourceId): TextNote? { + return null + } + private suspend fun write( note: TextNote, callback: (SaveNoteResult) -> Unit, diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt b/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt index 1f7de8aa..305e3eaa 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/repo/voices/VoiceNotesRepo.kt @@ -1,6 +1,7 @@ package dev.arkbuilders.arkmemo.repo.voices import android.util.Log +import dev.arkbuilders.arklib.ResourceId import dev.arkbuilders.arklib.computeId import dev.arkbuilders.arklib.data.index.Resource import dev.arkbuilders.arkmemo.di.IO_DISPATCHER @@ -39,6 +40,16 @@ class VoiceNotesRepo readStorage() } + override suspend fun findNote(id: ResourceId): VoiceNote? { + return withContext(iODispatcher) { + root.listFiles(VOICE_EXT) { path -> + path.toVoiceNote(helper = helper) + }.firstOrNull { voiceNote -> + voiceNote.duration.isNotEmpty() && id == voiceNote.resource?.id + } + } + } + override suspend fun delete(notes: List) { helper.deleteNotes(notes) } @@ -101,25 +112,29 @@ class VoiceNotesRepo private suspend fun readStorage(): List = withContext(iODispatcher) { root.listFiles(VOICE_EXT) { path -> - val id = computeId(path.fileSize(), path) - val resource = - Resource( - id = id, - name = path.name, - extension = path.extension, - modified = path.getLastModifiedTime(), - ) - - val userNoteProperties = helper.readProperties(id, "") - VoiceNote( - title = userNoteProperties.title, - description = userNoteProperties.description, - path = path, - duration = extractDuration(path.pathString), - resource = resource, - ) + path.toVoiceNote(helper) }.filter { voiceNote -> voiceNote.duration.isNotEmpty() } } + + private fun Path.toVoiceNote(helper: NotesRepoHelper): VoiceNote { + val id = computeId(this.fileSize(), this) + val resource = + Resource( + id = id, + name = this.name, + extension = this.extension, + modified = this.getLastModifiedTime(), + ) + + val userNoteProperties = helper.readProperties(id, "") + return VoiceNote( + title = userNoteProperties.title, + description = userNoteProperties.description, + path = this, + duration = extractDuration(this.pathString), + resource = resource, + ) + } } private const val VOICES_REPO = "VoiceNotesRepo" diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt index e9a3a151..052d2971 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/activities/MainActivity.kt @@ -3,7 +3,6 @@ package dev.arkbuilders.arkmemo.ui.activities import android.content.Intent import android.os.Bundle import android.view.WindowManager -import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.IdRes import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat @@ -13,7 +12,6 @@ import androidx.fragment.app.Fragment import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint import dev.arkbuilders.arkmemo.R -import dev.arkbuilders.arkmemo.contracts.PermissionContract import dev.arkbuilders.arkmemo.databinding.ActivityMainBinding import dev.arkbuilders.arkmemo.models.RootNotFound import dev.arkbuilders.arkmemo.preferences.MemoPreferences @@ -22,6 +20,7 @@ import dev.arkbuilders.arkmemo.ui.dialogs.FilePickerDialog import dev.arkbuilders.arkmemo.ui.fragments.BaseFragment import dev.arkbuilders.arkmemo.ui.fragments.EditTextNotesFragment import dev.arkbuilders.arkmemo.ui.fragments.NotesFragment +import dev.arkbuilders.arkmemo.utils.PermissionManager import dev.arkbuilders.components.filepicker.onArkPathPicked import javax.inject.Inject import kotlin.io.path.exists @@ -37,25 +36,10 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { private val fragContainer = R.id.container var fragment: Fragment = NotesFragment() + val permissionManager = PermissionManager(activity = this) init { - FilePickerDialog.readPermLauncher = - registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> - if (isGranted) { - FilePickerDialog.show() - } else { - finish() - } - } - - FilePickerDialog.readPermLauncherSdkR = - registerForActivityResult(PermissionContract()) { isGranted -> - if (isGranted) { - FilePickerDialog.show() - } else { - finish() - } - } + FilePickerDialog.permissionManager = permissionManager } override fun onCreate(savedInstanceState: Bundle?) { @@ -72,21 +56,27 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { showFragment(savedInstanceState) } - val storageFolderExisting = memoPreferences.getNotesStorage().exists() - if (memoPreferences.storageNotAvailable()) { - if (!storageFolderExisting) { - showNoNoteStorageDialog(RootNotFound(rootPath = memoPreferences.getPath())) - } else { - FilePickerDialog.show(this, supportFragmentManager) - } - } else { - if (memoPreferences.isLastLaunchSuccess()) { - showFragment(savedInstanceState) + permissionManager.askForWriteStorage { granted -> + if (!granted) { + finish() } else { - showRetrySelectRootDialog( - rootPath = memoPreferences.getPath(), - savedInstanceState = savedInstanceState, - ) + val storageFolderExisting = memoPreferences.getNotesStorage().exists() + if (memoPreferences.storageNotAvailable()) { + if (!storageFolderExisting) { + showNoNoteStorageDialog(RootNotFound(rootPath = memoPreferences.getPath())) + } else { + FilePickerDialog.show(this, supportFragmentManager) + } + } else { + if (memoPreferences.isLastLaunchSuccess()) { + showFragment(savedInstanceState) + } else { + showRetrySelectRootDialog( + rootPath = memoPreferences.getPath(), + savedInstanceState = savedInstanceState, + ) + } + } } } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt index eb65bc29..ff168ce8 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/dialogs/FilePickerDialog.kt @@ -1,18 +1,13 @@ package dev.arkbuilders.arkmemo.ui.dialogs -import android.Manifest -import android.content.pm.PackageManager -import android.os.Build import android.os.Bundle -import android.os.Environment -import androidx.activity.result.ActivityResultLauncher import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat import androidx.fragment.app.FragmentManager import dagger.hilt.android.AndroidEntryPoint -import dev.arkbuilders.arkmemo.BuildConfig import dev.arkbuilders.arkmemo.R import dev.arkbuilders.arkmemo.preferences.MemoPreferences +import dev.arkbuilders.arkmemo.utils.Permission +import dev.arkbuilders.arkmemo.utils.PermissionManager import dev.arkbuilders.components.filepicker.ArkFilePickerConfig import dev.arkbuilders.components.filepicker.ArkFilePickerFragment import dev.arkbuilders.components.filepicker.ArkFilePickerMode @@ -39,8 +34,7 @@ class FilePickerDialog : ArkFilePickerFragment() { companion object { private const val TAG = "file_picker" private lateinit var fragmentManager: FragmentManager - var readPermLauncher: ActivityResultLauncher? = null - var readPermLauncherSdkR: ActivityResultLauncher? = null + var permissionManager: PermissionManager? = null fun show() { newInstance(getFilePickerConfig()).show(fragmentManager, TAG) @@ -51,10 +45,16 @@ class FilePickerDialog : ArkFilePickerFragment() { fragmentManager: FragmentManager, ) { Companion.fragmentManager = fragmentManager - if (isReadPermissionGranted(activity)) { + if (Permission.hasStoragePermission(activity)) { show() } else { - askForReadPermissions() + permissionManager?.askForWriteStorage { granted -> + if (granted) { + show() + } else { + activity.finish() + } + } } } @@ -63,24 +63,6 @@ class FilePickerDialog : ArkFilePickerFragment() { setConfig(config) } - private fun isReadPermissionGranted(activity: AppCompatActivity): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - Environment.isExternalStorageManager() - } else { - ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == - PackageManager.PERMISSION_GRANTED - } - } - - private fun askForReadPermissions() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val packageUri = "package:" + BuildConfig.APPLICATION_ID - readPermLauncherSdkR?.launch(packageUri) - } else { - readPermLauncher?.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } - } - private fun getFilePickerConfig() = ArkFilePickerConfig( mode = ArkFilePickerMode.FOLDER, diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkMediaPlayerFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkMediaPlayerFragment.kt index 432d03a1..becfb7c0 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkMediaPlayerFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkMediaPlayerFragment.kt @@ -59,6 +59,9 @@ class ArkMediaPlayerFragment : BaseEditNoteFragment() { return false } + override fun onViewRestoredWithNote(note: Note) { + } + private fun initUI() { binding.toolbar.ivRightActionIcon.setOnClickListener { showDeleteNoteDialog(note) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt index 25372a07..04690915 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/ArkRecorderFragment.kt @@ -399,6 +399,11 @@ class ArkRecorderFragment : BaseEditNoteFragment() { ) } + override fun onViewRestoredWithNote(note: Note) { + setNote(note as VoiceNote) + initExistingNoteUI() + } + private fun saveNote() { notesViewModel.onSaveClick(createNewNote(), parentNote = note) { show -> activity.showProgressBar(show) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt index f5842a0c..e185d0de 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/BaseEditNoteFragment.kt @@ -6,13 +6,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.activityViewModels +import dev.arkbuilders.arklib.ResourceId import dev.arkbuilders.arkmemo.R import dev.arkbuilders.arkmemo.databinding.FragmentEditNotesBinding import dev.arkbuilders.arkmemo.models.Note +import dev.arkbuilders.arkmemo.models.NoteType import dev.arkbuilders.arkmemo.ui.activities.MainActivity import dev.arkbuilders.arkmemo.ui.dialogs.CommonActionDialog import dev.arkbuilders.arkmemo.ui.viewmodels.NotesViewModel import dev.arkbuilders.arkmemo.ui.views.toast +import dev.arkbuilders.arkmemo.utils.getParcelableCompat import dev.arkbuilders.arkmemo.utils.gone import dev.arkbuilders.arkmemo.utils.visible import java.util.Calendar @@ -23,6 +26,10 @@ abstract class BaseEditNoteFragment : BaseFragment() { val notesViewModel: NotesViewModel by activityViewModels() val hostActivity by lazy { activity as MainActivity } + companion object { + const val BUNDLE_KEY_NOTE_ID = "bundle_key_note_id" + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -174,6 +181,36 @@ abstract class BaseEditNoteFragment : BaseFragment() { handleBackPressed() } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putParcelable(BUNDLE_KEY_NOTE_ID, getCurrentNote().resource?.id) + } + + override fun onViewStateRestored(savedInstanceState: Bundle?) { + super.onViewStateRestored(savedInstanceState) + val noteId = savedInstanceState?.getParcelableCompat(BUNDLE_KEY_NOTE_ID, ResourceId::class.java) + noteId?.let { + hostActivity.permissionManager.askForWriteStorage { granted -> + if (granted) { + notesViewModel.init { + val noteType = + when (this@BaseEditNoteFragment) { + is EditGraphicNotesFragment -> NoteType.GRAPHIC + is ArkRecorderFragment -> NoteType.VOICE + is EditTextNotesFragment -> NoteType.TEXT + else -> NoteType.TEXT + } + + notesViewModel.findNote(id = noteId, type = noteType) { foundNote -> + foundNote ?: return@findNote + onViewRestoredWithNote(foundNote) + } + } + } + } + } + } + abstract fun createNewNote(): Note abstract fun getCurrentNote(): Note @@ -181,4 +218,6 @@ abstract class BaseEditNoteFragment : BaseFragment() { abstract fun isContentChanged(): Boolean abstract fun isContentEmpty(): Boolean + + abstract fun onViewRestoredWithNote(note: Note) } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt index 394b891b..15db7b2e 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditGraphicNotesFragment.kt @@ -172,6 +172,12 @@ class EditGraphicNotesFragment : BaseEditNoteFragment() { return graphicNotesViewModel.svg().getPaths().isEmpty() } + override fun onViewRestoredWithNote(note: Note) { + this.note = note as GraphicNote + graphicNotesViewModel.onNoteOpened(note) + binding.notesCanvas.invalidate() + } + private fun initBottomControls() { val tvBrushSize = binding.layoutGraphicsControl.tvBrushSize tvBrushSize.setOnClickListener { diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt index d6d61d63..2ec0fd8d 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/EditTextNotesFragment.kt @@ -75,7 +75,6 @@ class EditTextNotesFragment : BaseEditNoteFragment() { note = it } noteStr = requireArguments().getString(NOTE_STRING_KEY) - notesViewModel.init {} } } @@ -167,6 +166,9 @@ class EditTextNotesFragment : BaseEditNoteFragment() { return binding.editNote.text.toString().trim().isEmpty() } + override fun onViewRestoredWithNote(note: Note) { + } + override fun onDestroyView() { super.onDestroyView() view?.viewTreeObserver?.removeOnWindowFocusChangeListener(windowFocusedListener) diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt index 7944af35..afa1b43e 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/fragments/NotesFragment.kt @@ -168,12 +168,21 @@ class NotesFragment : BaseFragment() { initEmptyStateViews() binding.pbLoading.visible() - notesViewModel.apply { - setLastLaunchSuccess(false) - init { - readAllNotes { - onNotesLoaded(it) - setLastLaunchSuccess(true) + activity.permissionManager.askForWriteStorage { granted -> + if (!granted) { + if (isVisible) { + activity.finish() + } + return@askForWriteStorage + } else { + notesViewModel.apply { + setLastLaunchSuccess(false) + init { + readAllNotes { + onNotesLoaded(it) + setLastLaunchSuccess(true) + } + } } } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/GraphicNotesViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/GraphicNotesViewModel.kt index ca05b1b2..269d7aa4 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/GraphicNotesViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/GraphicNotesViewModel.kt @@ -41,8 +41,10 @@ class GraphicNotesViewModel fun onNoteOpened(note: GraphicNote) { viewModelScope.launch { if (editPaths.isNotEmpty()) editPaths.clear() - editPaths.addAll(note.svg?.getPaths()!!) - svg = note.svg.copy() + note.svg?.getPaths()?.let { paths -> + editPaths.addAll(paths) + svg = note.svg.copy() + } } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt index 1787b741..9b186849 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/ui/viewmodels/NotesViewModel.kt @@ -9,6 +9,7 @@ import dev.arkbuilders.arklib.ResourceId import dev.arkbuilders.arkmemo.di.IO_DISPATCHER import dev.arkbuilders.arkmemo.models.GraphicNote import dev.arkbuilders.arkmemo.models.Note +import dev.arkbuilders.arkmemo.models.NoteType import dev.arkbuilders.arkmemo.models.SaveNoteResult import dev.arkbuilders.arkmemo.models.TextNote import dev.arkbuilders.arkmemo.models.VoiceNote @@ -69,6 +70,25 @@ class NotesViewModel } } + fun findNote( + id: ResourceId, + type: NoteType, + onResult: (note: Note?) -> Unit, + ) { + viewModelScope.launch(iODispatcher) { + val noteRepo = + when (type) { + NoteType.TEXT -> textNotesRepo + NoteType.VOICE -> voiceNotesRepo + NoteType.GRAPHIC -> graphicNotesRepo + } + val note = noteRepo.findNote(id) + withContext(Dispatchers.Main) { + onResult.invoke(note) + } + } + } + fun searchNote( keyword: String, onSuccess: (notes: List) -> Unit, diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/Permission.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/Permission.kt index fa06017c..b07e9eef 100644 --- a/app/src/main/java/dev/arkbuilders/arkmemo/utils/Permission.kt +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/Permission.kt @@ -1,7 +1,11 @@ package dev.arkbuilders.arkmemo.utils +import android.Manifest import android.content.Context import android.content.pm.PackageManager +import android.os.Build +import android.os.Environment +import androidx.core.content.ContextCompat object Permission { fun hasPermission( @@ -10,4 +14,15 @@ object Permission { ): Boolean { return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED } + + fun hasStoragePermission(context: Context): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Environment.isExternalStorageManager() + } else { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + ) == PackageManager.PERMISSION_GRANTED + } + } } diff --git a/app/src/main/java/dev/arkbuilders/arkmemo/utils/PermissionManager.kt b/app/src/main/java/dev/arkbuilders/arkmemo/utils/PermissionManager.kt new file mode 100644 index 00000000..17c0fa6d --- /dev/null +++ b/app/src/main/java/dev/arkbuilders/arkmemo/utils/PermissionManager.kt @@ -0,0 +1,38 @@ +package dev.arkbuilders.arkmemo.utils + +import android.Manifest +import android.os.Build +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import dev.arkbuilders.arkmemo.BuildConfig +import dev.arkbuilders.arkmemo.contracts.PermissionContract + +class PermissionManager(val activity: ComponentActivity) { + private var permissionResultCallback: ((granted: Boolean) -> Unit)? = null + private val permissionLauncher: ActivityResultLauncher = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + activity.registerForActivityResult(PermissionContract()) { isGranted -> + permissionResultCallback?.invoke(isGranted) + } + } else { + activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + permissionResultCallback?.invoke(isGranted) + } + } + + fun askForWriteStorage(onResult: ((granted: Boolean) -> Unit)? = null) { + if (Permission.hasStoragePermission(activity)) { + onResult?.invoke(true) + return + } + + permissionResultCallback = onResult + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + val packageUri = "package:" + BuildConfig.APPLICATION_ID + permissionLauncher.launch(packageUri) + } else { + permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c0d7ee54..acd54e6e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,7 @@ navigationFragmentKtx = "2.5.2" preferenceKtx = "1.2.0" lifecycleViewmodelKtx = "2.5.1" arkFilepicker = "0.2.0" +#arkLib = "0.3.6-snapshot-parcelize-resource-04" arkLib = "0.3.5" googleHiltAndroid = "2.48" googleHiltCompiler = "2.48"