Migrate Feedback module from Java to Kt (#5985)

* Rename `.java` to `.kt`

* Migrated FeedbackContentCreator to kotlin

* Rename FeedbackDialog from `java` to `kt`

* Migrated Feedback Dialog from `java` to `kt`

* Renamed OnFeedbackSubmitCallback to kotlij

* Migrated OnFeedbackSubmitCallback to kotlin

* Fixed: TestCase Failure

* Fixed Test : Changed Private Modifier to Public

* Suppressed deprecated and added TODO for lint

* Linked Deprecation with Github Issue

* Rename Feedback from java to kt

* Migrated Feedback Data Class to Kotlin

* Modified the data class to var for mutuability
This commit is contained in:
Neel Doshi 2024-12-07 12:49:00 +05:30 committed by GitHub
parent 015c5d5c63
commit 64fd10d00e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 252 additions and 387 deletions

View file

@ -1,120 +0,0 @@
package fr.free.nrw.commons.feedback;
import android.content.Context;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AccountUtilKt;
import fr.free.nrw.commons.feedback.model.Feedback;
import fr.free.nrw.commons.utils.LangCodeUtils;
import java.util.Locale;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
/**
* Creates a wikimedia recognizable format
* from feedback information
*/
public class FeedbackContentCreator {
private StringBuilder sectionTextBuilder;
private StringBuilder sectionTitleBuilder;
private Feedback feedback;
private Context context;
public FeedbackContentCreator(Context context, Feedback feedback) {
this.feedback = feedback;
this.context = context;
init();
}
/**
* Initializes the string buffer object to append content from feedback object
*/
public void init() {
// Localization is not needed here, because this ends up on a page where developers read the feedback, so English is the most convenient.
/*
* Construct the feedback section title
*/
//Get the UTC Date and Time and add it to the Title
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.ENGLISH);
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
final String UTC_FormattedDate = dateFormat.format(new Date());
sectionTitleBuilder = new StringBuilder();
sectionTitleBuilder.append("Feedback from ");
sectionTitleBuilder.append(AccountUtilKt.getUserName(context));
sectionTitleBuilder.append(" for version ");
sectionTitleBuilder.append(feedback.getVersion());
sectionTitleBuilder.append(" on ");
sectionTitleBuilder.append(UTC_FormattedDate);
/*
* Construct the feedback section text
*/
sectionTextBuilder = new StringBuilder();
sectionTextBuilder.append("\n");
sectionTextBuilder.append(feedback.getTitle());
sectionTextBuilder.append("\n");
sectionTextBuilder.append("\n");
if (feedback.getApiLevel() != null) {
sectionTextBuilder.append("* ");
sectionTextBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.api_level));
sectionTextBuilder.append(": ");
sectionTextBuilder.append(feedback.getApiLevel());
sectionTextBuilder.append("\n");
}
if (feedback.getAndroidVersion() != null) {
sectionTextBuilder.append("* ");
sectionTextBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.android_version));
sectionTextBuilder.append(": ");
sectionTextBuilder.append(feedback.getAndroidVersion());
sectionTextBuilder.append("\n");
}
if (feedback.getDeviceManufacturer() != null) {
sectionTextBuilder.append("* ");
sectionTextBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.device_manufacturer));
sectionTextBuilder.append(": ");
sectionTextBuilder.append(feedback.getDeviceManufacturer());
sectionTextBuilder.append("\n");
}
if (feedback.getDeviceModel() != null) {
sectionTextBuilder.append("* ");
sectionTextBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.device_model));
sectionTextBuilder.append(": ");
sectionTextBuilder.append(feedback.getDeviceModel());
sectionTextBuilder.append("\n");
}
if (feedback.getDevice() != null) {
sectionTextBuilder.append("* ");
sectionTextBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.device_name));
sectionTextBuilder.append(": ");
sectionTextBuilder.append(feedback.getDevice());
sectionTextBuilder.append("\n");
}
if (feedback.getNetworkType() != null) {
sectionTextBuilder.append("* ");
sectionTextBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.network_type));
sectionTextBuilder.append(": ");
sectionTextBuilder.append(feedback.getNetworkType());
sectionTextBuilder.append("\n");
}
sectionTextBuilder.append("~~~~");
sectionTextBuilder.append("\n");
}
public String getSectionText() {
return sectionTextBuilder.toString();
}
public String getSectionTitle() {
return sectionTitleBuilder.toString();
}
}

