Allow adding user by ID or QR code (fixes #36).
|
@ -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.
|
||||
|
|
BIN
android/src/main/res/drawable-hdpi/ic_person_add_white_24dp.png
Normal file
After Width: | Height: | Size: 289 B |
BIN
android/src/main/res/drawable-hdpi/ic_qrcode_white_24dp.png
Normal file
After Width: | Height: | Size: 391 B |
BIN
android/src/main/res/drawable-mdpi/ic_person_add_white_24dp.png
Normal file
After Width: | Height: | Size: 204 B |
BIN
android/src/main/res/drawable-mdpi/ic_qrcode_white_24dp.png
Normal file
After Width: | Height: | Size: 233 B |
BIN
android/src/main/res/drawable-xhdpi/ic_person_add_white_24dp.png
Normal file
After Width: | Height: | Size: 329 B |
BIN
android/src/main/res/drawable-xhdpi/ic_qrcode_white_24dp.png
Normal file
After Width: | Height: | Size: 271 B |
After Width: | Height: | Size: 464 B |
BIN
android/src/main/res/drawable-xxhdpi/ic_qrcode_white_24dp.png
Normal file
After Width: | Height: | Size: 329 B |
After Width: | Height: | Size: 610 B |
BIN
android/src/main/res/drawable-xxxhdpi/ic_qrcode_white_24dp.png
Normal file
After Width: | Height: | Size: 380 B |
|
@ -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>
|
||||
|
|
17
android/src/main/res/menu/connections.xml
Normal 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>
|
|
@ -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 -->
|
||||
|
||||
|
|
|
@ -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).
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|