In app feedback (#4845)

* rebase

* Progress

* Completed UI and POST Request

* removed invalid string resource

* Removed unused code & Added string resources

* Resolved Code style issues

* Javadoc for getters & setters

* Codestyle fixes

* Minor Fixes

* wip

* Tests

* Comments

* Fixed Tests

* Minor changes

* minor change

* Comments

* Minor Fixes

* fixed tests

* Removed Butterknife

* Fixed tests

* Removed Unecessary strings

* Minor chnages

* Minor fix

* Minor changes

* Minor changes

* Implemented Suggestions

* Removed Redundant Toast
This commit is contained in:
Devarsh Mavani 2022-03-20 08:40:25 +05:30 committed by GitHub
parent 00760ba1c6
commit fa0370438b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 777 additions and 5 deletions

View file

@ -0,0 +1,95 @@
package fr.free.nrw.commons.feedback;
import android.content.Context;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.feedback.model.Feedback;
import fr.free.nrw.commons.utils.LangCodeUtils;
import java.util.Locale;
/**
* Creates a wikimedia recognizable format
* from feedback information
*/
public class FeedbackContentCreator {
private StringBuilder stringBuilder;
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.
stringBuilder = new StringBuilder();
stringBuilder.append("== ");
stringBuilder.append("Feedback from ~~~ for version ");
stringBuilder.append(feedback.getVersion());
stringBuilder.append(" ==");
stringBuilder.append("\n");
stringBuilder.append(feedback.getTitle());
stringBuilder.append("\n");
stringBuilder.append("\n");
if (feedback.getApiLevel() != null) {
stringBuilder.append("* ");
stringBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.api_level));
stringBuilder.append(": ");
stringBuilder.append(feedback.getApiLevel());
stringBuilder.append("\n");
}
if (feedback.getAndroidVersion() != null) {
stringBuilder.append("* ");
stringBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.android_version));
stringBuilder.append(": ");
stringBuilder.append(feedback.getAndroidVersion());
stringBuilder.append("\n");
}
if (feedback.getDeviceManufacturer() != null) {
stringBuilder.append("* ");
stringBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.device_manufacturer));
stringBuilder.append(": ");
stringBuilder.append(feedback.getDeviceManufacturer());
stringBuilder.append("\n");
}
if (feedback.getDeviceModel() != null) {
stringBuilder.append("* ");
stringBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.device_model));
stringBuilder.append(": ");
stringBuilder.append(feedback.getDeviceModel());
stringBuilder.append("\n");
}
if (feedback.getDevice() != null) {
stringBuilder.append("* ");
stringBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.device_name));
stringBuilder.append(": ");
stringBuilder.append(feedback.getDevice());
stringBuilder.append("\n");
}
if (feedback.getNetworkType() != null) {
stringBuilder.append("* ");
stringBuilder.append(LangCodeUtils.getLocalizedResources(context,
Locale.ENGLISH).getString(R.string.network_type));
stringBuilder.append(": ");
stringBuilder.append(feedback.getNetworkType());
stringBuilder.append("\n");
}
stringBuilder.append("~~~~");
stringBuilder.append("\n");
}
@Override
public String toString() {
return stringBuilder.toString();
}
}

View file

@ -0,0 +1,66 @@
package fr.free.nrw.commons.feedback;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;
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;
/**
* Feedback dialog that asks user for message and
* other device specifications
*/
public class FeedbackDialog extends Dialog {
DialogFeedbackBinding dialogFeedbackBinding;
private OnFeedbackSubmitCallback onFeedbackSubmitCallback;
public FeedbackDialog(Context context, OnFeedbackSubmitCallback onFeedbackSubmitCallback) {
super(context);
this.onFeedbackSubmitCallback = onFeedbackSubmitCallback;
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dialogFeedbackBinding = DialogFeedbackBinding.inflate(getLayoutInflater());
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,15 @@
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,171 @@
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

@ -24,13 +24,23 @@ import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.actions.PageEditClient;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.feedback.FeedbackContentCreator;
import fr.free.nrw.commons.feedback.model.Feedback;
import fr.free.nrw.commons.feedback.FeedbackDialog;
import fr.free.nrw.commons.feedback.OnFeedbackSubmitCallback;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.logging.CommonsLogSender;
import fr.free.nrw.commons.profile.ProfileActivity;
import fr.free.nrw.commons.review.ReviewActivity;
import fr.free.nrw.commons.settings.SettingsActivity;
import io.reactivex.Single;
import io.reactivex.SingleSource;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
@ -47,6 +57,10 @@ public class MoreBottomSheetFragment extends BottomSheetDialogFragment {
@Inject @Named("default_preferences")
JsonKvStore store;
@Inject
@Named("commons-page-edit")
PageEditClient pageEditClient;
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@ -104,7 +118,44 @@ public class MoreBottomSheetFragment extends BottomSheetDialogFragment {
@OnClick(R.id.more_feedback)
public void onFeedbackClicked() {
showAlertDialog();
showFeedbackDialog();
}
/**
* Creates and shows a dialog asking feedback from users
*/
private void showFeedbackDialog() {
new FeedbackDialog(getContext(), new OnFeedbackSubmitCallback() {
@Override
public void onFeedbackSubmit(Feedback feedback) {
uploadFeedback(feedback);
}
}).show();
}
/**
* uploads feedback data on the server
*/
void uploadFeedback(Feedback feedback) {
FeedbackContentCreator feedbackContentCreator = new FeedbackContentCreator(getContext(), feedback);
Single<Boolean> single =
pageEditClient.prependEdit("Commons:Mobile_app/Feedback", feedbackContentCreator.toString(), "Summary")
.flatMapSingle(result -> Single.just(result))
.firstOrError();
Single.defer((Callable<SingleSource<Boolean>>) () -> single)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(aBoolean -> {
if (aBoolean) {
Toast.makeText(getContext(), getString(R.string.thanks_feedback), Toast.LENGTH_SHORT)
.show();
} else {
Toast.makeText(getContext(), getString(R.string.error_feedback),
Toast.LENGTH_SHORT).show();
}
});
}
/**