View file

@ -0,0 +1,123 @@
package fr.free.nrw.commons.feedback
import android.content.Context
import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.getUserName
import fr.free.nrw.commons.feedback.model.Feedback
import fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
class FeedbackContentCreator(context: Context, feedback: Feedback) {
private var sectionTitleBuilder = StringBuilder()
private var sectionTextBuilder = StringBuilder()
init {
// Localization is not needed here
// because this ends up on a page where developers read the feedback,
// so English is the most convenient.
//Get the UTC Date and Time and add it to the Title
val dateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.ENGLISH)
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
val utcFormattedDate = dateFormat.format(Date())
// Construct the feedback section title
sectionTitleBuilder.append("Feedback from ")
sectionTitleBuilder.append(getUserName(context))
sectionTitleBuilder.append(" for version ")
sectionTitleBuilder.append(feedback.version)
sectionTitleBuilder.append(" on ")
sectionTitleBuilder.append(utcFormattedDate)
// Construct the feedback section text
sectionTextBuilder = StringBuilder()
sectionTextBuilder.append("\n")
sectionTextBuilder.append(feedback.title)
sectionTextBuilder.append("\n")
sectionTextBuilder.append("\n")
if (feedback.apiLevel != null) {
sectionTextBuilder.append("* ")
sectionTextBuilder.append(
getLocalizedResources(
context,
Locale.ENGLISH
).getString(R.string.api_level)
)
sectionTextBuilder.append(": ")
sectionTextBuilder.append(feedback.apiLevel)
sectionTextBuilder.append("\n")
}
if (feedback.androidVersion != null) {
sectionTextBuilder.append("* ")
sectionTextBuilder.append(
getLocalizedResources(
context,
Locale.ENGLISH
).getString(R.string.android_version)
)
sectionTextBuilder.append(": ")
sectionTextBuilder.append(feedback.androidVersion)
sectionTextBuilder.append("\n")
}
if (feedback.deviceManufacturer != null) {
sectionTextBuilder.append("* ")
sectionTextBuilder.append(
getLocalizedResources(
context,
Locale.ENGLISH
).getString(R.string.device_manufacturer)
)
sectionTextBuilder.append(": ")
sectionTextBuilder.append(feedback.deviceManufacturer)
sectionTextBuilder.append("\n")
}
if (feedback.deviceModel != null) {
sectionTextBuilder.append("* ")
sectionTextBuilder.append(
getLocalizedResources(
context,
Locale.ENGLISH
).getString(R.string.device_model)
)
sectionTextBuilder.append(": ")
sectionTextBuilder.append(feedback.deviceModel)
sectionTextBuilder.append("\n")
}
if (feedback.device != null) {
sectionTextBuilder.append("* ")
sectionTextBuilder.append(
getLocalizedResources(
context,
Locale.ENGLISH
).getString(R.string.device_name)
)
sectionTextBuilder.append(": ")
sectionTextBuilder.append(feedback.device)
sectionTextBuilder.append("\n")
}
if (feedback.networkType != null) {
sectionTextBuilder.append("* ")
sectionTextBuilder.append(
getLocalizedResources(
context,
Locale.ENGLISH
).getString(R.string.network_type)
)
sectionTextBuilder.append(": ")
sectionTextBuilder.append(feedback.networkType)
sectionTextBuilder.append("\n")
}
sectionTextBuilder.append("~~~~")
sectionTextBuilder.append("\n")
}
fun getSectionText(): String {
return sectionTextBuilder.toString()
}
fun getSectionTitle(): String {
return sectionTitleBuilder.toString()
}
}

View file

