Added a bug report screen.
This commit is contained in:
parent
b3a9c96fe6
commit
3cc58e9764
15 changed files with 997 additions and 12 deletions
|
|
@ -145,4 +145,5 @@ dependencies {
|
|||
compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
|
||||
compile 'com.github.kabouzeid:RecyclerView-FastScroll:1.8-kmod'
|
||||
compile 'com.heinrichreimersoftware:material-intro:b8ec16d3d6'
|
||||
compile 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:2.1.5'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,11 +107,9 @@
|
|||
<meta-data
|
||||
android:name="com.crashlytics.ApiKey"
|
||||
android:value="b23725bd3d266aa65c5a3dd1816b2f801524a189" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.kabouzeid.gramophone.glide.PhonographGlideModule"
|
||||
android:value="GlideModule" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
|
||||
android:value="GlideModule" />
|
||||
|
|
@ -119,11 +117,9 @@
|
|||
<activity
|
||||
android:name=".ui.activities.tageditor.SongTagEditorActivity"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activities.tageditor.AlbumTagEditorActivity"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
<activity android:name=".ui.activities.SearchActivity" />
|
||||
|
||||
<receiver
|
||||
|
|
@ -138,7 +134,6 @@
|
|||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/app_widget_big_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".appwidgets.AppWidgetClassic"
|
||||
android:exported="false"
|
||||
|
|
@ -151,7 +146,6 @@
|
|||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/app_widget_classic_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".appwidgets.AppWidgetSmall"
|
||||
android:exported="false"
|
||||
|
|
@ -168,17 +162,17 @@
|
|||
<activity
|
||||
android:name=".ui.activities.SettingsActivity"
|
||||
android:label="@string/action_settings" />
|
||||
|
||||
<activity android:name=".ui.activities.PlaylistDetailActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activities.AboutActivity"
|
||||
android:label="@string/action_about" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activities.intro.AppIntroActivity"
|
||||
android:label="@string/intro_label"
|
||||
android:theme="@style/Theme.Intro" />
|
||||
<activity
|
||||
android:name=".ui.activities.bugreport.BugReportActivity"
|
||||
android:label="@string/report_an_issue" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -19,6 +19,7 @@ import com.kabouzeid.gramophone.R;
|
|||
import com.kabouzeid.gramophone.dialogs.ChangelogDialog;
|
||||
import com.kabouzeid.gramophone.dialogs.DonationsDialog;
|
||||
import com.kabouzeid.gramophone.ui.activities.base.AbsBaseActivity;
|
||||
import com.kabouzeid.gramophone.ui.activities.bugreport.BugReportActivity;
|
||||
import com.kabouzeid.gramophone.ui.activities.intro.AppIntroActivity;
|
||||
|
||||
import butterknife.Bind;
|
||||
|
|
@ -36,7 +37,6 @@ public class AboutActivity extends AbsBaseActivity implements View.OnClickListen
|
|||
private static String GITHUB = "https://github.com/kabouzeid";
|
||||
private static String WEBSITE = "http://kabouzeid.com/";
|
||||
|
||||
private static String REPORT_BUGS = "https://github.com/kabouzeid/phonograph-issue-tracker";
|
||||
private static String GOOGLE_PLUS_COMMUNITY = "https://plus.google.com/u/0/communities/106227738496107108513";
|
||||
private static String TRANSLATE = "https://phonograph.oneskyapp.com/collaboration/project?id=26521";
|
||||
private static String RATE_ON_GOOGLE_PLAY = "https://play.google.com/store/apps/details?id=com.kabouzeid.gramophone";
|
||||
|
|
@ -179,7 +179,7 @@ public class AboutActivity extends AbsBaseActivity implements View.OnClickListen
|
|||
} else if (v == visitWebsite) {
|
||||
openUrl(WEBSITE);
|
||||
} else if (v == reportBugs) {
|
||||
openUrl(REPORT_BUGS);
|
||||
startActivity(new Intent(this, BugReportActivity.class));
|
||||
} else if (v == joinGooglePlusCommunity) {
|
||||
openUrl(GOOGLE_PLUS_COMMUNITY);
|
||||
} else if (v == translate) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,414 @@
|
|||
package com.kabouzeid.gramophone.ui.activities.bugreport;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.StringDef;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.design.widget.TextInputEditText;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.kabouzeid.appthemehelper.ThemeStore;
|
||||
import com.kabouzeid.appthemehelper.util.TintHelper;
|
||||
import com.kabouzeid.gramophone.R;
|
||||
import com.kabouzeid.gramophone.misc.DialogAsyncTask;
|
||||
import com.kabouzeid.gramophone.ui.activities.base.AbsThemeActivity;
|
||||
import com.kabouzeid.gramophone.ui.activities.bugreport.model.DeviceInfo;
|
||||
import com.kabouzeid.gramophone.ui.activities.bugreport.model.Report;
|
||||
import com.kabouzeid.gramophone.ui.activities.bugreport.model.github.ExtraInfo;
|
||||
import com.kabouzeid.gramophone.ui.activities.bugreport.model.github.GithubLogin;
|
||||
import com.kabouzeid.gramophone.ui.activities.bugreport.model.github.GithubTarget;
|
||||
|
||||
import org.eclipse.egit.github.core.Issue;
|
||||
import org.eclipse.egit.github.core.client.GitHubClient;
|
||||
import org.eclipse.egit.github.core.client.RequestException;
|
||||
import org.eclipse.egit.github.core.service.IssueService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import butterknife.Bind;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class BugReportActivity extends AbsThemeActivity {
|
||||
|
||||
private static final int STATUS_BAD_CREDENTIALS = 401;
|
||||
private static final int STATUS_ISSUES_NOT_ENABLED = 410;
|
||||
|
||||
@StringDef({RESULT_OK, RESULT_BAD_CREDENTIALS, RESULT_INVALID_TOKEN, RESULT_ISSUES_NOT_ENABLED,
|
||||
RESULT_UNKNOWN})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface Result {
|
||||
}
|
||||
|
||||
private static final String RESULT_OK = "RESULT_OK";
|
||||
private static final String RESULT_BAD_CREDENTIALS = "RESULT_BAD_CREDENTIALS";
|
||||
private static final String RESULT_INVALID_TOKEN = "RESULT_INVALID_TOKEN";
|
||||
private static final String RESULT_ISSUES_NOT_ENABLED = "RESULT_ISSUES_NOT_ENABLED";
|
||||
private static final String RESULT_UNKNOWN = "RESULT_UNKNOWN";
|
||||
|
||||
private DeviceInfo deviceInfo;
|
||||
|
||||
@Bind(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
@Bind(R.id.input_title)
|
||||
TextInputEditText inputTitle;
|
||||
@Bind(R.id.input_description)
|
||||
TextInputEditText inputDescription;
|
||||
@Bind(R.id.air_textDeviceInfo)
|
||||
TextView textDeviceInfo;
|
||||
|
||||
@Bind(R.id.input_username)
|
||||
TextInputEditText inputUsername;
|
||||
@Bind(R.id.input_password)
|
||||
TextInputEditText inputPassword;
|
||||
@Bind(R.id.option_use_account)
|
||||
RadioButton optionUseAccount;
|
||||
@Bind(R.id.option_anonymous)
|
||||
RadioButton optionManual;
|
||||
|
||||
@Bind(R.id.button_send)
|
||||
FloatingActionButton sendFab;
|
||||
|
||||
private static final String ISSUE_TRACKER_LINK = "https://github.com/kabouzeid/phonograph-issue-tracker";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_bug_report);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setStatusbarColorAuto();
|
||||
setNavigationbarColorAuto();
|
||||
setTaskDescriptionColorAuto();
|
||||
|
||||
initViews();
|
||||
|
||||
if (TextUtils.isEmpty(getTitle()))
|
||||
setTitle(R.string.report_an_issue);
|
||||
|
||||
|
||||
deviceInfo = new DeviceInfo(this);
|
||||
textDeviceInfo.setText(deviceInfo.toString());
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
final int accentColor = ThemeStore.accentColor(this);
|
||||
final int primaryColor = ThemeStore.primaryColor(this);
|
||||
toolbar.setBackgroundColor(primaryColor);
|
||||
setSupportActionBar(toolbar);
|
||||
//noinspection ConstantConditions
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
TintHelper.setTintAuto(optionUseAccount, accentColor, false);
|
||||
optionUseAccount.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
inputTitle.setEnabled(true);
|
||||
inputDescription.setEnabled(true);
|
||||
inputUsername.setEnabled(true);
|
||||
inputPassword.setEnabled(true);
|
||||
|
||||
optionManual.setChecked(false);
|
||||
sendFab.hide(new FloatingActionButton.OnVisibilityChangedListener() {
|
||||
@Override
|
||||
public void onHidden(FloatingActionButton fab) {
|
||||
super.onHidden(fab);
|
||||
sendFab.setImageResource(R.drawable.ic_send_white_24dp);
|
||||
sendFab.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
TintHelper.setTintAuto(optionManual, accentColor, false);
|
||||
optionManual.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
inputTitle.setEnabled(false);
|
||||
inputDescription.setEnabled(false);
|
||||
inputUsername.setEnabled(false);
|
||||
inputPassword.setEnabled(false);
|
||||
|
||||
optionUseAccount.setChecked(false);
|
||||
sendFab.hide(new FloatingActionButton.OnVisibilityChangedListener() {
|
||||
@Override
|
||||
public void onHidden(FloatingActionButton fab) {
|
||||
super.onHidden(fab);
|
||||
sendFab.setImageResource(R.drawable.ic_open_in_browser_white_24dp);
|
||||
sendFab.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
inputPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView textView, int actionId, KeyEvent event) {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||
reportIssue();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
textDeviceInfo.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
copyDeviceInfoToClipBoard();
|
||||
}
|
||||
});
|
||||
|
||||
TintHelper.setTintAuto(sendFab, accentColor, true);
|
||||
sendFab.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
reportIssue();
|
||||
}
|
||||
});
|
||||
|
||||
TintHelper.setTintAuto(inputTitle, accentColor, false);
|
||||
TintHelper.setTintAuto(inputDescription, accentColor, false);
|
||||
TintHelper.setTintAuto(inputUsername, accentColor, false);
|
||||
TintHelper.setTintAuto(inputPassword, accentColor, false);
|
||||
}
|
||||
|
||||
private void reportIssue() {
|
||||
if (optionUseAccount.isChecked()) {
|
||||
if (!validateInput()) return;
|
||||
String username = inputUsername.getText().toString();
|
||||
String password = inputPassword.getText().toString();
|
||||
sendBugReport(new GithubLogin(username, password));
|
||||
} else {
|
||||
copyDeviceInfoToClipBoard();
|
||||
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(ISSUE_TRACKER_LINK));
|
||||
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyDeviceInfoToClipBoard() {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||
ClipData clip = ClipData.newPlainText(getString(R.string.device_info), deviceInfo.toMarkdown());
|
||||
clipboard.setPrimaryClip(clip);
|
||||
|
||||
Toast.makeText(BugReportActivity.this, R.string.copied_device_info_to_clipboard, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private boolean validateInput() {
|
||||
boolean hasErrors = false;
|
||||
|
||||
if (optionUseAccount.isChecked()) {
|
||||
if (TextUtils.isEmpty(inputUsername.getText())) {
|
||||
setError(inputUsername, R.string.bug_report_no_username);
|
||||
hasErrors = true;
|
||||
} else {
|
||||
removeError(inputUsername);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(inputPassword.getText())) {
|
||||
setError(inputPassword, R.string.bug_report_no_password);
|
||||
hasErrors = true;
|
||||
} else {
|
||||
removeError(inputPassword);
|
||||
}
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(inputTitle.getText())) {
|
||||
setError(inputTitle, R.string.bug_report_no_title);
|
||||
hasErrors = true;
|
||||
} else {
|
||||
removeError(inputTitle);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(inputDescription.getText())) {
|
||||
setError(inputDescription, R.string.bug_report_no_description);
|
||||
hasErrors = true;
|
||||
} else {
|
||||
removeError(inputDescription);
|
||||
}
|
||||
|
||||
return !hasErrors;
|
||||
}
|
||||
|
||||
private void setError(TextInputEditText editText, @StringRes int errorRes) {
|
||||
TextInputLayout layout = (TextInputLayout) editText.getParent();
|
||||
layout.setError(getString(errorRes));
|
||||
}
|
||||
|
||||
private void removeError(TextInputEditText editText) {
|
||||
TextInputLayout layout = (TextInputLayout) editText.getParent();
|
||||
layout.setError(null);
|
||||
}
|
||||
|
||||
private void sendBugReport(GithubLogin login) {
|
||||
if (!validateInput()) return;
|
||||
|
||||
String bugTitle = inputTitle.getText().toString();
|
||||
String bugDescription = inputDescription.getText().toString();
|
||||
|
||||
ExtraInfo extraInfo = new ExtraInfo();
|
||||
onSaveExtraInfo(extraInfo);
|
||||
|
||||
Report report = new Report(bugTitle, bugDescription, deviceInfo, extraInfo);
|
||||
GithubTarget target = new GithubTarget("kabouzeid", "phonograph-issue-tracker");
|
||||
|
||||
ReportIssueAsyncTask.report(this, report, target, login);
|
||||
}
|
||||
|
||||
protected void onSaveExtraInfo(ExtraInfo extraInfo) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private static class ReportIssueAsyncTask extends DialogAsyncTask<Void, Void, String> {
|
||||
private final Report report;
|
||||
private final GithubTarget target;
|
||||
private final GithubLogin login;
|
||||
|
||||
public static void report(Activity activity, Report report, GithubTarget target,
|
||||
GithubLogin login) {
|
||||
new ReportIssueAsyncTask(activity, report, target, login).execute();
|
||||
}
|
||||
|
||||
private ReportIssueAsyncTask(Activity activity, Report report, GithubTarget target,
|
||||
GithubLogin login) {
|
||||
super(activity);
|
||||
this.report = report;
|
||||
this.target = target;
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Dialog createDialog(@NonNull Context context) {
|
||||
return new MaterialDialog.Builder(context)
|
||||
.progress(true, 0)
|
||||
.progressIndeterminateStyle(true)
|
||||
.title(R.string.bug_report_uploading)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Result
|
||||
protected String doInBackground(Void... params) {
|
||||
GitHubClient client;
|
||||
if (login.shouldUseApiToken()) {
|
||||
client = new GitHubClient().setOAuth2Token(login.getApiToken());
|
||||
} else {
|
||||
client = new GitHubClient().setCredentials(login.getUsername(), login.getPassword());
|
||||
}
|
||||
|
||||
Issue issue = new Issue().setTitle(report.getTitle()).setBody(report.getDescription());
|
||||
try {
|
||||
new IssueService(client).createIssue(target.getUsername(), target.getRepository(), issue);
|
||||
return RESULT_OK;
|
||||
} catch (RequestException e) {
|
||||
switch (e.getStatus()) {
|
||||
case STATUS_BAD_CREDENTIALS:
|
||||
if (login.shouldUseApiToken())
|
||||
return RESULT_INVALID_TOKEN;
|
||||
return RESULT_BAD_CREDENTIALS;
|
||||
case STATUS_ISSUES_NOT_ENABLED:
|
||||
return RESULT_ISSUES_NOT_ENABLED;
|
||||
default:
|
||||
e.printStackTrace();
|
||||
return RESULT_UNKNOWN;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return RESULT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@Result String result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
Context context = getContext();
|
||||
if (context == null) return;
|
||||
|
||||
switch (result) {
|
||||
case RESULT_OK:
|
||||
tryToFinishActivity();
|
||||
break;
|
||||
case RESULT_BAD_CREDENTIALS:
|
||||
new MaterialDialog.Builder(context)
|
||||
.title(R.string.bug_report_failed)
|
||||
.content(R.string.bug_report_failed_wrong_credentials)
|
||||
.positiveText(android.R.string.ok)
|
||||
.show();
|
||||
break;
|
||||
case RESULT_INVALID_TOKEN:
|
||||
new MaterialDialog.Builder(context)
|
||||
.title(R.string.bug_report_failed)
|
||||
.content(R.string.bug_report_failed_invalid_token)
|
||||
.positiveText(android.R.string.ok)
|
||||
.show();
|
||||
break;
|
||||
case RESULT_ISSUES_NOT_ENABLED:
|
||||
new MaterialDialog.Builder(context)
|
||||
.title(R.string.bug_report_failed)
|
||||
.content(R.string.bug_report_failed_issues_not_available)
|
||||
.positiveText(android.R.string.ok)
|
||||
.show();
|
||||
break;
|
||||
default:
|
||||
new MaterialDialog.Builder(context)
|
||||
.title(R.string.bug_report_failed)
|
||||
.content(R.string.bug_report_failed_unknown)
|
||||
.positiveText(android.R.string.ok)
|
||||
.onPositive(new MaterialDialog.SingleButtonCallback() {
|
||||
@Override
|
||||
public void onClick(@NonNull MaterialDialog dialog,
|
||||
@NonNull DialogAction which) {
|
||||
tryToFinishActivity();
|
||||
}
|
||||
})
|
||||
.cancelListener(new DialogInterface.OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
tryToFinishActivity();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToFinishActivity() {
|
||||
Context context = getContext();
|
||||
if (context instanceof Activity && !((Activity) context).isFinishing()) {
|
||||
((Activity) context).finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.kabouzeid.gramophone.ui.activities.bugreport.model;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.IntRange;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class DeviceInfo {
|
||||
private final int versionCode;
|
||||
private final String versionName;
|
||||
private final String buildVersion = Build.VERSION.INCREMENTAL;
|
||||
private final String releaseVersion = Build.VERSION.RELEASE;
|
||||
@IntRange(from = 0)
|
||||
private final int sdkVersion = Build.VERSION.SDK_INT;
|
||||
private final String buildID = Build.DISPLAY;
|
||||
private final String brand = Build.BRAND;
|
||||
private final String manufacturer = Build.MANUFACTURER;
|
||||
private final String device = Build.DEVICE;
|
||||
private final String model = Build.MODEL;
|
||||
private final String product = Build.PRODUCT;
|
||||
private final String hardware = Build.HARDWARE;
|
||||
@SuppressLint("NewApi")
|
||||
@SuppressWarnings("deprecation")
|
||||
private final String[] abis = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
|
||||
Build.SUPPORTED_ABIS : new String[]{Build.CPU_ABI, Build.CPU_ABI2};
|
||||
@SuppressLint("NewApi")
|
||||
private final String[] abis32Bits = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
|
||||
Build.SUPPORTED_32_BIT_ABIS : null;
|
||||
@SuppressLint("NewApi")
|
||||
private final String[] abis64Bits = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ?
|
||||
Build.SUPPORTED_64_BIT_ABIS : null;
|
||||
|
||||
public DeviceInfo(Context context) {
|
||||
PackageInfo packageInfo;
|
||||
try {
|
||||
packageInfo = context.getPackageManager()
|
||||
.getPackageInfo(context.getPackageName(), 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
packageInfo = null;
|
||||
}
|
||||
if (packageInfo != null) {
|
||||
versionCode = packageInfo.versionCode;
|
||||
versionName = packageInfo.versionName;
|
||||
} else {
|
||||
versionCode = -1;
|
||||
versionName = null;
|
||||
}
|
||||
}
|
||||
|
||||
public String toMarkdown() {
|
||||
return "Device info:\n"
|
||||
+ "---\n"
|
||||
+ "<table>\n"
|
||||
+ "<tr><td>App version</td><td>" + versionName + "</td></tr>\n"
|
||||
+ "<tr><td>App version code</td><td>" + versionCode + "</td></tr>\n"
|
||||
+ "<tr><td>Android build version</td><td>" + buildVersion + "</td></tr>\n"
|
||||
+ "<tr><td>Android release version</td><td>" + releaseVersion + "</td></tr>\n"
|
||||
+ "<tr><td>Android SDK version</td><td>" + sdkVersion + "</td></tr>\n"
|
||||
+ "<tr><td>Android build ID</td><td>" + buildID + "</td></tr>\n"
|
||||
+ "<tr><td>Device brand</td><td>" + brand + "</td></tr>\n"
|
||||
+ "<tr><td>Device manufacturer</td><td>" + manufacturer + "</td></tr>\n"
|
||||
+ "<tr><td>Device name</td><td>" + device + "</td></tr>\n"
|
||||
+ "<tr><td>Device model</td><td>" + model + "</td></tr>\n"
|
||||
+ "<tr><td>Device product name</td><td>" + product + "</td></tr>\n"
|
||||
+ "<tr><td>Device hardware name</td><td>" + hardware + "</td></tr>\n"
|
||||
+ "<tr><td>ABIs</td><td>" + Arrays.toString(abis) + "</td></tr>\n"
|
||||
+ "<tr><td>ABIs (32bit)</td><td>" + Arrays.toString(abis32Bits) + "</td></tr>\n"
|
||||
+ "<tr><td>ABIs (64bit)</td><td>" + Arrays.toString(abis64Bits) + "</td></tr>\n"
|
||||
+ "</table>\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "App version: " + versionName + "\n"
|
||||
+ "App version code: " + versionCode + "\n"
|
||||
+ "Android build version: " + buildVersion + "\n"
|
||||
+ "Android release version: " + releaseVersion + "\n"
|
||||
+ "Android SDK version: " + sdkVersion + "\n"
|
||||
+ "Android build ID: " + buildID + "\n"
|
||||
+ "Device brand: " + brand + "\n"
|
||||
+ "Device manufacturer: " + manufacturer + "\n"
|
||||
+ "Device name: " + device + "\n"
|
||||
+ "Device model: " + model + "\n"
|
||||
+ "Device product name: " + product + "\n"
|
||||
+ "Device hardware name: " + hardware + "\n"
|
||||
+ "ABIs: " + Arrays.toString(abis) + "\n"
|
||||
+ "ABIs (32bit): " + Arrays.toString(abis32Bits) + "\n"
|
||||
+ "ABIs (64bit): " + Arrays.toString(abis64Bits);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.kabouzeid.gramophone.ui.activities.bugreport.model;
|
||||
|
||||
import com.kabouzeid.gramophone.ui.activities.bugreport.model.github.ExtraInfo;
|
||||
|
||||
public class Report {
|
||||
private final String title;
|
||||
|
||||
private final String description;
|
||||
|
||||
private final DeviceInfo deviceInfo;
|
||||
|
||||
private final ExtraInfo extraInfo;
|
||||
|
||||
public Report(String title, String description, DeviceInfo deviceInfo, ExtraInfo extraInfo) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.deviceInfo = deviceInfo;
|
||||
this.extraInfo = extraInfo;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description + "\n\n"
|
||||
+ "-\n\n"
|
||||
+ deviceInfo.toMarkdown() + "\n\n"
|
||||
+ extraInfo.toMarkdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.kabouzeid.gramophone.ui.activities.bugreport.model.github;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ExtraInfo {
|
||||
private final Map<String, String> extraInfo = new LinkedHashMap<>();
|
||||
|
||||
public void put(String key, String value) {
|
||||
extraInfo.put(key, value);
|
||||
}
|
||||
|
||||
public void put(String key, boolean value) {
|
||||
extraInfo.put(key, Boolean.toString(value));
|
||||
}
|
||||
|
||||
public void put(String key, double value) {
|
||||
extraInfo.put(key, Double.toString(value));
|
||||
}
|
||||
|
||||
public void put(String key, float value) {
|
||||
extraInfo.put(key, Float.toString(value));
|
||||
}
|
||||
|
||||
public void put(String key, long value) {
|
||||
extraInfo.put(key, Long.toString(value));
|
||||
}
|
||||
|
||||
public void put(String key, int value) {
|
||||
extraInfo.put(key, Integer.toString(value));
|
||||
}
|
||||
|
||||
public void put(String key, Object value) {
|
||||
extraInfo.put(key, String.valueOf(value));
|
||||
}
|
||||
|
||||
public void remove(String key) {
|
||||
extraInfo.remove(key);
|
||||
}
|
||||
|
||||
public String toMarkdown() {
|
||||
if (extraInfo.isEmpty()) return "";
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
output.append("Extra info:\n"
|
||||
+ "---\n"
|
||||
+ "<table>\n");
|
||||
for (String key : extraInfo.keySet()) {
|
||||
output.append("<tr><td>")
|
||||
.append(key)
|
||||
.append("</td><td>")
|
||||
.append(extraInfo.get(key))
|
||||
.append("</td></tr>\n");
|
||||
}
|
||||
output.append("</table>\n");
|
||||
|
||||
return output.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.kabouzeid.gramophone.ui.activities.bugreport.model.github;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
public class GithubLogin {
|
||||
private final String username;
|
||||
|
||||
private final String password;
|
||||
|
||||
private final String apiToken;
|
||||
|
||||
public GithubLogin(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.apiToken = null;
|
||||
}
|
||||
|
||||
public GithubLogin(String apiToken) {
|
||||
this.username = null;
|
||||
this.password = null;
|
||||
this.apiToken = apiToken;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public boolean shouldUseApiToken() {
|
||||
return TextUtils.isEmpty(username) || TextUtils.isEmpty(password);
|
||||
}
|
||||
|
||||
public String getApiToken() {
|
||||
return apiToken;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.kabouzeid.gramophone.ui.activities.bugreport.model.github;
|
||||
|
||||
public class GithubTarget {
|
||||
private final String username;
|
||||
|
||||
private final String repository;
|
||||
|
||||
public GithubTarget(String username, String repository) {
|
||||
this.username = username;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getRepository() {
|
||||
return repository;
|
||||
}
|
||||
}
|
||||
10
app/src/main/res/drawable/ic_send_white_24dp.xml
Normal file
10
app/src/main/res/drawable/ic_send_white_24dp.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0"
|
||||
android:width="24dp">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z" />
|
||||
</vector>
|
||||
68
app/src/main/res/layout/activity_bug_report.xml
Normal file
68
app/src/main/res/layout/activity_bug_report.xml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/Toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<include
|
||||
layout="@layout/bug_report_card_report"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<include
|
||||
layout="@layout/bug_report_card_device_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:layout_margin="16dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/button_send"
|
||||
style="@style/Fab"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="@dimen/fab_margin_top_left_right"
|
||||
app:srcCompat="@drawable/ic_send_white_24dp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
37
app/src/main/res/layout/bug_report_card_device_info.xml
Normal file
37
app/src/main/res/layout/bug_report_card_device_info.xml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?cardBackgroundColor"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="24dp"
|
||||
android:text="@string/device_info"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/air_textDeviceInfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:background="?rectSelector"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
188
app/src/main/res/layout/bug_report_card_report.xml
Normal file
188
app/src/main/res/layout/bug_report_card_report.xml
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?cardBackgroundColor"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/md_listitem_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/option_use_account"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:gravity="start|center_vertical"
|
||||
android:minHeight="@dimen/md_listitem_height" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="56dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/bug_report_use_account"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/your_account_data_is_only_used_for_authentication"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingLeft="72dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingStart="72dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/bug_report_issue"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/input_layout_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/input_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/title"
|
||||
android:inputType="textCapSentences"
|
||||
android:singleLine="true" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/input_layout_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/input_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/description"
|
||||
android:inputType="textCapSentences" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/login"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/input_layout_username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/input_username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/username"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:singleLine="true" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
android:id="@+id/input_layout_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.design.widget.TextInputEditText
|
||||
android:id="@+id/input_password"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/password"
|
||||
android:imeOptions="actionSend"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="@dimen/md_listitem_height"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/option_anonymous"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|center_vertical"
|
||||
android:minHeight="@dimen/md_listitem_height" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="56dp"
|
||||
android:layout_marginStart="56dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/bug_report_manual"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/you_will_be_forwarded_to_the_issue_tracker_website"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Caption" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v7.widget.CardView>
|
||||
|
|
@ -22,6 +22,12 @@
|
|||
<copyright>Copyright (C) 2015 Haruki Hasegawa</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>android-issue-reporter</name>
|
||||
<url>https://github.com/HeinrichReimer/android-issue-reporter</url>
|
||||
<copyright>Copyright 2016 Heinrich Reimer</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>Android-ObservableScrollView</name>
|
||||
<url>https://github.com/ksoichiro/Android-ObservableScrollView</url>
|
||||
|
|
|
|||
|
|
@ -248,4 +248,27 @@
|
|||
<string name="app_widget_big_name">Phonograph - Big</string>
|
||||
<string name="app_widget_classic_name">Phonograph - Classic</string>
|
||||
<string name="app_widget_small_name">Phonograph - Small</string>
|
||||
<string name="report_an_issue">Report an issue</string>
|
||||
<string name="bug_report_issue">Issue</string>
|
||||
<string name="login">Login</string>
|
||||
<string name="title">Title</string>
|
||||
<string name="description">Description</string>
|
||||
<string name="device_info">Device info</string>
|
||||
<string name="bug_report_use_account">Send using GitHub account</string>
|
||||
<string name="bug_report_manual">Send manually</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="bug_report_no_title">Please enter an issue title</string>
|
||||
<string name="bug_report_no_description">Please enter an issue description</string>
|
||||
<string name="bug_report_no_username">Please enter your valid GitHub username</string>
|
||||
<string name="bug_report_no_password">Please enter your valid GitHub password</string>
|
||||
<string name="bug_report_uploading">Uploading report to GitHub…</string>
|
||||
<string name="bug_report_failed">Unable to send report</string>
|
||||
<string name="bug_report_failed_wrong_credentials">Wrong username or password</string>
|
||||
<string name="bug_report_failed_invalid_token">Invalid access token. Please contact the app developer.</string>
|
||||
<string name="bug_report_failed_issues_not_available">Issues are not enabled for the selected repository. Please contact the app developer.</string>
|
||||
<string name="bug_report_failed_unknown">An unexpected error occurred. Please contact the app developer.</string>
|
||||
<string name="copied_device_info_to_clipboard">Copied device info to clipboard.</string>
|
||||
<string name="your_account_data_is_only_used_for_authentication">Your account data is only used for authentication.</string>
|
||||
<string name="you_will_be_forwarded_to_the_issue_tracker_website">You will be forwarded to the issue tracker website.</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue