Allow adding user by ID or QR code (fixes #36).

This commit is contained in:
Felix Ableitner 2015-12-22 02:20:35 +01:00
parent 1a8ae49f79
commit 514827ad8f
19 changed files with 204 additions and 80 deletions

View file

@ -13,11 +13,13 @@ buildscript {
dependencies {
compile 'com.android.support:design:23.1.1'
compile 'com.android.support:multidex:1.0.1'
androidTestCompile 'com.android.support:multidex-instrumentation:1.0.1',
{ exclude module: 'multidex' }
compile 'org.scala-lang:scala-library:2.11.7'
compile 'com.mobsandgeeks:adapter-kit:0.5.3'
compile 'com.google.zxing:android-integration:3.2.1'
compile 'com.google.zxing:core:3.2.1'
compile project(path: ':core')
androidTestCompile 'com.android.support:multidex-instrumentation:1.0.1',
{ exclude module: 'multidex' }
}
// RtlHardcoded behaviour differs between target API versions. We only care about API 15.

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

View file

@ -1,20 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="25dp">
android:layout_height="match_parent">
<ImageView
android:id="@+id/identicon"
android:layout_width="150dp"
android:layout_height="150dp"
android:layout_marginBottom="25dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:padding="25dp">
<TextView
android:id="@+id/address"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/identicon"
android:layout_width="150dp"
android:layout_height="150dp"
android:scaleType="fitCenter" />
</LinearLayout>
<TextView
android:id="@+id/address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:layout_marginBottom="25dp" />
<ImageView
android:id="@+id/qr_code"
android:layout_width="150dp"
android:layout_height="150dp"
android:scaleType="fitCenter" />
</LinearLayout>
</ScrollView>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/add_contact"
android:title="Add Contact"
android:icon="@drawable/ic_person_add_white_24dp"
app:showAsAction="ifRoom" />
<item
android:id="@+id/scan_qr"
android:title="Scan QR-Code"
android:icon="@drawable/ic_qrcode_white_24dp"
app:showAsAction="ifRoom" />
</menu>

View file

@ -60,6 +60,10 @@
<!-- Toast shown when clicking a user that is already a contact -->
<string name="contact_already_added">You have already added %1$s as a contact</string>
<string name="enter_id">Enter user ID</string>
<string name="invalid_address">Invalid address</string>
<!-- SettingsActivity -->

View file

@ -1,5 +1,6 @@
package com.nutomic.ensichat.activities
import android.app.Activity
import android.app.AlertDialog.Builder
import android.content.DialogInterface.OnClickListener
import android.content._
@ -9,7 +10,9 @@ import android.support.v4.content.LocalBroadcastManager
import android.view._
import android.widget.AdapterView.OnItemClickListener
import android.widget._
import com.google.zxing.integration.android.IntentIntegrator
import com.nutomic.ensichat.R
import com.nutomic.ensichat.core.Address
import com.nutomic.ensichat.service.CallbackHandler
import com.nutomic.ensichat.util.Database
import com.nutomic.ensichat.views.UsersAdapter
@ -55,22 +58,79 @@ class ConnectionsActivity extends EnsichatActivity with OnItemClickListener {
LocalBroadcastManager.getInstance(this).unregisterReceiver(onContactsUpdatedReceiver)
}
override def onCreateOptionsMenu(menu: Menu): Boolean = {
getMenuInflater.inflate(R.menu.connections, menu)
true
}
override def onOptionsItemSelected(item: MenuItem): Boolean = item.getItemId match {
case R.id.add_contact =>
val et = new EditText(this)
new Builder(this)
.setTitle(R.string.enter_id)
.setView(et)
.setPositiveButton(android.R.string.ok, new OnClickListener {
override def onClick(dialog: DialogInterface, which: Int): Unit = {
addContact(et.getText.toString)
}
})
.setNegativeButton(android.R.string.cancel, null)
.show()
true
case R.id.scan_qr =>
new IntentIntegrator(this).initiateScan
true
case android.R.id.home =>
NavUtils.navigateUpFromSameTask(this)
true
case _ =>
super.onOptionsItemSelected(item)
}
/**
* Initiates adding the device as contact if it hasn't been added yet.
*/
override def onItemClick(parent: AdapterView[_], view: View, position: Int, id: Long): Unit = {
val contact = adapter.getItem(position)
if (database.getContacts.contains(contact)) {
val text = getString(R.string.contact_already_added, contact.name)
override def onItemClick(parent: AdapterView[_], view: View, position: Int, id: Long): Unit =
addContact(adapter.getItem(position).address.toString)
/**
* Receives value of scanned QR code and sets it as device ID.
*/
override def onActivityResult(requestCode: Int, resultCode: Int, intent: Intent) {
val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent)
if (scanResult != null && resultCode == Activity.RESULT_OK) {
addContact(scanResult.getContents)
}
}
/**
* Parses the address, and shows a dialog to add the user as a contact.
*
* Displays a warning toast if the address is invalid or if the user is already a contact.
*/
private def addContact(address: String): Unit = {
val parsedAddress =
try {
new Address(address)
} catch {
case e: IllegalArgumentException =>
Toast.makeText(this, R.string.invalid_address, Toast.LENGTH_LONG).show()
return
}
val user = service.get.getUser(parsedAddress)
if (database.getContacts.contains(user)) {
val text = getString(R.string.contact_already_added, user.name)
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
return
}
new Builder(this)
.setMessage(getString(R.string.dialog_add_contact, contact.name))
.setMessage(getString(R.string.dialog_add_contact, user.name))
.setPositiveButton(android.R.string.yes, new OnClickListener {
override def onClick(dialog: DialogInterface, which: Int): Unit = {
database.addContact(contact)
database.addContact(user)
Toast.makeText(ConnectionsActivity.this, R.string.toast_contact_added, Toast.LENGTH_SHORT)
.show()
}
@ -79,14 +139,6 @@ class ConnectionsActivity extends EnsichatActivity with OnItemClickListener {
.show()
}
override def onOptionsItemSelected(item: MenuItem): Boolean = item.getItemId match {
case android.R.id.home =>
NavUtils.navigateUpFromSameTask(this)
true
case _ =>
super.onOptionsItemSelected(item);
}
/**
* Fetches connections and displays them (excluding contacts).
*/

View file

@ -97,12 +97,13 @@ class ContactsFragment extends ListFragment with OnClickListener {
true
case R.id.my_address =>
val prefs = PreferenceManager.getDefaultSharedPreferences(getActivity)
val fragment = new IdenticonFragment()
val fragment = new UserInfoFragment()
val bundle = new Bundle()
bundle.putString(
IdenticonFragment.ExtraAddress, ChatService.newCrypto(getActivity).localAddress.toString)
UserInfoFragment.ExtraAddress, ChatService.newCrypto(getActivity).localAddress.toString)
bundle.putString(
IdenticonFragment.ExtraUserName, prefs.getString(SettingsInterface.KeyUserName, ""))
UserInfoFragment.ExtraUserName, prefs.getString(SettingsInterface.KeyUserName, ""))
bundle.putBoolean(UserInfoFragment.ExtraShowQr, true)
fragment.setArguments(bundle)
fragment.show(getFragmentManager, "dialog")
true

View file

@ -1,42 +0,0 @@
package com.nutomic.ensichat.fragments
import android.app.{AlertDialog, Dialog, DialogFragment}
import android.os.Bundle
import android.view.LayoutInflater
import android.widget.{ImageView, TextView}
import com.nutomic.ensichat.R
import com.nutomic.ensichat.core.Address
import com.nutomic.ensichat.util.IdenticonGenerator
object IdenticonFragment {
val ExtraAddress = "address"
val ExtraUserName = "user_name"
}
/**
* Displays identicon, username and address for a user.
*
* Use [[IdenticonFragment#getInstance]] to invoke.
*/
class IdenticonFragment extends DialogFragment {
private lazy val address = new Address(getArguments.getString(IdenticonFragment.ExtraAddress))
private lazy val userName = getArguments.getString(IdenticonFragment.ExtraUserName)
override def onCreateDialog(savedInstanceState: Bundle): Dialog = {
val view = LayoutInflater.from(getActivity).inflate(R.layout.fragment_identicon, null)
view.findViewById(R.id.identicon)
.asInstanceOf[ImageView]
.setImageBitmap(IdenticonGenerator.generate(address, (150, 150), getActivity))
view.findViewById(R.id.address)
.asInstanceOf[TextView]
.setText(getString(R.string.address_colon, address.toString))
new AlertDialog.Builder(getActivity)
.setTitle(userName)
.setView(view)
.setPositiveButton(android.R.string.ok, null)
.create()
}
}

View file

@ -0,0 +1,76 @@
package com.nutomic.ensichat.fragments
import android.app.{AlertDialog, Dialog, DialogFragment}
import android.graphics.{Bitmap, Color}
import android.os.Bundle
import android.view.LayoutInflater
import android.widget.{ImageView, TextView}
import com.google.zxing.BarcodeFormat
import com.google.zxing.common.BitMatrix
import com.google.zxing.qrcode.QRCodeWriter
import com.nutomic.ensichat.R
import com.nutomic.ensichat.core.Address
import com.nutomic.ensichat.util.IdenticonGenerator
object UserInfoFragment {
val ExtraAddress = "address"
val ExtraUserName = "user_name"
val ExtraShowQr = "show_qr"
}
/**
* Displays identicon, username and address for a user.
*
* Use [[UserInfoFragment#getInstance]] to invoke.
*/
class UserInfoFragment extends DialogFragment {
private lazy val address = new Address(getArguments.getString(UserInfoFragment.ExtraAddress))
private lazy val userName = getArguments.getString(UserInfoFragment.ExtraUserName)
private lazy val showQr = getArguments.getBoolean(UserInfoFragment.ExtraShowQr)
override def onCreateDialog(savedInstanceState: Bundle): Dialog = {
val view = LayoutInflater.from(getActivity).inflate(R.layout.fragment_identicon, null)
view.findViewById(R.id.identicon)
.asInstanceOf[ImageView]
.setImageBitmap(IdenticonGenerator.generate(address, (150, 150), getActivity))
view.findViewById(R.id.address)
.asInstanceOf[TextView]
.setText(getString(R.string.address_colon, address.toString()))
if (showQr) {
val matrix = new QRCodeWriter().encode(address.toString(), BarcodeFormat.QR_CODE, 150, 150)
view.findViewById(R.id.qr_code)
.asInstanceOf[ImageView]
.setImageBitmap(renderMatrix(matrix))
}
new AlertDialog.Builder(getActivity)
.setTitle(userName)
.setView(view)
.setPositiveButton(android.R.string.ok, null)
.create()
}
/**
* Converts a [[BitMatrix]] instance into a [[Bitmap]].
*/
private def renderMatrix(bitMatrix: BitMatrix): Bitmap = {
val height = bitMatrix.getHeight
val width = bitMatrix.getWidth
val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
for (x <- 0 until width) {
for (y <- 0 until height) {
val color =
if (bitMatrix.get(x,y))
Color.BLACK
else
Color.WHITE
bmp.setPixel(x, y, color)
}
}
bmp
}
}

View file

@ -8,7 +8,7 @@ import android.view.{LayoutInflater, View, ViewGroup}
import android.widget.{ArrayAdapter, ImageView, TextView}
import com.nutomic.ensichat.R
import com.nutomic.ensichat.core.User
import com.nutomic.ensichat.fragments.IdenticonFragment
import com.nutomic.ensichat.fragments.UserInfoFragment
import com.nutomic.ensichat.util.IdenticonGenerator
/**
@ -40,10 +40,10 @@ class UsersAdapter(activity: Activity) extends ArrayAdapter[User](activity, 0) w
override def onClick (v: View): Unit = {
val user = v.getTag.asInstanceOf[User]
val fragment = new IdenticonFragment()
val fragment = new UserInfoFragment()
val bundle = new Bundle()
bundle.putString(IdenticonFragment.ExtraAddress, user.address.toString)
bundle.putString(IdenticonFragment.ExtraUserName, user.name)
bundle.putString(UserInfoFragment.ExtraAddress, user.address.toString)
bundle.putString(UserInfoFragment.ExtraUserName, user.name)
fragment.setArguments(bundle)
fragment.show(activity.getFragmentManager, "dialog")
}