@ -1,75 +0,0 @@
package fr.free.nrw.commons.feedback;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.text.Html;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.databinding.DialogFeedbackBinding;
import fr.free.nrw.commons.feedback.model.Feedback;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.DeviceInfoUtil;
import java.util.Objects;
/**
* Feedback dialog that asks user for message and
* other device specifications
*/
public class FeedbackDialog extends Dialog {
DialogFeedbackBinding dialogFeedbackBinding;
private OnFeedbackSubmitCallback onFeedbackSubmitCallback;
private Spanned feedbackDestinationHtml;
public FeedbackDialog(Context context, OnFeedbackSubmitCallback onFeedbackSubmitCallback) {
super(context);
this.onFeedbackSubmitCallback = onFeedbackSubmitCallback;
feedbackDestinationHtml = Html.fromHtml(context.getString(R.string.feedback_destination_note));
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dialogFeedbackBinding = DialogFeedbackBinding.inflate(getLayoutInflater());
dialogFeedbackBinding.feedbackDestination.setText(feedbackDestinationHtml);
dialogFeedbackBinding.feedbackDestination.setMovementMethod(LinkMovementMethod.getInstance());
Objects.requireNonNull(getWindow()).setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
final View view = dialogFeedbackBinding.getRoot();
setContentView(view);
dialogFeedbackBinding.btnSubmitFeedback.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
submitFeedback();
}
});
}
/**
* When the button is clicked, it will create a feedback object
* and give a callback to calling activity/fragment
*/
void submitFeedback() {
if(dialogFeedbackBinding.feedbackItemEditText.getText().toString().equals("")) {
dialogFeedbackBinding.feedbackItemEditText.setError(getContext().getString(R.string.enter_description));
return;
}
String appVersion = ConfigUtils.getVersionNameWithSha(getContext());
String androidVersion = dialogFeedbackBinding.androidVersionCheckbox.isChecked() ? DeviceInfoUtil.getAndroidVersion() : null;
String apiLevel = dialogFeedbackBinding.apiLevelCheckbox.isChecked() ? DeviceInfoUtil.getAPILevel() : null;
String deviceManufacturer = dialogFeedbackBinding.deviceManufacturerCheckbox.isChecked() ? DeviceInfoUtil.getDeviceManufacturer() : null;
String deviceModel = dialogFeedbackBinding.deviceModelCheckbox.isChecked() ? DeviceInfoUtil.getDeviceModel() : null;
String deviceName = dialogFeedbackBinding.deviceNameCheckbox.isChecked() ? DeviceInfoUtil.getDevice() : null;
String networkType = dialogFeedbackBinding.networkTypeCheckbox.isChecked() ? DeviceInfoUtil.getConnectionType(getContext()).toString() : null;
Feedback feedback = new Feedback(appVersion, apiLevel
, dialogFeedbackBinding.feedbackItemEditText.getText().toString()
, androidVersion, deviceModel, deviceManufacturer, deviceName, networkType);
onFeedbackSubmitCallback.onFeedbackSubmit(feedback);
dismiss();
}
}

View file

@ -0,0 +1,82 @@
package fr.free.nrw.commons.feedback
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.text.Html
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.view.WindowManager
import fr.free.nrw.commons.R
import fr.free.nrw.commons.databinding.DialogFeedbackBinding
import fr.free.nrw.commons.feedback.model.Feedback
import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
import fr.free.nrw.commons.utils.DeviceInfoUtil.getAPILevel
import fr.free.nrw.commons.utils.DeviceInfoUtil.getAndroidVersion
import fr.free.nrw.commons.utils.DeviceInfoUtil.getConnectionType
import fr.free.nrw.commons.utils.DeviceInfoUtil.getDevice
import fr.free.nrw.commons.utils.DeviceInfoUtil.getDeviceManufacturer
import fr.free.nrw.commons.utils.DeviceInfoUtil.getDeviceModel
class FeedbackDialog(
context: Context,
private val onFeedbackSubmitCallback: OnFeedbackSubmitCallback) : Dialog(context) {
private var _binding: DialogFeedbackBinding? = null
private val binding get() = _binding!!
// TODO("Remove Deprecation") Issue : #6002
// 'fromHtml(String!): Spanned!' is deprecated. Deprecated in Java
@Suppress("DEPRECATION")
private var feedbackDestinationHtml: Spanned = Html.fromHtml(
context.getString(R.string.feedback_destination_note))
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = DialogFeedbackBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.feedbackDestination.text = feedbackDestinationHtml
binding.feedbackDestination.movementMethod = LinkMovementMethod.getInstance()
// TODO("DEPRECATION") Issue : #6002
// 'SOFT_INPUT_ADJUST_RESIZE: Int' is deprecated. Deprecated in Java
@Suppress("DEPRECATION")
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
binding.btnSubmitFeedback.setOnClickListener {
submitFeedback()
}
}
fun submitFeedback() {
if (binding.feedbackItemEditText.getText().toString() == "") {
binding.feedbackItemEditText.error = context.getString(R.string.enter_description)
return
}
val appVersion = context.getVersionNameWithSha()
val androidVersion =
if (binding.androidVersionCheckbox.isChecked) getAndroidVersion() else null
val apiLevel =
if (binding.apiLevelCheckbox.isChecked) getAPILevel() else null
val deviceManufacturer =
if (binding.deviceManufacturerCheckbox.isChecked) getDeviceManufacturer() else null
val deviceModel =
if (binding.deviceModelCheckbox.isChecked) getDeviceModel() else null
val deviceName =
if (binding.deviceNameCheckbox.isChecked) getDevice() else null
val networkType =
if (binding.networkTypeCheckbox.isChecked) getConnectionType(
context
).toString() else null
val feedback = Feedback(
appVersion, apiLevel,
binding.feedbackItemEditText.getText().toString(),
androidVersion, deviceModel, deviceManufacturer, deviceName, networkType
)
onFeedbackSubmitCallback.onFeedbackSubmit(feedback)
dismiss()
}
override fun dismiss() {
super.dismiss()
_binding = null
}
}

View file

@ -1,15 +0,0 @@
package fr.free.nrw.commons.feedback;
import fr.free.nrw.commons.feedback.model.Feedback;
/**
* This interface is used to provide callback
* from Feedback dialog whenever submit button is clicked
*/
public interface OnFeedbackSubmitCallback {
/**
* callback function, called when user clicks on submit
*/
void onFeedbackSubmit(Feedback feedback);
}

View file

@ -0,0 +1,7 @@
package fr.free.nrw.commons.feedback
import fr.free.nrw.commons.feedback.model.Feedback
interface OnFeedbackSubmitCallback {
fun onFeedbackSubmit(feedback: Feedback)
}

View file

@ -1,171 +0,0 @@
package fr.free.nrw.commons.feedback.model;
/**
* Pojo class for storing information that are required while uploading a feedback
*/
public class Feedback {
/**
* Version of app
*/
private String version;
/**
* API level of user's phone
*/
private String apiLevel;
/**
* Title/Description entered by user
*/
private String title;
/**
* Android version of user's device
*/
private String androidVersion;
/**
* Device Model of user's device
*/
private String deviceModel;
/**
* Device manufacturer name
*/
private String deviceManufacturer;
/**
* Device name stored on user's device
*/
private String device;
/**
* network type user is having (Ex: Wifi)
*/
private String networkType;
public Feedback(final String version, final String apiLevel, final String title, final String androidVersion,
final String deviceModel, final String deviceManufacturer, final String device, final String networkType
) {
this.version = version;
this.apiLevel = apiLevel;
this.title = title;
this.androidVersion = androidVersion;
this.deviceModel = deviceModel;
this.deviceManufacturer = deviceManufacturer;
this.device = device;
this.networkType = networkType;
}
/**
* Get the version from which this piece of feedback is being sent.
* Ex: 3.0.1
*/
public String getVersion() {
return version;
}
/**
* Set the version of app to given version
*/
public void setVersion(final String version) {
this.version = version;
}
/**
* gets api level of device
* Ex: 28
*/
public String getApiLevel() {
return apiLevel;
}
/**
* sets api level value to given value
*/
public void setApiLevel(final String apiLevel) {
this.apiLevel = apiLevel;
}
/**
* gets feedback text entered by user
*/
public String getTitle() {
return title;
}
/**
* sets feedback text
*/
public void setTitle(final String title) {
this.title = title;
}
/**
* gets android version of device
* Ex: 9
*/
public String getAndroidVersion() {
return androidVersion;
}
/**
* sets value of android version
*/
public void setAndroidVersion(final String androidVersion) {
this.androidVersion = androidVersion;
}
/**
* get device model of current device
* Ex: Redmi 6 Pro
*/
public String getDeviceModel() {
return deviceModel;
}
/**
* sets value of device model to a given value
*/
public void setDeviceModel(final String deviceModel) {
this.deviceModel = deviceModel;
}
/**
* get device manufacturer of user's device
* Ex: Redmi
*/
public String getDeviceManufacturer() {
return deviceManufacturer;
}
/**
* set device manufacturer value to a given value
*/
public void setDeviceManufacturer(final String deviceManufacturer) {
this.deviceManufacturer = deviceManufacturer;
}
/**
* get device name of user's device
*/
public String getDevice() {
return device;
}
/**
* sets device name value to a given value
*/
public void setDevice(final String device) {
this.device = device;
}
/**
* get network type of user's network
* Ex: wifi
*/
public String getNetworkType() {
return networkType;
}
/**
* sets network type to a given value
*/
public void setNetworkType(final String networkType) {
this.networkType = networkType;
}
}

