From 46fcfdf0a5ad9b00a517ce5231c827649c85be67 Mon Sep 17 00:00:00 2001 From: Vivian Li <112584985+strawberrybread@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:12:10 -0400 Subject: [PATCH 1/4] actions: less warnings is more (fixes #3772) (#3773) Co-authored-by: dogi --- app/build.gradle | 4 +- .../planet/myplanet/utilities/FileUtils.kt | 98 ++++++++++++------- 2 files changed, 65 insertions(+), 37 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f712ee31ab..68f3233ff4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 21 targetSdkVersion 34 - versionCode 1655 - versionName "0.16.55" + versionCode 1656 + versionName "0.16.56" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/utilities/FileUtils.kt b/app/src/main/java/org/ole/planet/myplanet/utilities/FileUtils.kt index d1436a27a1..f8f48ebf93 100644 --- a/app/src/main/java/org/ole/planet/myplanet/utilities/FileUtils.kt +++ b/app/src/main/java/org/ole/planet/myplanet/utilities/FileUtils.kt @@ -1,5 +1,6 @@ package org.ole.planet.myplanet.utilities +import android.app.PendingIntent import android.app.usage.StorageStatsManager import android.content.Context import android.content.Intent @@ -13,9 +14,8 @@ import android.provider.MediaStore import android.text.TextUtils import android.webkit.MimeTypeMap import androidx.annotation.RequiresApi -import androidx.core.content.FileProvider -import org.ole.planet.myplanet.BuildConfig import org.ole.planet.myplanet.MainApplication +import android.content.pm.PackageInstaller import org.ole.planet.myplanet.R import java.io.BufferedReader import java.io.File @@ -116,29 +116,42 @@ object FileUtils { @JvmStatic fun installApk(activity: Context, file: String?) { + if (!file?.endsWith("apk")!!) return + val toInstall = File(file) + if (!toInstall.exists()) return try { - if (!file?.endsWith("apk")!!) return - val toInstall = getSDPathFromUrl(file) - toInstall.setReadable(true, false) - val apkUri: Uri - val intent: Intent - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - apkUri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", toInstall) - intent = Intent(Intent.ACTION_INSTALL_PACKAGE) - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - intent.setData(apkUri) - } else { - apkUri = Uri.fromFile(toInstall) - intent = Intent(Intent.ACTION_VIEW) - intent.setDataAndType(apkUri, "application/vnd.android.package-archive") - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - activity.startActivity(intent) + val packageInstaller = activity.packageManager.packageInstaller + val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + val sessionId = packageInstaller.createSession(params) + val session = packageInstaller.openSession(sessionId) + addApkToInstallSession(toInstall, session) + val intent = Intent(activity, activity.javaClass) + val pendingIntent = PendingIntent.getActivity(activity, 0, intent, + PendingIntent.FLAG_IMMUTABLE) + val intentSender = pendingIntent.intentSender + session.commit(intentSender) + session.close() } catch (e: Exception) { e.printStackTrace() } } + @Throws(IOException::class) + private fun addApkToInstallSession(apkFile: File, session: PackageInstaller.Session) { + val out: OutputStream = session.openWrite("my_app_session", 0, -1) + val fis = FileInputStream(apkFile) + fis.use { input -> + out.use { output -> + val buffer = ByteArray(4096) + var length: Int + while (input.read(buffer).also { length = it } != -1) { + output.write(buffer, 0, length) + } + session.fsync(out) + } + } + } + private fun getMimeType(url: String): String? { var type: String? = null val extension = MimeTypeMap.getFileExtensionFromUrl(url) @@ -223,24 +236,39 @@ object FileUtils { @JvmStatic fun getImagePath(context: Context, uri: Uri?): String? { - var cursor = uri?.let { context.contentResolver.query(it, null, null, null, null) } - return if (cursor != null && cursor.moveToFirst()) { - var document_id = cursor.getString(0) - document_id = document_id.substring(document_id.lastIndexOf(":") + 1) - cursor.close() - cursor = context.contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, MediaStore.Images.Media._ID + " = ? ", arrayOf(document_id), null) + if (uri == null) return null + val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA) + var cursor: Cursor? = null + try { + cursor = context.contentResolver.query(uri, projection, null, null, null) if (cursor != null && cursor.moveToFirst()) { - val path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)) - cursor.close() - path - } else { - // Handle the case when the cursor is empty or null - null // or return an appropriate default value or handle the error accordingly + val documentIdIndex = cursor.getColumnIndex(MediaStore.Images.Media._ID) + if (documentIdIndex >= 0) { + val documentId = cursor.getString(documentIdIndex) + cursor.close() + val selection = "${MediaStore.Images.Media._ID} = ?" + val selectionArgs = arrayOf(documentId) + cursor = context.contentResolver.query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + projection, + selection, + selectionArgs, + null + ) + if (cursor != null && cursor.moveToFirst()) { + val dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA) + if (dataIndex >= 0) { + val path = cursor.getString(dataIndex) + cursor.close() + return path + } + } + } } - } else { - // Handle the case when the cursor is empty or null - null // or return an appropriate default value or handle the error accordingly + } finally { + cursor?.close() } + return null } @JvmStatic @@ -417,7 +445,7 @@ object FileUtils { fun nameWithoutExtension(fileName: String?): String?{ extractFileName(fileName) - val nameWithExtension = FileUtils.extractFileName(fileName) + val nameWithExtension = extractFileName(fileName) val nameWithoutExtension = nameWithExtension?.substringBeforeLast(".") return nameWithoutExtension } From 3a65ee9e6f4f8e913d5b5aabebe0d70658f847fb Mon Sep 17 00:00:00 2001 From: Elijah Whang <59347000+ewhang5@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:38:14 -0500 Subject: [PATCH 2/4] actions: less warnings is more (fixes #3777) (#3781) Co-authored-by: dogi --- app/build.gradle | 4 +- .../myplanet/ui/courses/CourseStepFragment.kt | 4 +- .../myplanet/ui/exam/TakeExamFragment.kt | 4 +- .../planet/myplanet/utilities/CameraUtils.kt | 77 ++++++++++++------- 4 files changed, 56 insertions(+), 33 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 68f3233ff4..1997250c14 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 21 targetSdkVersion 34 - versionCode 1656 - versionName "0.16.56" + versionCode 1657 + versionName "0.16.57" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/courses/CourseStepFragment.kt b/app/src/main/java/org/ole/planet/myplanet/ui/courses/CourseStepFragment.kt index fd61014b61..e2a44d1c8d 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/courses/CourseStepFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/courses/CourseStepFragment.kt @@ -25,7 +25,7 @@ import org.ole.planet.myplanet.model.RealmSubmission import org.ole.planet.myplanet.model.RealmUserModel import org.ole.planet.myplanet.service.UserProfileDbHandler import org.ole.planet.myplanet.ui.exam.TakeExamFragment -import org.ole.planet.myplanet.utilities.CameraUtils.CapturePhoto +import org.ole.planet.myplanet.utilities.CameraUtils.capturePhoto import org.ole.planet.myplanet.utilities.CameraUtils.ImageCaptureCallback import org.ole.planet.myplanet.utilities.Constants import org.ole.planet.myplanet.utilities.Constants.showBetaFeature @@ -164,7 +164,7 @@ class CourseStepFragment : BaseContainerFragment(), ImageCaptureCallback { takeExam.arguments = b homeItemClickListener?.openCallFragment(takeExam) context?.let { it1 -> - CapturePhoto(it1, object : ImageCaptureCallback { + capturePhoto(it1, object : ImageCaptureCallback { override fun onImageCapture(fileUri: String?) { } }) diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/exam/TakeExamFragment.kt b/app/src/main/java/org/ole/planet/myplanet/ui/exam/TakeExamFragment.kt index b698a9102f..e47cb79318 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/exam/TakeExamFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/exam/TakeExamFragment.kt @@ -21,7 +21,7 @@ import org.ole.planet.myplanet.model.RealmExamQuestion import org.ole.planet.myplanet.model.RealmSubmission import org.ole.planet.myplanet.model.RealmSubmission.Companion.createSubmission import org.ole.planet.myplanet.service.UserProfileDbHandler -import org.ole.planet.myplanet.utilities.CameraUtils.CapturePhoto +import org.ole.planet.myplanet.utilities.CameraUtils.capturePhoto import org.ole.planet.myplanet.utilities.CameraUtils.ImageCaptureCallback import org.ole.planet.myplanet.utilities.JsonParserUtils.getStringAsJsonArray import org.ole.planet.myplanet.utilities.JsonUtils.getString @@ -210,7 +210,7 @@ class TakeExamFragment : BaseExamFragment(), View.OnClickListener, CompoundButto try { if (isCertified && !isMySurvey) { context?.let { it1 -> - CapturePhoto(it1, object : ImageCaptureCallback { + capturePhoto(it1, object : ImageCaptureCallback { override fun onImageCapture(fileUri: String?) { } }) diff --git a/app/src/main/java/org/ole/planet/myplanet/utilities/CameraUtils.kt b/app/src/main/java/org/ole/planet/myplanet/utilities/CameraUtils.kt index 59efc46ee8..b9caaf7d66 100644 --- a/app/src/main/java/org/ole/planet/myplanet/utilities/CameraUtils.kt +++ b/app/src/main/java/org/ole/planet/myplanet/utilities/CameraUtils.kt @@ -6,24 +6,28 @@ import android.content.pm.PackageManager import android.graphics.ImageFormat import android.graphics.SurfaceTexture import android.hardware.camera2.* +import android.hardware.camera2.params.OutputConfiguration +import android.hardware.camera2.params.SessionConfiguration import android.media.ImageReader +import android.os.Build import android.os.Handler import android.os.HandlerThread -import android.util.Size import android.view.Surface import androidx.core.content.ContextCompat import java.io.File import java.io.FileOutputStream import java.util.* +import java.util.concurrent.Executors object CameraUtils { private var cameraDevice: CameraDevice? = null private var captureSession: CameraCaptureSession? = null private var imageReader: ImageReader? = null private var backgroundHandler: Handler - private var backgroundThread: HandlerThread + private var backgroundThread: HandlerThread = HandlerThread("CameraBackground") + @JvmStatic - fun CapturePhoto(context: Context, callback: ImageCaptureCallback) { + fun capturePhoto(context: Context, callback: ImageCaptureCallback) { if (ContextCompat.checkSelfPermission( context, Manifest.permission.CAMERA @@ -48,11 +52,7 @@ object CameraUtils { captureBuilder?.addTarget(imageReader!!.surface) captureBuilder?.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) - val captureCallback = object : CameraCaptureSession.CaptureCallback() { - override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) { - super.onCaptureCompleted(session, request, result) - } - } + val captureCallback = object : CameraCaptureSession.CaptureCallback() {} captureSession?.stopRepeating() captureSession?.abortCaptures() @@ -83,13 +83,6 @@ object CameraUtils { val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager try { val cameraId = manager.cameraIdList[0] // Assuming we want to use the first (rear) camera - val characteristics = manager.getCameraCharacteristics(cameraId) - val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) - val largest = Collections.max( - listOf(*map!!.getOutputSizes(ImageFormat.JPEG)), - CompareSizesByArea() - ) - val reader = ImageReader.newInstance(largest.width, largest.height, ImageFormat.JPEG, 2) manager.openCamera(cameraId, object : CameraDevice.StateCallback() { override fun onOpened(camera: CameraDevice) { cameraDevice = camera @@ -117,9 +110,10 @@ object CameraUtils { val surface = Surface(texture) val captureRequestBuilder = cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) captureRequestBuilder.addTarget(surface) - cameraDevice!!.createCaptureSession( - listOf(surface), - object : CameraCaptureSession.StateCallback() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + val outputConfigurations = listOf(OutputConfiguration(surface)) + val executor = Executors.newSingleThreadExecutor() + val stateCallback = object : CameraCaptureSession.StateCallback() { override fun onConfigured(session: CameraCaptureSession) { if (cameraDevice == null) return captureSession = session @@ -139,9 +133,44 @@ object CameraUtils { } override fun onConfigureFailed(session: CameraCaptureSession) {} - }, - backgroundHandler - ) + } + + val sessionConfiguration = SessionConfiguration( + SessionConfiguration.SESSION_REGULAR, + outputConfigurations, + executor, + stateCallback + ) + + cameraDevice!!.createCaptureSession(sessionConfiguration) + } else { + @Suppress("DEPRECATION") + cameraDevice!!.createCaptureSession( + listOf(surface), + object : CameraCaptureSession.StateCallback() { + override fun onConfigured(session: CameraCaptureSession) { + if (cameraDevice == null) return + captureSession = session + try { + captureRequestBuilder.set( + CaptureRequest.CONTROL_AF_MODE, + CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE + ) + captureSession!!.setRepeatingRequest( + captureRequestBuilder.build(), + null, + backgroundHandler + ) + } catch (e: CameraAccessException) { + e.printStackTrace() + } + } + + override fun onConfigureFailed(session: CameraCaptureSession) {} + }, + backgroundHandler + ) + } } catch (e: CameraAccessException) { e.printStackTrace() } @@ -151,13 +180,7 @@ object CameraUtils { fun onImageCapture(fileUri: String?) } - private class CompareSizesByArea : Comparator { - override fun compare(lhs: Size, rhs: Size): Int { - return java.lang.Long.signum(lhs.width.toLong() * lhs.height - rhs.width.toLong() * rhs.height) - } - } init { - backgroundThread = HandlerThread("CameraBackground") backgroundThread.start() backgroundHandler = Handler(backgroundThread.looper) } From 54f59ed8d3d92f5f864e0031ff7fe2603163e4c0 Mon Sep 17 00:00:00 2001 From: rlam20 <52076121+rlam20@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:46:37 -0400 Subject: [PATCH 3/4] feedback: smoother empty list (fixes #3726) (#3783) Co-authored-by: dogi --- app/build.gradle | 4 +- .../myplanet/base/BaseRecyclerFragment.kt | 1 + .../ui/feedback/FeedbackListFragment.kt | 37 ++++++++++++++----- .../res/layout/fragment_feedback_list.xml | 11 ++++-- app/src/main/res/values-ar/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-ne/strings.xml | 1 + app/src/main/res/values-so/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 10 files changed, 44 insertions(+), 15 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1997250c14..798be1c95d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 21 targetSdkVersion 34 - versionCode 1657 - versionName "0.16.57" + versionCode 1658 + versionName "0.16.58" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/base/BaseRecyclerFragment.kt b/app/src/main/java/org/ole/planet/myplanet/base/BaseRecyclerFragment.kt index cfb2abc90a..64dfbbb1f9 100644 --- a/app/src/main/java/org/ole/planet/myplanet/base/BaseRecyclerFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/base/BaseRecyclerFragment.kt @@ -298,6 +298,7 @@ abstract class BaseRecyclerFragment
  • : BaseRecyclerParentFragment(), On "submission" -> (v as TextView).setText(R.string.no_submissions) "teams" -> (v as TextView).setText(R.string.no_teams) "chatHistory" -> (v as TextView).setText(R.string.no_chats) + "feedback" -> (v as TextView).setText(R.string.no_feedback) else -> (v as TextView).setText(R.string.no_data_available_please_check_and_try_again) } } diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/feedback/FeedbackListFragment.kt b/app/src/main/java/org/ole/planet/myplanet/ui/feedback/FeedbackListFragment.kt index 09d5b33970..bf49250c18 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/feedback/FeedbackListFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/feedback/FeedbackListFragment.kt @@ -14,6 +14,7 @@ import org.ole.planet.myplanet.model.RealmFeedback import org.ole.planet.myplanet.model.RealmUserModel import org.ole.planet.myplanet.service.UserProfileDbHandler import org.ole.planet.myplanet.ui.feedback.FeedbackFragment.OnFeedbackSubmittedListener +import org.ole.planet.myplanet.base.BaseRecyclerFragment.Companion.showNoData class FeedbackListFragment : Fragment(), OnFeedbackSubmittedListener { private lateinit var fragmentFeedbackListBinding: FragmentFeedbackListBinding @@ -25,6 +26,7 @@ class FeedbackListFragment : Fragment(), OnFeedbackSubmittedListener { fragmentFeedbackListBinding = FragmentFeedbackListBinding.inflate(inflater, container, false) mRealm = DatabaseService(requireActivity()).realmInstance userModel = UserProfileDbHandler(requireContext()).userModel + fragmentFeedbackListBinding.fab.setOnClickListener { val feedbackFragment = FeedbackFragment() feedbackFragment.setOnFeedbackSubmittedListener(this) @@ -33,15 +35,13 @@ class FeedbackListFragment : Fragment(), OnFeedbackSubmittedListener { } } - mRealm.executeTransactionAsync( - Realm.Transaction { }, - Realm.Transaction.OnSuccess { - feedbackList = mRealm.where(RealmFeedback::class.java) - .equalTo("owner", userModel?.name).findAllAsync() - feedbackList?.addChangeListener { results -> - updatedFeedbackList(results) - } - }) + feedbackList = mRealm.where(RealmFeedback::class.java) + .equalTo("owner", userModel?.name).findAllAsync() + + feedbackList?.addChangeListener { results -> + updatedFeedbackList(results) + } + return fragmentFeedbackListBinding.root } @@ -53,6 +53,11 @@ class FeedbackListFragment : Fragment(), OnFeedbackSubmittedListener { if (userModel?.isManager() == true) list = mRealm.where(RealmFeedback::class.java).findAll() val adapterFeedback = AdapterFeedback(requireActivity(), list) fragmentFeedbackListBinding.rvFeedback.adapter = adapterFeedback + + val itemCount = feedbackList?.size ?: 0 + showNoData(fragmentFeedbackListBinding.tvMessage, itemCount, "feedback") + + updateTextViewsVisibility(itemCount) } override fun onDestroy() { @@ -74,11 +79,25 @@ class FeedbackListFragment : Fragment(), OnFeedbackSubmittedListener { updatedFeedbackList(updatedList) }) } + private fun updatedFeedbackList(updatedList: RealmResults?) { activity?.runOnUiThread { val adapterFeedback = updatedList?.let { AdapterFeedback(requireActivity(), it) } fragmentFeedbackListBinding.rvFeedback.adapter = adapterFeedback adapterFeedback?.notifyDataSetChanged() + + val itemCount = updatedList?.size ?: 0 + showNoData(fragmentFeedbackListBinding.tvMessage, itemCount, "feedback") + updateTextViewsVisibility(itemCount) } } + + private fun updateTextViewsVisibility(itemCount: Int) { + val visibility = if (itemCount == 0) View.GONE else View.VISIBLE + fragmentFeedbackListBinding.tvTitle.visibility = visibility + fragmentFeedbackListBinding.tvType.visibility = visibility + fragmentFeedbackListBinding.tvPriority.visibility = visibility + fragmentFeedbackListBinding.tvStatus.visibility = visibility + fragmentFeedbackListBinding.tvOpenDate.visibility = visibility + } } diff --git a/app/src/main/res/layout/fragment_feedback_list.xml b/app/src/main/res/layout/fragment_feedback_list.xml index b430b2fde6..a566f95885 100644 --- a/app/src/main/res/layout/fragment_feedback_list.xml +++ b/app/src/main/res/layout/fragment_feedback_list.xml @@ -22,7 +22,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - - - \ No newline at end of file + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 228ad83a45..1ff4fea205 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -1051,6 +1051,7 @@ كوكب %s التقديمات غير متاحة لا توجد محادثات سابقة + لا توجد تعليقات متاحة %s diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 47d2cb2eec..cc2c828a20 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1051,6 +1051,7 @@ %s Planeta envíos no disponibles no hay chats anteriores + No hay comentarios aquí %s diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 1c26669b65..e9f31a26a5 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1051,6 +1051,7 @@ %s planète soumissions non disponibles aucune discussion précédente + aucun commentaire disponible %s diff --git a/app/src/main/res/values-ne/strings.xml b/app/src/main/res/values-ne/strings.xml index c75d0b674e..855313a00a 100644 --- a/app/src/main/res/values-ne/strings.xml +++ b/app/src/main/res/values-ne/strings.xml @@ -1051,6 +1051,7 @@ %s ग्रह पेशाहरू उपलब्ध छैनन् अघिल्ला कुराकानीहरू छैनन् + कुनै प्रतिक्रिया उपलब्ध छैन %s diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index 38c3ada43d..ccedfc34df 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -1051,6 +1051,7 @@ %s Meerah soo gudbin lama heli karo ma jiraan wada sheekaysi hore + wax jawaab celin ah lama hayo %s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 599bfa36ff..f35f36b6e2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1051,6 +1051,7 @@ %s\'s Planet submissions not available no previous chats + no feedback available %s From 8f4c631d035e469e34f409289f309ad1eb9de554 Mon Sep 17 00:00:00 2001 From: Gideon Okuro Date: Mon, 8 Jul 2024 22:12:33 +0300 Subject: [PATCH 4/4] resources: safer transactions (fixes #3645) (#3785) Co-authored-by: dogi --- app/build.gradle | 4 +- .../ui/resources/ResourcesFragment.kt | 181 +++++++++--------- 2 files changed, 97 insertions(+), 88 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 798be1c95d..52eec789e6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 21 targetSdkVersion 34 - versionCode 1658 - versionName "0.16.58" + versionCode 1659 + versionName "0.16.59" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourcesFragment.kt b/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourcesFragment.kt index ce94d8315c..06afdf6c7c 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourcesFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourcesFragment.kt @@ -39,21 +39,22 @@ import java.util.UUID class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItemSelected, ChipDeletedListener, TagClickListener, OnFilterListener { - private var tvAddToLib: TextView? = null - private var tvSelected: TextView? = null - var etSearch: EditText? = null - private var etTags: EditText? = null - var adapterLibrary: AdapterResource? = null - private var flexBoxTags: FlexboxLayout? = null - lateinit var searchTags: MutableList - var config: ChipCloudConfig? = null - private var clearTags: Button? = null - private var orderByTitle: Button? = null - private var orderByDate: Button? = null - private var selectAll: CheckBox? = null + private lateinit var tvAddToLib: TextView + private lateinit var tvSelected: TextView + private lateinit var etSearch: EditText + private lateinit var etTags: EditText + private lateinit var flexBoxTags: FlexboxLayout + private lateinit var searchTags: MutableList + private lateinit var config: ChipCloudConfig + private lateinit var clearTags: Button + private lateinit var orderByTitle: Button + private lateinit var orderByDate: Button + private lateinit var selectAll: CheckBox + private lateinit var filter: ImageButton + private lateinit var adapterLibrary: AdapterResource var map: HashMap? = null private var confirmation: AlertDialog? = null - var filter: ImageButton? = null + override fun getLayout(): Int { return R.layout.fragment_my_library } @@ -62,34 +63,34 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem map = getRatings(mRealm, "resource", model?.id) val libraryList: List = getList(RealmMyLibrary::class.java).filterIsInstance() adapterLibrary = AdapterResource(requireActivity(), libraryList, map!!, mRealm) - adapterLibrary?.setRatingChangeListener(this) - adapterLibrary?.setListener(this) - return adapterLibrary!! + adapterLibrary.setRatingChangeListener(this) + adapterLibrary.setListener(this) + return adapterLibrary } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) searchTags = ArrayList() config = Utilities.getCloudConfig().showClose(R.color.black_overlay) - tvAddToLib = requireView().findViewById(R.id.tv_add) - etSearch = requireView().findViewById(R.id.et_search) - etTags = requireView().findViewById(R.id.et_tags) - clearTags = requireView().findViewById(R.id.btn_clear_tags) - tvSelected = requireView().findViewById(R.id.tv_selected) - flexBoxTags = requireView().findViewById(R.id.flexbox_tags) - selectAll = requireView().findViewById(R.id.selectAll) - tvDelete = requireView().findViewById(R.id.tv_delete) - filter = requireView().findViewById(R.id.filter) + tvAddToLib = view.findViewById(R.id.tv_add) + etSearch = view.findViewById(R.id.et_search) + etTags = view.findViewById(R.id.et_tags) + clearTags = view.findViewById(R.id.btn_clear_tags) + tvSelected = view.findViewById(R.id.tv_selected) + flexBoxTags = view.findViewById(R.id.flexbox_tags) + selectAll = view.findViewById(R.id.selectAll) + filter = view.findViewById(R.id.filter) + initArrays() updateTvDelete() - tvAddToLib?.setOnClickListener { + tvAddToLib.setOnClickListener { if ((selectedItems?.size ?: 0) > 0) { confirmation = createAlertDialog() confirmation?.show() addToMyList() selectedItems?.clear() - tvAddToLib?.isEnabled = false // After clearing selectedItems size is always 0 + tvAddToLib.isEnabled = false checkList() } } @@ -97,45 +98,54 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem tvDelete?.setOnClickListener { AlertDialog.Builder(this.context) .setMessage(R.string.confirm_removal) - .setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> + .setPositiveButton(R.string.yes) { _, _ -> deleteSelected(true) val newFragment = ResourcesFragment() recreateFragment(newFragment) } .setNegativeButton(R.string.no, null).show() } - etSearch?.addTextChangedListener(object : TextWatcher { + + etSearch.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - adapterLibrary?.setLibraryList(applyFilter(filterLibraryByTag(etSearch?.text.toString().trim { it <= ' ' }, searchTags))) - showNoData(tvMessage, adapterLibrary?.itemCount, "resources") + adapterLibrary.setLibraryList( + applyFilter( + filterLibraryByTag( + etSearch.text.toString().trim(), searchTags + ) + ) + ) + showNoData(tvMessage, adapterLibrary.itemCount, "resources") } override fun afterTextChanged(s: Editable) {} }) - requireView().findViewById(R.id.btn_collections).setOnClickListener { + + view.findViewById(R.id.btn_collections).setOnClickListener { val f = CollectionsFragment.getInstance(searchTags, "resources") f.setListener(this@ResourcesFragment) f.show(childFragmentManager, "") } - showNoData(tvMessage, adapterLibrary?.itemCount, "resources") + showNoData(tvMessage, adapterLibrary.itemCount, "resources") clearTagsButton() - setupUI(requireView().findViewById(R.id.my_library_parent_layout), requireActivity()) + setupUI(view.findViewById(R.id.my_library_parent_layout), requireActivity()) changeButtonStatus() additionalSetup() - tvFragmentInfo = requireView().findViewById(R.id.tv_fragment_info) + tvFragmentInfo = view.findViewById(R.id.tv_fragment_info) if (isMyCourseLib) tvFragmentInfo.setText(R.string.txt_myLibrary) checkList() - selectAll?.setOnClickListener { + + selectAll.setOnClickListener { updateTvDelete() - val allSelected = selectedItems?.size == adapterLibrary?.getLibraryList()?.size - adapterLibrary?.selectAllItems(!allSelected) + val allSelected = selectedItems?.size == adapterLibrary.getLibraryList().size + adapterLibrary.selectAllItems(!allSelected) if (allSelected) { - selectAll?.isChecked = false - selectAll?.text = getString(R.string.select_all) + selectAll.isChecked = false + selectAll.text = getString(R.string.select_all) } else { - selectAll?.isChecked = true - selectAll?.text = getString(R.string.unselect_all) + selectAll.isChecked = true + selectAll.text = getString(R.string.unselect_all) } } } @@ -149,14 +159,14 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem } private fun checkList() { - if (adapterLibrary?.getLibraryList()?.isEmpty() == true) { - selectAll?.visibility = View.GONE - etSearch?.visibility = View.GONE - tvAddToLib?.visibility = View.GONE - tvSelected?.visibility = View.GONE + if (adapterLibrary.getLibraryList().isEmpty()) { + selectAll.visibility = View.GONE + etSearch.visibility = View.GONE + tvAddToLib.visibility = View.GONE + tvSelected.visibility = View.GONE requireView().findViewById(R.id.btn_collections).visibility = View.GONE requireView().findViewById(R.id.filter).visibility = View.GONE - clearTags?.visibility = View.GONE + clearTags.visibility = View.GONE tvDelete?.visibility = View.GONE } } @@ -193,17 +203,17 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem } private fun clearTagsButton() { - clearTags?.setOnClickListener { + clearTags.setOnClickListener { saveSearchActivity() searchTags.clear() - etSearch?.setText("") - tvSelected?.text = "" + etSearch.setText("") + tvSelected.text = "" levels.clear() mediums.clear() subjects.clear() languages.clear() - adapterLibrary?.setLibraryList(applyFilter(filterLibraryByTag("", searchTags))) - showNoData(tvMessage, adapterLibrary?.itemCount, "resources") + adapterLibrary.setLibraryList(applyFilter(filterLibraryByTag("", searchTags))) + showNoData(tvMessage, adapterLibrary.itemCount, "resources") } } @@ -214,30 +224,30 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem } override fun onTagClicked(realmTag: RealmTag) { - flexBoxTags?.removeAllViews() + flexBoxTags.removeAllViews() val chipCloud = ChipCloud(activity, flexBoxTags, config) chipCloud.setDeleteListener(this) if (!searchTags.contains(realmTag)) searchTags.add(realmTag) chipCloud.addChips(searchTags) - adapterLibrary?.setLibraryList(applyFilter(filterLibraryByTag(etSearch?.text.toString(), searchTags))) + adapterLibrary.setLibraryList(applyFilter(filterLibraryByTag(etSearch.text.toString(), searchTags))) showTagText(searchTags, tvSelected) - showNoData(tvMessage, adapterLibrary?.itemCount, "resources") + showNoData(tvMessage, adapterLibrary.itemCount, "resources") } override fun onTagSelected(tag: RealmTag) { val li: MutableList = ArrayList() li.add(tag) searchTags = li - tvSelected?.text = "${getString(R.string.selected)}${tag.name}" - adapterLibrary?.setLibraryList(applyFilter(filterLibraryByTag(etSearch?.text.toString(), li))) - showNoData(tvMessage, adapterLibrary?.itemCount, "resources") + tvSelected.text = "${getString(R.string.selected)}${tag.name}" + adapterLibrary.setLibraryList(applyFilter(filterLibraryByTag(etSearch.text.toString(), li))) + showNoData(tvMessage, adapterLibrary.itemCount, "resources") } override fun onOkClicked(list: List?) { if (list?.isEmpty() == true) { searchTags.clear() - adapterLibrary?.setLibraryList(applyFilter(filterLibraryByTag(etSearch?.text.toString(), searchTags))) - showNoData(tvMessage, adapterLibrary?.itemCount, "resources") + adapterLibrary.setLibraryList(applyFilter(filterLibraryByTag(etSearch.text.toString(), searchTags))) + showNoData(tvMessage, adapterLibrary.itemCount, "resources") } else { for (tag in list ?: emptyList()) { onTagClicked(tag) @@ -246,20 +256,20 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem } private fun changeButtonStatus() { - tvAddToLib?.isEnabled = (selectedItems?.size ?: 0) > 0 - if (adapterLibrary?.areAllSelected() == true) { - selectAll?.isChecked = true - selectAll?.text = getString(R.string.unselect_all) + tvAddToLib.isEnabled = (selectedItems?.size ?: 0) > 0 + if (adapterLibrary.areAllSelected()) { + selectAll.isChecked = true + selectAll.text = getString(R.string.unselect_all) } else { - selectAll?.isChecked = false - selectAll?.text = getString(R.string.select_all) + selectAll.isChecked = false + selectAll.text = getString(R.string.select_all) } } override fun chipDeleted(i: Int, s: String) { searchTags.removeAt(i) - adapterLibrary?.setLibraryList(applyFilter(filterLibraryByTag(etSearch?.text.toString(), searchTags))) - showNoData(tvMessage, adapterLibrary?.itemCount, "resources") + adapterLibrary.setLibraryList(applyFilter(filterLibraryByTag(etSearch.text.toString(), searchTags))) + showNoData(tvMessage, adapterLibrary.itemCount, "resources") } override fun filter(subjects: MutableSet, languages: MutableSet, mediums: MutableSet, levels: MutableSet) { @@ -267,14 +277,14 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem this.languages = languages this.mediums = mediums this.levels = levels - adapterLibrary?.setLibraryList(applyFilter(filterLibraryByTag(etSearch?.text.toString().trim { it <= ' ' }, searchTags))) - showNoData(tvMessage, adapterLibrary?.itemCount, "resources") + adapterLibrary.setLibraryList(applyFilter(filterLibraryByTag(etSearch.text.toString().trim { it <= ' ' }, searchTags))) + showNoData(tvMessage, adapterLibrary.itemCount, "resources") } override fun getData(): Map> { - val libraryList = adapterLibrary?.getLibraryList()?.filterNotNull() + val libraryList = adapterLibrary.getLibraryList().filterNotNull() val b: MutableMap> = HashMap() - b["languages"] = libraryList?.let { getArrayList(it, "languages").filterNotNull().toSet() }!! + b["languages"] = libraryList.let { getArrayList(it, "languages").filterNotNull().toSet() } b["subjects"] = libraryList.let { getSubjects(it).toList().toSet() } b["mediums"] = getArrayList(libraryList, "mediums").filterNotNull().toSet() b["levels"] = getLevels(libraryList).toList().toSet() @@ -299,7 +309,7 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem private fun filterApplied(): Boolean { return !(subjects.isEmpty() && languages.isEmpty() && mediums.isEmpty() && levels.isEmpty() - && searchTags.isEmpty() && "${etSearch?.text}".isEmpty()) + && searchTags.isEmpty() && "${etSearch.text}".isEmpty()) } private fun saveSearchActivity() { @@ -310,7 +320,7 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem activity.time = Calendar.getInstance().timeInMillis activity.createdOn = model?.planetCode!! activity.parentCode = model?.parentCode!! - activity.text = etSearch?.text.toString() + activity.text = "${etSearch.text}" activity.type = "resources" val filter = JsonObject() filter.add("tags", getTagsArray(searchTags)) @@ -324,22 +334,21 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem } private fun recreateFragment(fragment: Fragment) { - if (isMyCourseLib) { - val args = Bundle() - args.putBoolean("isMyCourseLib", true) - fragment.arguments = args - val transaction = parentFragmentManager.beginTransaction() - transaction.replace(R.id.fragment_container, fragment) - transaction.addToBackStack(null) - transaction.commit() - } else { + if (isAdded && activity != null && !requireActivity().isFinishing) { val transaction = parentFragmentManager.beginTransaction() + if (isMyCourseLib) { + val args = Bundle().apply { + putBoolean("isMyCourseLib", true) + } + fragment.arguments = args + } transaction.replace(R.id.fragment_container, fragment) transaction.addToBackStack(null) transaction.commit() } } + private fun additionalSetup() { val bottomSheet = requireView().findViewById(R.id.card_filter) requireView().findViewById(R.id.filter).setOnClickListener { @@ -353,7 +362,7 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem f.show(childFragmentManager, "") bottomSheet.visibility = View.GONE } - orderByDate?.setOnClickListener { adapterLibrary?.toggleSortOrder() } - orderByTitle?.setOnClickListener { adapterLibrary?.toggleTitleSortOrder() } + orderByDate.setOnClickListener { adapterLibrary.toggleSortOrder() } + orderByTitle.setOnClickListener { adapterLibrary.toggleTitleSortOrder() } } } \ No newline at end of file