Merge branch 'develope' into 'master'

Merge to master

See merge request its-wg/overview/overview-ubo/misp-auth!2
pull/5/head
Felix Prahl-Kamps 2018-07-22 20:27:31 +02:00
commit 49b5af5a05
34 changed files with 305 additions and 79 deletions

View File

@ -1,33 +1,33 @@
![MispBump Logo](./images/mispbump.svg)
# MISPBump # MISPBump
With MISPBump it is easy to share events on your MISP instance with other instances. Instead of generating organisations, sync-users and sync-servers you scan only two QR-Codes and the job is done. With MISPBump it is easy to synchronise events on different MISP instances. Instead of generating organisations, sync-users and sync-servers you only have to scan two QR-Codes and you are ready for syncing.
# Security # Security
A key agreement is realized with Diffie Hellman (Elliptic Curve 256 Bit), sensible data is encrypted with AES. A key agreement is realized with Diffie Hellman (Elliptic Curve 256 Bit), sensible data is encrypted with AES.
(how are credentials stored in app, keystore?) TODO: how are credentials stored in app, keystore?
# How does it work? # How does it work?
1. Main screen (no changes)
1. Gather your organisation information from your MISP instance 1. Gather your organisation information from your MISP instance
![Gather Information](./Screenshots/sync-profile.png) ![Gather Information](./images/Screenshots/sync-profile.png)
1. Scan your partners generated public key and at the same time share yours 1. Scan your partners generated public key and at the same time share yours
![Scan Public Key](./Screenshots/scan-pub-key.png) ![Scan Public Key](./images/Screenshots/scan-pub-key.png)
2. Validate the public key you scanned 2. Validate the public key you scanned
![Public Key Received](./Screenshots/pub-key-received.png) ![Public Key Received](./images/Screenshots/pub-key-received.png)
3. After another scan the information you need to synchronise is securely transmitted to your phone 3. After another scan the information you need to synchronise is securely transmitted to your phone
![Secure Info Received](./Screenshots/org-info-received.png) ![Secure Info Received](./images/Screenshots/org-info-received.png)
4. Upload the information to your own MISP instance 4. Upload the information to your own MISP instance
![Upload](./Screenshots/upload.png) ![Upload](./images/Screenshots/upload.png)
5. That's it! You are ready to share events across your instances 5. That's it! You are ready to share events across your instances
![Main Screen](./Screenshots/main.png) ![Main Screen](./images/Screenshots/main.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

View File

@ -7,9 +7,9 @@
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/launcher_handshake_square"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/launcher_handshake_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
@ -17,7 +17,6 @@
android:label="@string/app_name"> android:label="@string/app_name">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>

View File

@ -10,6 +10,9 @@ import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
@ -22,15 +25,20 @@ import android.widget.CompoundButton;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import com.android.volley.VolleyError; import com.android.volley.VolleyError;
import de.overview.wg.its.mispauth.adapter.OrganisationInfoEntryAdapter;
import de.overview.wg.its.mispauth.auxiliary.PreferenceManager; import de.overview.wg.its.mispauth.auxiliary.PreferenceManager;
import de.overview.wg.its.mispauth.auxiliary.ReadableError; import de.overview.wg.its.mispauth.auxiliary.ReadableError;
import de.overview.wg.its.mispauth.cam.DialogFactory; import de.overview.wg.its.mispauth.cam.DialogFactory;
import de.overview.wg.its.mispauth.model.Organisation; import de.overview.wg.its.mispauth.model.Organisation;
import de.overview.wg.its.mispauth.model.StringPair;
import de.overview.wg.its.mispauth.model.User; import de.overview.wg.its.mispauth.model.User;
import de.overview.wg.its.mispauth.network.MispRequest; import de.overview.wg.its.mispauth.network.MispRequest;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
public class CredentialsActivity extends AppCompatActivity implements View.OnClickListener { public class CredentialsActivity extends AppCompatActivity implements View.OnClickListener {
@ -42,12 +50,16 @@ public class CredentialsActivity extends AppCompatActivity implements View.OnCli
private TextInputLayout urlLayout, apiLayout; private TextInputLayout urlLayout, apiLayout;
private TextView emptyView; private TextView emptyView;
private ViewGroup organisationView; // private ViewGroup organisationView;
private ProgressBar progressBar; private ProgressBar progressBar;
private Organisation myOrganisation; private Organisation myOrganisation;
private User myUser; private User myUser;
private RecyclerView recyclerView;
private OrganisationInfoEntryAdapter adapter;
private List<StringPair> orgInfoEntries = new ArrayList<>();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -120,10 +132,17 @@ public class CredentialsActivity extends AppCompatActivity implements View.OnCli
urlLayout = findViewById(R.id.input_layout_server_url); urlLayout = findViewById(R.id.input_layout_server_url);
apiLayout = findViewById(R.id.input_layout_api_key); apiLayout = findViewById(R.id.input_layout_api_key);
emptyView = findViewById(R.id.empty); emptyView = findViewById(R.id.empty);
organisationView = findViewById(R.id.myOrganisationView); // organisationView = findViewById(R.id.myOrganisationView);
FloatingActionButton fab = findViewById(R.id.fab_download_own_org_info); FloatingActionButton fab = findViewById(R.id.fab_download_own_org_info);
fab.setOnClickListener(this); fab.setOnClickListener(this);
recyclerView = findViewById(R.id.recyclerView);
adapter = new OrganisationInfoEntryAdapter();
RecyclerView.LayoutManager manager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(manager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setAdapter(adapter);
} }
/** /**
@ -141,15 +160,16 @@ public class CredentialsActivity extends AppCompatActivity implements View.OnCli
if (myOrganisation == null) { if (myOrganisation == null) {
emptyView.setVisibility(View.VISIBLE); emptyView.setVisibility(View.VISIBLE);
organisationView.setVisibility(View.GONE); // organisationView.setVisibility(View.GONE);
recyclerView.setVisibility(View.GONE);
} else { } else {
emptyView.setVisibility(View.GONE); emptyView.setVisibility(View.GONE);
organisationView.setVisibility(View.VISIBLE); // organisationView.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.VISIBLE);
visualizeOrganisation(); visualizeOrganisation();
} }
} }
private void savePreferences() { private void savePreferences() {
@ -164,7 +184,10 @@ public class CredentialsActivity extends AppCompatActivity implements View.OnCli
} }
if (myUser != null) { if (myUser != null) {
myUser.clearForStorage();
preferenceManager.setMyUser(myUser); preferenceManager.setMyUser(myUser);
} }
if (myOrganisation != null) { if (myOrganisation != null) {
@ -243,20 +266,13 @@ public class CredentialsActivity extends AppCompatActivity implements View.OnCli
private void downloadOrgInfo() { private void downloadOrgInfo() {
if (myOrganisation != null) { if (myOrganisation != null) {
DialogInterface.OnClickListener pos = new DialogInterface.OnClickListener() { DialogInterface.OnClickListener pos = new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
downloadOrgInfo(); downloadOrgInfo();
} }
}; };
new DialogFactory(this).createOverrideDialog(pos, null).show(); new DialogFactory(this).createOverrideDialog(pos, null).show();
} else {
downloadOrgInfo();
} }
if (!validCredentials()) { if (!validCredentials()) {
@ -294,7 +310,8 @@ public class CredentialsActivity extends AppCompatActivity implements View.OnCli
return; return;
} }
organisationView.setVisibility(View.VISIBLE); recyclerView.setVisibility(View.VISIBLE);
// organisationView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE); emptyView.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
@ -305,7 +322,8 @@ public class CredentialsActivity extends AppCompatActivity implements View.OnCli
public void onError(VolleyError volleyError) { public void onError(VolleyError volleyError) {
makeSnackBar(ReadableError.toReadable(volleyError)); makeSnackBar(ReadableError.toReadable(volleyError));
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
organisationView.setVisibility(View.GONE); recyclerView.setVisibility(View.GONE);
// organisationView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE); emptyView.setVisibility(View.VISIBLE);
} }
}); });
@ -315,7 +333,8 @@ public class CredentialsActivity extends AppCompatActivity implements View.OnCli
public void onError(VolleyError volleyError) { public void onError(VolleyError volleyError) {
makeSnackBar(ReadableError.toReadable(volleyError)); makeSnackBar(ReadableError.toReadable(volleyError));
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
organisationView.setVisibility(View.GONE); recyclerView.setVisibility(View.GONE);
// organisationView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE); emptyView.setVisibility(View.VISIBLE);
} }
}); });
@ -324,25 +343,32 @@ public class CredentialsActivity extends AppCompatActivity implements View.OnCli
private void visualizeOrganisation() { private void visualizeOrganisation() {
TextView title = organisationView.findViewById(R.id.organisation_title); orgInfoEntries.add(new StringPair("Name", myOrganisation.getName()));
title.setText(myOrganisation.getName()); // TextView title = organisationView.findViewById(R.id.organisation_title);
// TextView title = findViewById(R.id.org_title);
// title.setText(myOrganisation.getName());
TextView uuid = organisationView.findViewById(R.id.organisation_uuid); orgInfoEntries.add(new StringPair("UUID", myOrganisation.getUuid()));
uuid.setText(myOrganisation.getUuid()); // TextView uuid = organisationView.findViewById(R.id.organisation_uuid);
// uuid.setText(myOrganisation.getUuid());
TextView description = organisationView.findViewById(R.id.organisation_description); orgInfoEntries.add(new StringPair("Description", myOrganisation.getDescription()));
description.setText(myOrganisation.getDescription()); // TextView description = organisationView.findViewById(R.id.organisation_description);
// description.setText(myOrganisation.getDescription());
TextView nationality = organisationView.findViewById(R.id.organisation_nationality); orgInfoEntries.add(new StringPair("Nationality", myOrganisation.getNationality()));
nationality.setText(myOrganisation.getNationality()); // TextView nationality = organisationView.findViewById(R.id.organisation_nationality);
// nationality.setText(myOrganisation.getNationality());
TextView sector = findViewById(R.id.organisation_sector); orgInfoEntries.add(new StringPair("Sector", myOrganisation.getSector()));
sector.setText(myOrganisation.getSector()); // TextView sector = findViewById(R.id.organisation_sector);
// sector.setText(myOrganisation.getSector());
TextView users = findViewById(R.id.organisation_user_count); orgInfoEntries.add(new StringPair("User Count", "" + myOrganisation.getUserCount()));
// TextView users = findViewById(R.id.organisation_user_count);
users.setText("" + myOrganisation.getUserCount()); // users.setText("" + myOrganisation.getUserCount());
adapter.setList(orgInfoEntries);
} }
private void exitSafely() { private void exitSafely() {

View File

@ -91,7 +91,7 @@ public class MainActivity extends AppCompatActivity {
emptyPartnerListView = findViewById(R.id.empty); emptyPartnerListView = findViewById(R.id.empty);
syncedPartnerRecyclerView = findViewById(R.id.recyclerView); syncedPartnerRecyclerView = findViewById(R.id.recyclerView);
syncedPartnerAdapter = new SyncedPartnerAdapter(this, syncedPartnerList); syncedPartnerAdapter = new SyncedPartnerAdapter(syncedPartnerList);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext()); RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
syncedPartnerRecyclerView.setLayoutManager(mLayoutManager); syncedPartnerRecyclerView.setLayoutManager(mLayoutManager);
syncedPartnerRecyclerView.setItemAnimator(new DefaultItemAnimator()); syncedPartnerRecyclerView.setItemAnimator(new DefaultItemAnimator());

View File

@ -92,17 +92,16 @@ public class UploadActivity extends AppCompatActivity implements View.OnClickLis
} }
private List<UploadState> syncUploadStates = new ArrayList<>(); private List<UploadState> syncUploadStates = new ArrayList<>();
private void SyncUpload() { private void SyncUpload() {
partnerOrganisation = partnerInformation.getOrganisation(); partnerOrganisation = partnerInformation.getOrganisation();
partnerSyncUser = partnerInformation.getUser(); partnerSyncUser = partnerInformation.getUser();
partnerServer = partnerInformation.getServer(); partnerServer = partnerInformation.getServer();
syncUploadStates.add(new UploadState("Add local organisation")); syncUploadStates.add(new UploadState("Create local organisation"));
syncUploadStates.add(new UploadState("Add sync user to organisation")); syncUploadStates.add(new UploadState("Create sync user & add to organisation"));
syncUploadStates.add(new UploadState("Add external organisation")); syncUploadStates.add(new UploadState("Create external organisation"));
syncUploadStates.add(new UploadState("Add sync server")); syncUploadStates.add(new UploadState("Create sync server"));
uploadStateAdapter.setStateList(syncUploadStates); uploadStateAdapter.setStateList(syncUploadStates);

View File

@ -0,0 +1,53 @@
package de.overview.wg.its.mispauth.adapter;
import android.support.annotation.NonNull;
import android.support.v4.util.Pair;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import de.overview.wg.its.mispauth.R;
import de.overview.wg.its.mispauth.model.StringPair;
import java.util.*;
public class OrganisationInfoEntryAdapter extends RecyclerView.Adapter<OrganisationInfoEntryAdapter.MyViewHolder> {
private List<StringPair> list = new ArrayList<>();
class MyViewHolder extends RecyclerView.ViewHolder {
TextView title, value;
private MyViewHolder(View view) {
super(view);
this.title = view.findViewById(R.id.title);
this.value = view.findViewById(R.id.value);
}
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View row = LayoutInflater.from(parent.getContext()).inflate(R.layout.row_org_info_entry, parent, false);
return new OrganisationInfoEntryAdapter.MyViewHolder(row);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.title.setText(list.get(position).key);
holder.value.setText(list.get(position).value);
}
@Override
public int getItemCount() {
return list.size();
}
public void setList(List<StringPair> list) {
this.list = list;
notifyDataSetChanged();
}
}

View File

@ -36,13 +36,11 @@ public class Organisation {
private int createdBy; private int createdBy;
private int userCount; private int userCount;
public Organisation() { public Organisation() {}
}
public Organisation(JSONObject json) throws JSONException { public Organisation(JSONObject json) throws JSONException {
fromJSON(json); fromJSON(json);
} }
public void fromJSON(JSONObject org) throws JSONException { public void fromJSON(JSONObject org) throws JSONException {
id = org.optInt(ID_KEY, -1); id = org.optInt(ID_KEY, -1);
@ -65,7 +63,6 @@ public class Organisation {
public JSONObject toJSON() { public JSONObject toJSON() {
return toJSON(false); return toJSON(false);
} }
public JSONObject toJSON(boolean minimal) { public JSONObject toJSON(boolean minimal) {
JSONObject org = new JSONObject(); JSONObject org = new JSONObject();
@ -99,7 +96,6 @@ public class Organisation {
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
public String getName() { public String getName() {
return name; return name;
} }
@ -107,7 +103,6 @@ public class Organisation {
public String getDescription() { public String getDescription() {
return description; return description;
} }
public void setDescription(String description) { public void setDescription(String description) {
this.description = description; this.description = description;
} }
@ -115,7 +110,6 @@ public class Organisation {
public String getSector() { public String getSector() {
return sector; return sector;
} }
public void setSector(String sector) { public void setSector(String sector) {
this.sector = sector; this.sector = sector;
} }
@ -123,7 +117,6 @@ public class Organisation {
public String getNationality() { public String getNationality() {
return nationality; return nationality;
} }
public void setNationality(String nationality) { public void setNationality(String nationality) {
this.nationality = nationality; this.nationality = nationality;
} }
@ -131,7 +124,6 @@ public class Organisation {
public int getId() { public int getId() {
return id; return id;
} }
public void setId(int id) { public void setId(int id) {
this.id = id; this.id = id;
} }
@ -139,7 +131,6 @@ public class Organisation {
public String getDateCreated() { public String getDateCreated() {
return dateCreated; return dateCreated;
} }
public void setDateCreated(String dateCreated) { public void setDateCreated(String dateCreated) {
this.dateCreated = dateCreated; this.dateCreated = dateCreated;
} }
@ -147,7 +138,6 @@ public class Organisation {
public String getDateModified() { public String getDateModified() {
return dateModified; return dateModified;
} }
public void setDateModified(String dateModified) { public void setDateModified(String dateModified) {
this.dateModified = dateModified; this.dateModified = dateModified;
} }
@ -155,7 +145,6 @@ public class Organisation {
public String getType() { public String getType() {
return type; return type;
} }
public void setType(String type) { public void setType(String type) {
this.type = type; this.type = type;
} }
@ -163,7 +152,6 @@ public class Organisation {
public String getContacts() { public String getContacts() {
return contacts; return contacts;
} }
public void setContacts(String contacts) { public void setContacts(String contacts) {
this.contacts = contacts; this.contacts = contacts;
} }
@ -171,7 +159,6 @@ public class Organisation {
public boolean isLocal() { public boolean isLocal() {
return local; return local;
} }
public void setLocal(boolean local) { public void setLocal(boolean local) {
this.local = local; this.local = local;
} }
@ -179,7 +166,6 @@ public class Organisation {
public String getUuid() { public String getUuid() {
return uuid; return uuid;
} }
public void setUuid(String uuid) { public void setUuid(String uuid) {
this.uuid = uuid; this.uuid = uuid;
} }
@ -187,7 +173,6 @@ public class Organisation {
public String getRestrictedToDomain() { public String getRestrictedToDomain() {
return restrictedToDomain; return restrictedToDomain;
} }
public void setRestrictedToDomain(String restrictedToDomain) { public void setRestrictedToDomain(String restrictedToDomain) {
this.restrictedToDomain = restrictedToDomain; this.restrictedToDomain = restrictedToDomain;
} }
@ -195,7 +180,6 @@ public class Organisation {
public int getCreatedBy() { public int getCreatedBy() {
return createdBy; return createdBy;
} }
public void setCreatedBy(int createdBy) { public void setCreatedBy(int createdBy) {
this.createdBy = createdBy; this.createdBy = createdBy;
} }
@ -203,7 +187,6 @@ public class Organisation {
public int getUserCount() { public int getUserCount() {
return userCount; return userCount;
} }
public void setUserCount(int userCount) { public void setUserCount(int userCount) {
this.userCount = userCount; this.userCount = userCount;
} }

View File

@ -0,0 +1,11 @@
package de.overview.wg.its.mispauth.model;
public class StringPair {
public String key, value;
public StringPair(String key, String value) {
this.key = key;
this.value = value;
}
}

View File

@ -63,8 +63,8 @@ public class User {
private String dateCreated; private String dateCreated;
private String dateModified; private String dateModified;
public User() { public User() {}
}
public User(JSONObject user) throws JSONException { public User(JSONObject user) throws JSONException {
fromJSON(user); fromJSON(user);
} }
@ -138,6 +138,12 @@ public class User {
return user; return user;
} }
public void clearForStorage() {
setAuthkey("");
setGpgKey("");
setCertifPublic("");
}
public int getId() { public int getId() {
return id; return id;

View File

@ -48,7 +48,7 @@
android:paddingLeft="16dp" android:paddingLeft="16dp"
android:paddingRight="16dp" android:paddingRight="16dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:paddingBottom="42dp" android:paddingBottom="32dp"
app:passwordToggleEnabled="true" app:passwordToggleEnabled="true"
app:passwordToggleTint="#FFF" app:passwordToggleTint="#FFF"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -70,18 +70,41 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<include <!--<include-->
android:layout_width="0dp" <!--android:layout_width="0dp"-->
<!--android:layout_height="0dp"-->
<!--layout="@layout/view_organisation"-->
<!--app:layout_constraintStart_toStartOf="parent"-->
<!--android:layout_marginStart="16dp"-->
<!--app:layout_constraintTop_toTopOf="parent"-->
<!--android:layout_marginTop="32dp"-->
<!--app:layout_constraintEnd_toEndOf="parent"-->
<!--android:layout_marginEnd="16dp"-->
<!--app:layout_constraintBottom_toBottomOf="parent"-->
<!--android:layout_marginBottom="16dp"/>-->
<!--<TextView-->
<!--android:id="@+id/org_title"-->
<!--android:layout_width="match_parent"-->
<!--android:layout_height="wrap_content"-->
<!--android:layout_marginTop="32dp"-->
<!--app:layout_constraintStart_toStartOf="parent"-->
<!--app:layout_constraintEnd_toEndOf="parent"-->
<!--app:layout_constraintTop_toTopOf="parent"-->
<!--tools:text="Organisation A"-->
<!--android:textAppearance="@android:style/TextAppearance.Material.Title"-->
<!--android:textAlignment="center"/>-->
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
layout="@layout/view_organisation"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"/> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
>
</android.support.v7.widget.RecyclerView>
<TextView <TextView
android:id="@+id/empty" android:id="@+id/empty"

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="16dp"
android:textColor="#000"
android:text="Test Title" android:textSize="17sp" android:textStyle="normal"/>
<TextView
android:layout_marginStart="20dp"
android:layout_marginBottom="16dp"
android:id="@+id/value"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/title"
android:text="Test Subtitle"/>
<View
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_below="@id/value"
android:layout_width="match_parent" android:layout_height="1dp"
android:background="#11000000"/>
</RelativeLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 343 KiB

After

Width:  |  Height:  |  Size: 343 KiB

View File

Before

Width:  |  Height:  |  Size: 380 KiB

After

Width:  |  Height:  |  Size: 380 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

96
images/mispbump.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.0 KiB