View file

@ -0,0 +1,30 @@
package fr.free.nrw.commons.feedback.model
/**
* Pojo class for storing information that are required while uploading a feedback
*/
data class Feedback (
// Version of app
var version : String? = null,
// API level of user's phone
var apiLevel: String? = null,
// Title/Description entered by user
var title: String? = null,
// Android version of user's device
var androidVersion: String? = null,
// Device Model of user's device
var deviceModel: String? = null,
// Device manufacturer name
var deviceManufacturer: String? = null,
// Device name stored on user's device
var device: String? = null,
// network type user is having (Ex: Wifi)
var networkType: String? = null
)

View file

@ -10,7 +10,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@ -25,6 +24,7 @@ import fr.free.nrw.commons.databinding.FragmentMoreBottomSheetBinding
import fr.free.nrw.commons.di.ApplicationlessInjection import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.feedback.FeedbackContentCreator import fr.free.nrw.commons.feedback.FeedbackContentCreator
import fr.free.nrw.commons.feedback.FeedbackDialog import fr.free.nrw.commons.feedback.FeedbackDialog
import fr.free.nrw.commons.feedback.OnFeedbackSubmitCallback
import fr.free.nrw.commons.feedback.model.Feedback import fr.free.nrw.commons.feedback.model.Feedback
import fr.free.nrw.commons.kvstore.BasicKvStore import fr.free.nrw.commons.kvstore.BasicKvStore
import fr.free.nrw.commons.kvstore.JsonKvStore import fr.free.nrw.commons.kvstore.JsonKvStore
@ -156,7 +156,11 @@ class MoreBottomSheetFragment : BottomSheetDialogFragment() {
* Creates and shows a dialog asking feedback from users * Creates and shows a dialog asking feedback from users
*/ */
private fun showFeedbackDialog() { private fun showFeedbackDialog() {
FeedbackDialog(requireContext()) { uploadFeedback(it) }.show() FeedbackDialog(requireContext(), object : OnFeedbackSubmitCallback{
override fun onFeedbackSubmit(feedback: Feedback) {
uploadFeedback(feedback)
}
}).show()
} }
/** /**
@ -168,8 +172,8 @@ class MoreBottomSheetFragment : BottomSheetDialogFragment() {
val single = pageEditClient.createNewSection( val single = pageEditClient.createNewSection(
"Commons:Mobile_app/Feedback", "Commons:Mobile_app/Feedback",
feedbackContentCreator.sectionTitle, feedbackContentCreator.getSectionTitle(),
feedbackContentCreator.sectionText, feedbackContentCreator.getSectionText(),
"New feedback on version ${feedback.version} of the app" "New feedback on version ${feedback.version} of the app"
) )
.flatMapSingle { Single.just(it) } .flatMapSingle { Single.just(it) }

View file

@ -34,7 +34,7 @@ class FeedbackDialogTests {
private lateinit var dialogFeedbackBinding: DialogFeedbackBinding private lateinit var dialogFeedbackBinding: DialogFeedbackBinding
@Mock @Mock
private val onFeedbackSubmitCallback: OnFeedbackSubmitCallback? = null private lateinit var onFeedbackSubmitCallback: OnFeedbackSubmitCallback
private lateinit var dialog: FeedbackDialog private lateinit var dialog: FeedbackDialog
private lateinit var context: Context private lateinit var context: Context
@ -53,7 +53,7 @@ class FeedbackDialogTests {
dialog.show() dialog.show()
Whitebox.setInternalState(dialog, "onFeedbackSubmitCallback", onFeedbackSubmitCallback) Whitebox.setInternalState(dialog, "onFeedbackSubmitCallback", onFeedbackSubmitCallback)
Whitebox.setInternalState(dialog, "dialogFeedbackBinding", dialogFeedbackBinding) Whitebox.setInternalState(dialog, "_binding", dialogFeedbackBinding)
} }
@Test @Test