Added a bug report screen.

This commit is contained in:
Karim Abou Zeid 2016-04-16 17:09:00 +02:00
commit 3cc58e9764
15 changed files with 997 additions and 12 deletions

View file

@ -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'
}

View file

@ -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>

View file

@ -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) {

View file

@ -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();
}
}
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View 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>

View 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>

View 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>

View 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>

View file

@ -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>

View file

@ -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>