From 8891f766353535c9a9a79eae90170b2609273b18 Mon Sep 17 00:00:00 2001 From: Sanju S Date: Thu, 8 Apr 2021 19:31:23 +0530 Subject: [PATCH 1/2] Add SaveBitmap.kt utils function #134 Signed-off-by: Sanju S --- .../org/nottzapp/utils/SaveBitmap.kt | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 app/src/main/java/thecodemonks/org/nottzapp/utils/SaveBitmap.kt diff --git a/app/src/main/java/thecodemonks/org/nottzapp/utils/SaveBitmap.kt b/app/src/main/java/thecodemonks/org/nottzapp/utils/SaveBitmap.kt new file mode 100644 index 0000000..6ee6519 --- /dev/null +++ b/app/src/main/java/thecodemonks/org/nottzapp/utils/SaveBitmap.kt @@ -0,0 +1,65 @@ +/* + * + * * + * * * MIT License + * * * + * * * Copyright (c) 2020 Spikey Sanju + * * * + * * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * * of this software and associated documentation files (the "Software"), to deal + * * * in the Software without restriction, including without limitation the rights + * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * * copies of the Software, and to permit persons to whom the Software is + * * * furnished to do so, subject to the following conditions: + * * * + * * * The above copyright notice and this permission notice shall be included in all + * * * copies or substantial portions of the Software. + * * * + * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * * SOFTWARE. + * * + * + * + */ + +package thecodemonks.org.nottzapp.utils + +import android.app.Activity +import android.content.ContentValues +import android.graphics.Bitmap +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore + +@JvmField +val DEFAULT_FILENAME = "${"Expenso" + System.currentTimeMillis()}.png" + +fun saveBitmap(activity: Activity, bitmap: Bitmap, filename: String = DEFAULT_FILENAME): Uri? { + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, filename) + put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES) + } + } + + val contentResolver = activity.contentResolver + + val imageUri: Uri? = contentResolver.insert( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + contentValues + ) + + return imageUri.also { + val fileOutputStream = imageUri?.let { contentResolver.openOutputStream(it) } + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream) + fileOutputStream?.close() + } +} From eeb3c98ba6d775c2f756d27064c3aca2581ec3bc Mon Sep 17 00:00:00 2001 From: Sanju S Date: Thu, 8 Apr 2021 21:22:18 +0530 Subject: [PATCH 2/2] Add Share as Image #134 | Add Water mark #135 Signed-off-by: Sanju S --- .idea/misc.xml | 18 + app/build.gradle | 7 + app/src/main/AndroidManifest.xml | 32 +- .../ui/details/NotesDetailsFragment.kt | 101 +- .../org/nottzapp/ui/dialog/ErrorDialog.kt | 76 + .../org/nottzapp/utils/SaveBitmap.kt | 2 +- .../org/nottzapp/utils/ViewExt.kt | 2 + app/src/main/res/drawable/ic_baseline_add.xml | 4 +- app/src/main/res/drawable/ic_delete.xml | 39 +- app/src/main/res/drawable/ic_watermark.xml | 48 + .../main/res/layout/add_notes_fragment.xml | 4 +- .../main/res/layout/content_note_layout.xml | 15 +- .../main/res/layout/error_dialog_layout.xml | 88 + app/src/main/res/layout/notes_fragment.xml | 3 +- app/src/main/res/menu/share_menu.xml | 44 +- app/src/main/res/navigation/nav_graph.xml | 16 + app/src/main/res/raw/failed.json | 3492 +++++++++++++++++ app/src/main/res/values-night/colors.xml | 5 +- app/src/main/res/values/colors.xml | 3 +- app/src/main/res/values/dimen.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- 21 files changed, 3969 insertions(+), 36 deletions(-) create mode 100644 app/src/main/java/thecodemonks/org/nottzapp/ui/dialog/ErrorDialog.kt create mode 100644 app/src/main/res/drawable/ic_watermark.xml create mode 100644 app/src/main/res/layout/error_dialog_layout.xml create mode 100644 app/src/main/res/raw/failed.json diff --git a/.idea/misc.xml b/.idea/misc.xml index ef61796..c1a2313 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,23 @@ + + + diff --git a/app/build.gradle b/app/build.gradle index 7cdc8f0..fd15e69 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -106,6 +106,10 @@ dependencies { implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + // activity & fragment ktx + implementation "androidx.fragment:fragment-ktx:1.3.2" + implementation "androidx.activity:activity-ktx:1.3.0-alpha05" + implementation 'androidx.appcompat:appcompat:1.3.0-rc01' // Navigation Components implementation "androidx.navigation:navigation-fragment-ktx:2.3.4" @@ -118,6 +122,9 @@ dependencies { // Preference DataStore implementation "androidx.datastore:datastore-preferences:1.0.0-alpha06" + // Lottie Animation Library + implementation "com.airbnb.android:lottie:3.6.0" + // Hilt implementation "com.google.dagger:hilt-android:2.30.1-alpha" kapt "com.google.dagger:hilt-android-compiler:2.30.1-alpha" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a024384..7f340aa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,36 @@ + + + - \ No newline at end of file + diff --git a/app/src/main/java/thecodemonks/org/nottzapp/ui/details/NotesDetailsFragment.kt b/app/src/main/java/thecodemonks/org/nottzapp/ui/details/NotesDetailsFragment.kt index 9ddfe1a..78a5329 100644 --- a/app/src/main/java/thecodemonks/org/nottzapp/ui/details/NotesDetailsFragment.kt +++ b/app/src/main/java/thecodemonks/org/nottzapp/ui/details/NotesDetailsFragment.kt @@ -29,11 +29,22 @@ package thecodemonks.org.nottzapp.ui.details +import android.Manifest import android.annotation.SuppressLint +import android.content.Intent +import android.content.pm.PackageManager import android.os.Bundle -import android.view.* +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.core.app.ShareCompat +import androidx.core.content.ContextCompat +import androidx.core.view.drawToBitmap import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.findNavController @@ -42,6 +53,8 @@ import dagger.hilt.android.AndroidEntryPoint import thecodemonks.org.nottzapp.R import thecodemonks.org.nottzapp.databinding.FragmentNotesDetailsBinding import thecodemonks.org.nottzapp.ui.notes.NotesViewModel +import thecodemonks.org.nottzapp.utils.saveBitmap +import thecodemonks.org.nottzapp.utils.showOrHide @AndroidEntryPoint class NotesDetailsFragment : Fragment(R.layout.fragment_notes_details) { @@ -52,11 +65,59 @@ class NotesDetailsFragment : Fragment(R.layout.fragment_notes_details) { private lateinit var _binding: FragmentNotesDetailsBinding private val binding get() = _binding + // handle permission dialog + private val requestLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) shareImage() else showErrorDialog() + } + + private fun showErrorDialog() = findNavController().navigate( + NotesDetailsFragmentDirections.actionNotesDetailsFragmentToErrorDialog( + "Image share failed!", + "You have to enable storage permission to share transaction as Image" + ) + ) + + private fun shareImage() { + + if (!isStoragePermissionGranted()) { + requestLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) + return + } + + // unHide watermark + showLogo(true) + val imageURI = binding.noteLayout.noteDetailLayout.drawToBitmap().let { bitmap -> + // hide watermark + showLogo(false) + saveBitmap(requireActivity(), bitmap) + } ?: run { + Toast.makeText(requireContext(), "Error Occured", Toast.LENGTH_SHORT).show() + return + } + + val intent = ShareCompat.IntentBuilder(requireActivity()) + .setType("image/jpeg") + .setStream(imageURI) + .intent + + startActivity(Intent.createChooser(intent, null)) + } + + private fun showLogo(isVisible: Boolean) = with(binding) { + noteLayout.logo.showOrHide(isVisible) + } + + private fun isStoragePermissionGranted(): Boolean = ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) == PackageManager.PERMISSION_GRANTED + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { + ): View { _binding = FragmentNotesDetailsBinding.inflate(inflater, container, false) return binding.root } @@ -107,21 +168,13 @@ class NotesDetailsFragment : Fragment(R.layout.fragment_notes_details) { true } - R.id.action_share -> { - val shareMsg = getString( - R.string.share_message, - args.notes.title, - args.notes.description - ) - - val intent = ShareCompat.IntentBuilder.from(requireActivity()) - .setType("text/plain") - .setText(shareMsg) - .intent + R.id.action_share_text -> { + shareText() + true + } - if (intent.resolveActivity(requireActivity().packageManager) != null) { - startActivity(intent) - } + R.id.action_share_image -> { + shareImage() true } @@ -135,4 +188,20 @@ class NotesDetailsFragment : Fragment(R.layout.fragment_notes_details) { it.noteET.text.toString() ) } + + @SuppressLint("StringFormatMatches") + private fun shareText() = with(binding) { + val shareMsg = getString( + R.string.share_message, + args.notes.title, + args.notes.description + ) + + val intent = ShareCompat.IntentBuilder(requireActivity()) + .setType("text/plain") + .setText(shareMsg) + .intent + + startActivity(Intent.createChooser(intent, null)) + } } diff --git a/app/src/main/java/thecodemonks/org/nottzapp/ui/dialog/ErrorDialog.kt b/app/src/main/java/thecodemonks/org/nottzapp/ui/dialog/ErrorDialog.kt new file mode 100644 index 0000000..0496f44 --- /dev/null +++ b/app/src/main/java/thecodemonks/org/nottzapp/ui/dialog/ErrorDialog.kt @@ -0,0 +1,76 @@ +/* + * + * * + * * * MIT License + * * * + * * * Copyright (c) 2020 Spikey Sanju + * * * + * * * Permission is hereby granted, free of charge, to any person obtaining a copy + * * * of this software and associated documentation files (the "Software"), to deal + * * * in the Software without restriction, including without limitation the rights + * * * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * * * copies of the Software, and to permit persons to whom the Software is + * * * furnished to do so, subject to the following conditions: + * * * + * * * The above copyright notice and this permission notice shall be included in all + * * * copies or substantial portions of the Software. + * * * + * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * * * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * * * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * * * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * * * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * * * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * * * SOFTWARE. + * * + * + * + */ + +package thecodemonks.org.nottzapp.ui.dialog + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import androidx.navigation.fragment.navArgs +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import thecodemonks.org.nottzapp.databinding.ErrorDialogLayoutBinding + +class ErrorDialog : BottomSheetDialogFragment() { + private var _binding: ErrorDialogLayoutBinding? = null + private val binding get() = _binding!! + private val args: ErrorDialogArgs by navArgs() + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = ErrorDialogLayoutBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.run { + dialogTitle.text = args.title + dialogMessage.text = args.message + dialogButtonOk.setOnClickListener { dialog?.dismiss() } + } + } + + override fun onStart() { + super.onStart() + dialog?.window?.setLayout( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT + ) + } + + override fun onDestroy() { + super.onDestroy() + _binding = null + } +} diff --git a/app/src/main/java/thecodemonks/org/nottzapp/utils/SaveBitmap.kt b/app/src/main/java/thecodemonks/org/nottzapp/utils/SaveBitmap.kt index 6ee6519..f8cebb2 100644 --- a/app/src/main/java/thecodemonks/org/nottzapp/utils/SaveBitmap.kt +++ b/app/src/main/java/thecodemonks/org/nottzapp/utils/SaveBitmap.kt @@ -38,7 +38,7 @@ import android.os.Environment import android.provider.MediaStore @JvmField -val DEFAULT_FILENAME = "${"Expenso" + System.currentTimeMillis()}.png" +val DEFAULT_FILENAME = "${"Notzz" + System.currentTimeMillis()}.png" fun saveBitmap(activity: Activity, bitmap: Bitmap, filename: String = DEFAULT_FILENAME): Uri? { val contentValues = ContentValues().apply { diff --git a/app/src/main/java/thecodemonks/org/nottzapp/utils/ViewExt.kt b/app/src/main/java/thecodemonks/org/nottzapp/utils/ViewExt.kt index 57553ca..f6e4516 100644 --- a/app/src/main/java/thecodemonks/org/nottzapp/utils/ViewExt.kt +++ b/app/src/main/java/thecodemonks/org/nottzapp/utils/ViewExt.kt @@ -38,3 +38,5 @@ fun View.show() { fun View.hide() { visibility = View.GONE } + +fun View.showOrHide(isVisible: Boolean) = if (isVisible) show() else hide() diff --git a/app/src/main/res/drawable/ic_baseline_add.xml b/app/src/main/res/drawable/ic_baseline_add.xml index 05e24c8..5fa5700 100644 --- a/app/src/main/res/drawable/ic_baseline_add.xml +++ b/app/src/main/res/drawable/ic_baseline_add.xml @@ -30,10 +30,10 @@ diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml index 83f4280..cb21401 100644 --- a/app/src/main/res/drawable/ic_delete.xml +++ b/app/src/main/res/drawable/ic_delete.xml @@ -1,3 +1,32 @@ + + diff --git a/app/src/main/res/drawable/ic_watermark.xml b/app/src/main/res/drawable/ic_watermark.xml new file mode 100644 index 0000000..c06cb65 --- /dev/null +++ b/app/src/main/res/drawable/ic_watermark.xml @@ -0,0 +1,48 @@ + + + + + + + + diff --git a/app/src/main/res/layout/add_notes_fragment.xml b/app/src/main/res/layout/add_notes_fragment.xml index f90d9b3..bae9cb4 100644 --- a/app/src/main/res/layout/add_notes_fragment.xml +++ b/app/src/main/res/layout/add_notes_fragment.xml @@ -47,9 +47,9 @@ android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/dimen_32" - android:backgroundTint="@color/colorAccent" android:contentDescription="@string/app_name" android:src="@drawable/ic_baseline_check" - app:tint="@android:color/white" /> + app:backgroundTint="@color/colorAccent" + app:tint="@color/white" /> diff --git a/app/src/main/res/layout/content_note_layout.xml b/app/src/main/res/layout/content_note_layout.xml index 36beb77..8cd46ea 100644 --- a/app/src/main/res/layout/content_note_layout.xml +++ b/app/src/main/res/layout/content_note_layout.xml @@ -34,9 +34,22 @@ android:fillViewport="true"> + + diff --git a/app/src/main/res/layout/error_dialog_layout.xml b/app/src/main/res/layout/error_dialog_layout.xml new file mode 100644 index 0000000..83bddf8 --- /dev/null +++ b/app/src/main/res/layout/error_dialog_layout.xml @@ -0,0 +1,88 @@ + + + + + + + + + +