From 72e8757bb078923440d09b42ced6dcdb66340c19 Mon Sep 17 00:00:00 2001 From: DenBond7 Date: Mon, 8 Aug 2022 16:44:35 +0300 Subject: [PATCH 1/3] Improved sending feedbacks.| #1820 --- FlowCrypt/src/main/AndroidManifest.xml | 5 - .../email/api/retrofit/ApiRepository.kt | 13 ++ .../email/api/retrofit/ApiService.kt | 2 +- .../api/retrofit/FlowcryptApiRepository.kt | 13 ++ .../email/extensions/LifecycleOwnerExt.kt | 4 +- .../jetpack/viewmodel/LauncherViewModel.kt | 2 - .../viewmodel/SendFeedbackViewModel.kt | 65 +++++++ .../com/flowcrypt/email/model/Screenshot.kt | 47 +++++ .../email/service/FeedbackJobIntentService.kt | 170 ------------------ .../ui/activity/fragment/FeedbackFragment.kt | 55 ++---- .../dialog/SendFeedbackDialogFragment.kt | 123 +++++++++++++ .../layout/fragment_send_feedback_dialog.xml | 49 +++++ .../main/res/navigation/feedback_graph.xml | 24 ++- FlowCrypt/src/main/res/values-ru/strings.xml | 1 + FlowCrypt/src/main/res/values-uk/strings.xml | 1 + FlowCrypt/src/main/res/values/strings.xml | 1 + 16 files changed, 354 insertions(+), 221 deletions(-) create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SendFeedbackViewModel.kt create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/model/Screenshot.kt delete mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/service/FeedbackJobIntentService.kt create mode 100644 FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/SendFeedbackDialogFragment.kt create mode 100644 FlowCrypt/src/main/res/layout/fragment_send_feedback_dialog.xml diff --git a/FlowCrypt/src/main/AndroidManifest.xml b/FlowCrypt/src/main/AndroidManifest.xml index c91fcf1d18..25d2737cf5 100644 --- a/FlowCrypt/src/main/AndroidManifest.xml +++ b/FlowCrypt/src/main/AndroidManifest.xml @@ -165,11 +165,6 @@ android:exported="false" android:permission="android.permission.BIND_JOB_SERVICE" /> - - diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiRepository.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiRepository.kt index 63118b796c..61efd020e8 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiRepository.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiRepository.kt @@ -10,12 +10,14 @@ import com.flowcrypt.email.api.retrofit.base.BaseApiRepository import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel import com.flowcrypt.email.api.retrofit.request.model.LoginModel import com.flowcrypt.email.api.retrofit.request.model.MessageUploadRequest +import com.flowcrypt.email.api.retrofit.request.model.PostHelpFeedbackModel import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse import com.flowcrypt.email.api.retrofit.response.api.FesServerResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.api.MessageReplyTokenResponse import com.flowcrypt.email.api.retrofit.response.api.MessageUploadResponse +import com.flowcrypt.email.api.retrofit.response.api.PostHelpFeedbackResponse import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse import com.flowcrypt.email.api.retrofit.response.attester.PubResponse import com.flowcrypt.email.api.retrofit.response.attester.TestWelcomeResponse @@ -167,4 +169,15 @@ interface ApiRepository : BaseApiRepository { messageUploadRequest: MessageUploadRequest, msg: String ): Result + + /** + * Post a user feedback to our server + * + * @param context Interface to global information about an application environment. + * @param postHelpFeedbackModel an instance of [PostHelpFeedbackModel] + */ + suspend fun postHelpFeedback( + context: Context, + postHelpFeedbackModel: PostHelpFeedbackModel + ): Result } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiService.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiService.kt index 2dce1741a9..50f4b41f56 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiService.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/ApiService.kt @@ -93,7 +93,7 @@ interface ApiService { * @return [<] */ @POST(BuildConfig.API_URL + "help/feedback") - fun postHelpFeedback(@Body body: PostHelpFeedbackModel): Call + suspend fun postHelpFeedback(@Body body: PostHelpFeedbackModel): Response /** * This method create a [Call] object for the API "https://flowcrypt.com/attester/pub" diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/FlowcryptApiRepository.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/FlowcryptApiRepository.kt index eb2a8c864d..f7ccfa789f 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/FlowcryptApiRepository.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/retrofit/FlowcryptApiRepository.kt @@ -11,12 +11,14 @@ import com.flowcrypt.email.R import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel import com.flowcrypt.email.api.retrofit.request.model.LoginModel import com.flowcrypt.email.api.retrofit.request.model.MessageUploadRequest +import com.flowcrypt.email.api.retrofit.request.model.PostHelpFeedbackModel import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse import com.flowcrypt.email.api.retrofit.response.api.FesServerResponse import com.flowcrypt.email.api.retrofit.response.api.LoginResponse import com.flowcrypt.email.api.retrofit.response.api.MessageReplyTokenResponse import com.flowcrypt.email.api.retrofit.response.api.MessageUploadResponse +import com.flowcrypt.email.api.retrofit.response.api.PostHelpFeedbackResponse import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse import com.flowcrypt.email.api.retrofit.response.attester.PubResponse import com.flowcrypt.email.api.retrofit.response.attester.TestWelcomeResponse @@ -287,4 +289,15 @@ class FlowcryptApiRepository : ApiRepository { ) } } + + override suspend fun postHelpFeedback( + context: Context, + postHelpFeedbackModel: PostHelpFeedbackModel + ): Result = withContext(Dispatchers.IO) { + val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java) + getResult( + context = context, + expectedResultClass = PostHelpFeedbackResponse::class.java + ) { apiService.postHelpFeedback(postHelpFeedbackModel) } + } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/LifecycleOwnerExt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/LifecycleOwnerExt.kt index ced1fb9f32..522da571ac 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/LifecycleOwnerExt.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/LifecycleOwnerExt.kt @@ -12,7 +12,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.navigation.NavController import androidx.navigation.NavDirections import com.flowcrypt.email.R -import com.flowcrypt.email.ui.activity.fragment.FeedbackFragment +import com.flowcrypt.email.model.Screenshot import com.flowcrypt.email.ui.activity.fragment.FeedbackFragmentArgs import com.flowcrypt.email.ui.activity.fragment.dialog.FixNeedPassphraseIssueDialogFragment import com.flowcrypt.email.ui.activity.fragment.dialog.FixNeedPassphraseIssueDialogFragmentArgs @@ -146,7 +146,7 @@ fun LifecycleOwner.showFeedbackFragment( val navDirections = object : NavDirections { override val actionId: Int = R.id.feedback_graph override val arguments: Bundle = FeedbackFragmentArgs( - screenshot = FeedbackFragment.Screenshot(it) + screenshot = Screenshot(it) ).toBundle() } navController?.navigate(navDirections) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LauncherViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LauncherViewModel.kt index f7b93b92d2..31da13dc98 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LauncherViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/LauncherViewModel.kt @@ -12,7 +12,6 @@ import com.flowcrypt.email.R import com.flowcrypt.email.database.entity.AccountEntity import com.flowcrypt.email.jetpack.workmanager.ForwardedAttachmentsDownloaderWorker import com.flowcrypt.email.jetpack.workmanager.MessagesSenderWorker -import com.flowcrypt.email.service.FeedbackJobIntentService import com.flowcrypt.email.util.CacheManager import com.flowcrypt.email.util.FileAndDirectoryUtils import kotlinx.coroutines.flow.MutableStateFlow @@ -41,7 +40,6 @@ class LauncherViewModel(application: Application) : AccountViewModel(application ) ForwardedAttachmentsDownloaderWorker.enqueue(application) MessagesSenderWorker.enqueue(application) - FeedbackJobIntentService.enqueueWork(application) FileAndDirectoryUtils.cleanDir(CacheManager.getCurrentMsgTempDir()) isInitLoadingCompletedMutableStateFlow.value = diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SendFeedbackViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SendFeedbackViewModel.kt new file mode 100644 index 0000000000..52e72116e7 --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/SendFeedbackViewModel.kt @@ -0,0 +1,65 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.jetpack.viewmodel + +import android.app.Application +import android.content.Context +import android.util.Base64 +import androidx.lifecycle.viewModelScope +import com.flowcrypt.email.BuildConfig +import com.flowcrypt.email.R +import com.flowcrypt.email.api.retrofit.FlowcryptApiRepository +import com.flowcrypt.email.api.retrofit.request.model.PostHelpFeedbackModel +import com.flowcrypt.email.api.retrofit.response.api.PostHelpFeedbackResponse +import com.flowcrypt.email.api.retrofit.response.base.Result +import com.flowcrypt.email.database.entity.AccountEntity +import com.flowcrypt.email.model.Screenshot +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +/** + * @author Denis Bondarenko + * Date: 8/8/22 + * Time: 1:07 PM + * E-mail: DenBond7@gmail.com + */ +class SendFeedbackViewModel(application: Application) : BaseAndroidViewModel(application) { + private val repository = FlowcryptApiRepository() + private val postFeedbackMutableStateFlow: MutableStateFlow> = + MutableStateFlow(Result.none()) + val postFeedbackStateFlow: StateFlow> = + postFeedbackMutableStateFlow.asStateFlow() + + fun postFeedback( + account: AccountEntity, + feedbackMsg: String, + screenshot: Screenshot? = null + ) { + viewModelScope.launch { + val context: Context = getApplication() + postFeedbackMutableStateFlow.value = + Result.loading(progressMsg = context.getString(R.string.sending)) + val screenShotBase64 = + Base64.encodeToString(screenshot?.byteArray ?: byteArrayOf(), Base64.DEFAULT) + + try { + postFeedbackMutableStateFlow.value = repository.postHelpFeedback( + context = context, + PostHelpFeedbackModel( + email = account.email, + logs = "", + screenshot = screenShotBase64, + msg = "$feedbackMsg\n\nversion: Android ${BuildConfig.VERSION_NAME}" + ) + ) + } catch (e: Exception) { + postFeedbackMutableStateFlow.value = Result.exception(e) + } + } + } +} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/model/Screenshot.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/model/Screenshot.kt new file mode 100644 index 0000000000..dbc10a4967 --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/model/Screenshot.kt @@ -0,0 +1,47 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.model + +import android.os.Parcel +import android.os.Parcelable + +/** + * @author Denis Bondarenko + * Date: 8/8/22 + * Time: 2:23 PM + * E-mail: DenBond7@gmail.com + */ +data class Screenshot(val byteArray: ByteArray) : Parcelable { + constructor(parcel: Parcel) : this(parcel.createByteArray() ?: byteArrayOf()) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Screenshot + + if (!byteArray.contentEquals(other.byteArray)) return false + + return true + } + + override fun hashCode(): Int { + return byteArray.contentHashCode() + } + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeByteArray(byteArray) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = Screenshot(parcel) + override fun newArray(size: Int): Array = arrayOfNulls(size) + } +} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/service/FeedbackJobIntentService.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/service/FeedbackJobIntentService.kt deleted file mode 100644 index 5ddb5b0664..0000000000 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/service/FeedbackJobIntentService.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com - * Contributors: DenBond7 - */ - -package com.flowcrypt.email.service - -import android.content.Context -import android.content.Intent -import android.util.Base64 -import androidx.core.app.JobIntentService -import com.flowcrypt.email.BuildConfig -import com.flowcrypt.email.api.retrofit.ApiHelper -import com.flowcrypt.email.api.retrofit.ApiService -import com.flowcrypt.email.api.retrofit.request.model.PostHelpFeedbackModel -import com.flowcrypt.email.database.entity.AccountEntity -import com.flowcrypt.email.jobscheduler.JobIdManager -import com.flowcrypt.email.util.GeneralUtil -import com.flowcrypt.email.util.cache.DiskLruCache -import com.google.gson.GsonBuilder -import okhttp3.internal.io.FileSystem -import okio.buffer -import java.io.File -import java.io.InputStreamReader -import java.nio.charset.StandardCharsets -import java.util.UUID - -/** - * This service sends a user feedback to our API - * - * @author Denis Bondarenko - * Date: 9/3/19 - * Time: 9:09 AM - * E-mail: DenBond7@gmail.com - */ -class FeedbackJobIntentService : JobIntentService() { - private lateinit var diskLruCache: DiskLruCache - private val gson = GsonBuilder().create() - - override fun onCreate() { - super.onCreate() - initFeedbackCache(this) - } - - private fun initFeedbackCache(context: Context) { - diskLruCache = DiskLruCache( - FileSystem.SYSTEM, - File(context.cacheDir, CACHE_DIR_NAME), - CACHE_VERSION, - CACHE_SIZE - ) - } - - override fun onHandleWork(intent: Intent) { - addFeedbackFromIntentToCache(intent) - - if (GeneralUtil.isConnected(this)) { - sendCachedFeedback() - } - } - - private fun addFeedbackFromIntentToCache(intent: Intent) { - val account = intent.getParcelableExtra(EXTRA_KEY_ACCOUNT) - val feedbackMsg = intent.getStringExtra(EXTRA_KEY_FEEDBACK_MSG) - val screenShotBytes = intent.getByteArrayExtra(EXTRA_KEY_SCREENSHOT_BYTES) - val screenShotBase64 = Base64.encodeToString(screenShotBytes ?: byteArrayOf(), Base64.DEFAULT) - - feedbackMsg?.let { - addFeedbackToCache( - UUID.randomUUID().toString(), - FeedBackItem(account?.email, feedbackMsg, screenShotBase64) - ) - } - } - - private fun addFeedbackToCache(key: String, feedBackItem: FeedBackItem) { - val editor = diskLruCache.edit(key) ?: return - - val bufferedSink = editor.newSink().buffer() - bufferedSink.writeString(gson.toJson(feedBackItem), StandardCharsets.UTF_8) - bufferedSink.flush() - editor.commit() - bufferedSink.close() - } - - private fun sendCachedFeedback() { - val itemsIterator = diskLruCache.snapshots() - val apiService = ApiHelper.getInstance(this).retrofit.create(ApiService::class.java) - - while (itemsIterator.hasNext()) { - val item = itemsIterator.next() - val bufferedSource = item.getSource(0).buffer() - val inputStreamReader = InputStreamReader(bufferedSource.inputStream()) - val feedBackItem = gson.fromJson(inputStreamReader, FeedBackItem::class.java) - bufferedSource.close() - - val response = with(feedBackItem) { - apiService - .postHelpFeedback( - PostHelpFeedbackModel( - email = email ?: "", - logs = "", - screenshot = screenShot, - msg = "$feedbackMsg\n\nversion: Android ${BuildConfig.VERSION_NAME}" - ) - ) - .execute() - } - - if (response.isSuccessful) { - val body = response.body() - if (body?.isSent == true) { - diskLruCache.remove(item.key()) - } - } - } - } - - companion object { - private val EXTRA_KEY_ACCOUNT = - GeneralUtil.generateUniqueExtraKey("EXTRA_KEY_ACCOUNT", FeedbackJobIntentService::class.java) - private val EXTRA_KEY_FEEDBACK_MSG = - GeneralUtil.generateUniqueExtraKey( - "EXTRA_KEY_FEEDBACK_MSG", - FeedbackJobIntentService::class.java - ) - private val EXTRA_KEY_SCREENSHOT_BYTES = - GeneralUtil.generateUniqueExtraKey( - "EXTRA_KEY_SCREENSHOT_BYTES", - FeedbackJobIntentService::class.java - ) - - private const val CACHE_VERSION = 1 - private const val CACHE_SIZE: Long = 1024 * 1000 * 3 //3Mb - private const val CACHE_DIR_NAME = "feedback" - - /** - * Enqueue a new task for [FeedbackJobIntentService]. Set the feedback param to null to - * enqueue checking of non-sent feedbacks - * - * @param context Interface to global information about an application environment. - * @param account An active account. - * @param userComment A feedback which will be sent. - * @param screenShotBytes A screenshot bytes array. - */ - @JvmStatic - fun enqueueWork( - context: Context, account: AccountEntity? = null, userComment: String? = null, - screenShotBytes: ByteArray? = null - ) { - val intent = Intent(context, FeedbackJobIntentService::class.java) - intent.putExtra(EXTRA_KEY_ACCOUNT, account) - intent.putExtra(EXTRA_KEY_FEEDBACK_MSG, userComment) - intent.putExtra(EXTRA_KEY_SCREENSHOT_BYTES, screenShotBytes) - enqueueWork( - context, FeedbackJobIntentService::class.java, - JobIdManager.JOB_TYPE_FEEDBACK, intent - ) - } - } - - /** - * It's data class which describes info about a feedback - */ - data class FeedBackItem( - val email: String?, - val feedbackMsg: String, - val screenShot: String? - ) -} diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/FeedbackFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/FeedbackFragment.kt index deaff39d1d..f830a8ff3c 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/FeedbackFragment.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/FeedbackFragment.kt @@ -8,8 +8,6 @@ package com.flowcrypt.email.ui.activity.fragment import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Bundle -import android.os.Parcel -import android.os.Parcelable import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater @@ -29,9 +27,10 @@ import com.flowcrypt.email.extensions.gone import com.flowcrypt.email.extensions.navController import com.flowcrypt.email.extensions.toast import com.flowcrypt.email.extensions.visibleOrGone -import com.flowcrypt.email.service.FeedbackJobIntentService +import com.flowcrypt.email.model.Screenshot import com.flowcrypt.email.ui.activity.fragment.base.BaseFragment import com.flowcrypt.email.ui.activity.fragment.dialog.EditScreenshotDialogFragment +import com.flowcrypt.email.ui.activity.fragment.dialog.SendFeedbackDialogFragment import com.flowcrypt.email.util.GeneralUtil import com.flowcrypt.email.util.UIUtil @@ -61,6 +60,7 @@ class FeedbackFragment : BaseFragment() { super.onViewCreated(view, savedInstanceState) initViews() subscribeToEditScreenshot() + subscribeToSendFeedback() } override fun onSetupActionBarMenu(menuHost: MenuHost) { @@ -105,14 +105,13 @@ class FeedbackFragment : BaseFragment() { val nonNullAccount = account ?: AccountEntity(email = binding?.editTextUserEmail?.text.toString()) - FeedbackJobIntentService.enqueueWork( - context = requireContext(), - account = nonNullAccount, - userComment = binding?.editTextUserMessage?.text.toString(), - screenShotBytes = screenShotBytes + navController?.navigate( + FeedbackFragmentDirections.actionFeedbackFragmentToSendFeedbackDialogFragment( + nonNullAccount, + binding?.editTextUserMessage?.text.toString(), + screenShotBytes?.let { Screenshot(it) } + ) ) - toast(getString(R.string.thank_you_for_feedback)) - navController?.navigateUp() } return true } @@ -160,35 +159,13 @@ class FeedbackFragment : BaseFragment() { } } - data class Screenshot(val byteArray: ByteArray) : Parcelable { - constructor(parcel: Parcel) : this(parcel.createByteArray() ?: byteArrayOf()) - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as Screenshot - - if (!byteArray.contentEquals(other.byteArray)) return false - - return true - } - - override fun hashCode(): Int { - return byteArray.contentHashCode() - } - - override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeByteArray(byteArray) - } - - override fun describeContents(): Int { - return 0 - } - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel) = Screenshot(parcel) - override fun newArray(size: Int): Array = arrayOfNulls(size) + private fun subscribeToSendFeedback() { + setFragmentResultListener(SendFeedbackDialogFragment.REQUEST_KEY_RESULT) { _, bundle -> + val isSent = bundle.getBoolean(SendFeedbackDialogFragment.KEY_RESULT) + if (isSent) { + toast(getString(R.string.thank_you_for_feedback)) + navController?.navigateUp() + } } } } diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/SendFeedbackDialogFragment.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/SendFeedbackDialogFragment.kt new file mode 100644 index 0000000000..c76f6e4f9e --- /dev/null +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/ui/activity/fragment/dialog/SendFeedbackDialogFragment.kt @@ -0,0 +1,123 @@ +/* + * © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com + * Contributors: DenBond7 + */ + +package com.flowcrypt.email.ui.activity.fragment.dialog + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import androidx.core.os.bundleOf +import androidx.fragment.app.setFragmentResult +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.navArgs +import com.flowcrypt.email.R +import com.flowcrypt.email.api.retrofit.response.base.Result +import com.flowcrypt.email.databinding.FragmentSendFeedbackDialogBinding +import com.flowcrypt.email.extensions.countingIdlingResource +import com.flowcrypt.email.extensions.decrementSafely +import com.flowcrypt.email.extensions.gone +import com.flowcrypt.email.extensions.incrementSafely +import com.flowcrypt.email.extensions.navController +import com.flowcrypt.email.extensions.visible +import com.flowcrypt.email.jetpack.viewmodel.SendFeedbackViewModel +import com.flowcrypt.email.util.exception.ApiException + +/** + * @author Denis Bondarenko + * Date: 8/8/22 + * Time: 1:37 PM + * E-mail: DenBond7@gmail.com + */ +class SendFeedbackDialogFragment : BaseDialogFragment() { + private var binding: FragmentSendFeedbackDialogBinding? = null + private val args by navArgs() + private val sendFeedbackViewModel: SendFeedbackViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setupSendFeedbackViewModel() + sendFeedback() + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + binding = FragmentSendFeedbackDialogBinding.inflate( + LayoutInflater.from(requireContext()), + if ((view != null) and (view is ViewGroup)) view as ViewGroup? else null, + false + ) + + binding?.btRetry?.setOnClickListener { + sendFeedback() + } + + val builder = AlertDialog.Builder(requireContext()).apply { + setView(binding?.root) + setNegativeButton(R.string.cancel) { _, _ -> + navController?.navigateUp() + } + } + + return builder.create() + } + + private fun sendFeedback() { + sendFeedbackViewModel.postFeedback(args.accountEntity, args.feedbackMsg, args.screenshot) + } + + private fun setupSendFeedbackViewModel() { + lifecycleScope.launchWhenStarted { + sendFeedbackViewModel.postFeedbackStateFlow.collect { + when (it.status) { + Result.Status.LOADING -> { + countingIdlingResource?.incrementSafely() + binding?.pBLoading?.visible() + binding?.btRetry?.gone() + binding?.tVStatusMessage?.textAlignment = View.TEXT_ALIGNMENT_CENTER + binding?.tVStatusMessage?.text = it.progressMsg + } + + Result.Status.SUCCESS -> { + navController?.navigateUp() + setFragmentResult( + REQUEST_KEY_RESULT, + bundleOf(KEY_RESULT to (it.data?.isSent == true)) + ) + countingIdlingResource?.decrementSafely() + } + + Result.Status.EXCEPTION, Result.Status.ERROR -> { + binding?.pBLoading?.gone() + binding?.btRetry?.visible() + + val exception = it.exception ?: ApiException(it.data?.apiError) + val errorMsg = if (exception.message.isNullOrEmpty()) { + exception.javaClass.simpleName + } else exception.message + + binding?.tVStatusMessage?.textAlignment = View.TEXT_ALIGNMENT_TEXT_START + binding?.tVStatusMessage?.text = getString( + R.string.send_feedback_failed_hint, + getString(R.string.support_email), + errorMsg + ) + + countingIdlingResource?.decrementSafely() + } + + else -> {} + } + } + } + } + + companion object { + const val REQUEST_KEY_RESULT = "REQUEST_KEY_RESULT" + const val KEY_RESULT = "KEY_RESULT" + } +} diff --git a/FlowCrypt/src/main/res/layout/fragment_send_feedback_dialog.xml b/FlowCrypt/src/main/res/layout/fragment_send_feedback_dialog.xml new file mode 100644 index 0000000000..a55dd5494c --- /dev/null +++ b/FlowCrypt/src/main/res/layout/fragment_send_feedback_dialog.xml @@ -0,0 +1,49 @@ + + + + + + + +