Skip to content
This repository has been archived by the owner on Dec 14, 2021. It is now read-only.

Copy and reveal credentials #124

Merged
merged 12 commits into from
Oct 8, 2018
4 changes: 4 additions & 0 deletions app/src/main/java/mozilla/lockbox/LockboxApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ import io.sentry.android.AndroidSentryClientFactory
import mozilla.components.support.base.log.Log
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.base.log.sink.AndroidLogSink
import mozilla.lockbox.store.ClipboardStore

val log: Logger = Logger("Lockbox")
class LockboxApplication : Application() {
override fun onCreate() {
super.onCreate()
Log.addSink(AndroidLogSink())

// use context for system service
ClipboardStore.shared.apply(this)

// Set up Sentry using DSN (client key) from the Project Settings page on Sentry
val ctx = this.applicationContext
// Retrieved from environment's local (or bitrise's "Secrets") environment variable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,71 @@

package mozilla.lockbox.presenter

import io.reactivex.Observable
import io.reactivex.rxkotlin.addTo
import mozilla.lockbox.R
import mozilla.lockbox.flux.Dispatcher
import mozilla.lockbox.flux.Presenter
import mozilla.lockbox.model.ItemDetailViewModel
import mozilla.lockbox.model.titleFromHostname
import mozilla.lockbox.store.DataStore
import mozilla.lockbox.store.ClipboardStore

interface ItemDetailView {
var itemId: String?
fun updateItem(item: ItemDetailViewModel)
fun copyNotification(strId: Int)
mihainisipeanusv marked this conversation as resolved.
Show resolved Hide resolved

val btnUsernameCopyClicks: Observable<Unit>
val btnPasswordCopyClicks: Observable<Unit>
val btnTogglePasswordClicks: Observable<Unit>
mihainisipeanusv marked this conversation as resolved.
Show resolved Hide resolved

var isPasswordVisible: Boolean
}

class ItemDetailPresenter(
private val view: ItemDetailView,
private val dispatcher: Dispatcher = Dispatcher.shared,
private val dataStore: DataStore = DataStore.shared
private val dataStore: DataStore = DataStore.shared,
private val clipboardStore: ClipboardStore = ClipboardStore.shared
mihainisipeanusv marked this conversation as resolved.
Show resolved Hide resolved

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: whitespace.

) : Presenter() {

override fun onViewReady() {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: whitespace

this.view.btnUsernameCopyClicks
.subscribe {
view.itemId?.let {
dataStore.get(it)
.subscribe {
clipboardStore.clipboardCopy("username", it!!.username!!)
view.copyNotification(R.string.toast_username_copied)
}
.addTo(compositeDisposable)
}
}
.addTo(compositeDisposable)

this.view.btnPasswordCopyClicks
.subscribe {
view.itemId?.let {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract this statement into a method, parameterized for username and password.

At some point, it would be good to not hit the database each time, and stash the itemViewModel, but I don't think we need to worry about that now.

dataStore.get(it)
.subscribe {
clipboardStore.clipboardCopy("password", it!!.password)
view.copyNotification(R.string.toast_password_copied)
}
.addTo(compositeDisposable)
}
}
.addTo(compositeDisposable)

this.view.btnTogglePasswordClicks
.subscribe {
view.isPasswordVisible = view.isPasswordVisible.not()
}
.addTo(compositeDisposable)
}

override fun onResume() {
super.onResume()
val itemId = view?.itemId ?: return
Expand All @@ -32,5 +80,7 @@ class ItemDetailPresenter(
}
.subscribe(view::updateItem)
.addTo(compositeDisposable)

view.isPasswordVisible = false
}
}
26 changes: 26 additions & 0 deletions app/src/main/java/mozilla/lockbox/store/ClipboardStore.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package mozilla.lockbox.store

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import mozilla.lockbox.flux.Dispatcher

open class ClipboardStore(
val dispatcher: Dispatcher = Dispatcher.shared
) {
companion object {
val shared = ClipboardStore()
}

private lateinit var clipboardManager: ClipboardManager

fun apply(ctx: Context) {
clipboardManager = ctx.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
}
mihainisipeanusv marked this conversation as resolved.
Show resolved Hide resolved

fun clipboardCopy(label: String, str: String) {

mihainisipeanusv marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whitespace.

val clip = ClipData.newPlainText(label, str)
clipboardManager.primaryClip = clip
}
}
32 changes: 31 additions & 1 deletion app/src/main/java/mozilla/lockbox/view/ItemDetailFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@
package mozilla.lockbox.view

import android.os.Bundle
import android.support.annotation.StringRes
import android.text.method.PasswordTransformationMethod
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import com.jakewharton.rxbinding2.view.clicks
import io.reactivex.Observable
import kotlinx.android.synthetic.main.fragment_item_detail.*
import kotlinx.android.synthetic.main.fragment_item_detail.view.*
import kotlinx.android.synthetic.main.include_backable.*
import kotlinx.android.synthetic.main.include_backable.view.*
import mozilla.lockbox.R
import mozilla.lockbox.model.ItemDetailViewModel
import mozilla.lockbox.presenter.ItemDetailPresenter
Expand All @@ -35,6 +40,27 @@ class ItemDetailFragment : BackableFragment(), ItemDetailView {
return view
}

override val btnUsernameCopyClicks: Observable<Unit>
get() = view!!.btnUsernameCopy.clicks()

override val btnPasswordCopyClicks: Observable<Unit>
get() = view!!.btnPasswordCopy.clicks()

override val btnTogglePasswordClicks: Observable<Unit>
get() = view!!.btnPasswordToggle.clicks()

override var isPasswordVisible: Boolean = false
set(value) {
field = value
if (value) {
inputPassword.transformationMethod = null
btnPasswordToggle.setImageResource(R.drawable.ic_icon_hide)
} else {
inputPassword.transformationMethod = PasswordTransformationMethod.getInstance()
btnPasswordToggle.setImageResource(R.drawable.ic_icon_show)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.


override fun updateItem(item: ItemDetailViewModel) {
toolbar.title = item.title

Expand All @@ -50,6 +76,10 @@ class ItemDetailFragment : BackableFragment(), ItemDetailView {
inputUsername.setText(item.username, TextView.BufferType.NORMAL)
inputPassword.setText(item.password, TextView.BufferType.NORMAL)
}

override fun copyNotification(@StringRes strId: Int) {
mihainisipeanusv marked this conversation as resolved.
Show resolved Hide resolved
Toast.makeText(activity, getString(strId), Toast.LENGTH_SHORT).show()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

var EditText.readOnly: Boolean
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/res/drawable/ic_icon_copy.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">

<path
android:pathData="M16.545375,9.329625 L13.170375,5.954625 C12.9594457,5.74363195 12.6733442,5.62506372 12.375,5.625 L11.25,5.625 L11.25,4.5 C11.2499363,4.20165583 11.131368,3.91555432 10.920375,3.704625 L7.545375,0.329625 C7.33444568,0.118631953 7.04834417,6.37170812e-05 6.75,0 L3.375,0 C2.13235931,-7.6089797e-17 1.125,1.00735931 1.125,2.25 L1.125,10.125 C1.125,11.3676407 2.13235931,12.375 3.375,12.375 L6.75,12.375 L6.75,15.75 C6.75,16.9926407 7.75735931,18 9,18 L14.625,18 C15.8676407,18 16.875,16.9926407 16.875,15.75 L16.875,10.125 C16.8749363,9.82665583 16.756368,9.54055432 16.545375,9.329625 Z M14.15925,10.125 L12.375,10.125 L12.375,8.34075 L14.15925,10.125 Z M8.53425,4.5 L6.75,4.5 L6.75,2.71575 L8.53425,4.5 Z M6.75,7.875 L6.75,10.125 L3.375,10.125 L3.375,2.25 L5.625,2.25 L5.625,5.0625 C5.625,5.37316017 5.87683983,5.625 6.1875,5.625 L9,5.625 C7.75735931,5.625 6.75,6.63235931 6.75,7.875 Z M9,15.75 L9,7.875 L11.25,7.875 L11.25,10.6875 C11.25,10.9981602 11.5018398,11.25 11.8125,11.25 L14.625,11.25 L14.625,15.75 L9,15.75 Z"
android:strokeWidth="1"
android:fillColor="#4A4A4F"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/ic_icon_hide.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:pathData="M13.5001378,7.875 L9.00013784,12.375 C11.4854192,12.375 13.5001378,10.3602814 13.5001378,7.875 Z M17.9495128,8.6625 C18.018604,8.88219601 18.018604,9.11780399 17.9495128,9.3375 C16.6702783,13.1878327 13.0573283,15.7766216 9.00013784,15.75 C8.01052596,15.7487738 7.02713331,15.5935412 6.08526284,15.289875 L7.95388784,13.42125 C8.30030917,13.4724565 8.64995453,13.4987739 9.00013784,13.5 C11.9447269,13.5263468 14.6019241,11.7378916 15.6860128,9 C15.3985795,8.24211122 14.9720031,7.54463215 14.4282628,6.9435 L16.0145128,5.35725 C16.8945093,6.30345955 17.5551757,7.43196981 17.9495128,8.6625 Z M16.5455128,1.454625 C16.9846925,1.89393733 16.9846925,2.60606267 16.5455128,3.045375 L3.04551284,16.545375 C2.76305406,16.8378262 2.34477521,16.9551142 1.9514421,16.8521593 C1.55810899,16.7492044 1.25093341,16.4420289 1.14797853,16.0486957 C1.04502366,15.6553626 1.16231168,15.2370838 1.45476284,14.954625 L2.89926284,13.50675 C1.56431174,12.4209648 0.574503528,10.9698716 0.0507628361,9.33075 C-0.0169209454,9.11314152 -0.0169209454,8.88010848 0.0507628361,8.6625 C0.915942709,6.08251245 2.85677943,4.00409597 5.37156688,2.96452103 C7.88635433,1.92494608 10.728255,2.02625156 13.1626378,3.24225 L14.9547628,1.454625 C15.3940752,1.01544532 16.1062005,1.01544532 16.5455128,1.454625 Z M9.56263784,5.625 C8.63065732,5.625 7.87513784,6.38051948 7.87513784,7.3125 C7.87809346,7.64390747 7.98123585,7.96666865 8.17101284,8.238375 L10.4885128,5.920875 C10.2168065,5.73109801 9.89404531,5.62795562 9.56263784,5.625 Z M2.31426284,9 C2.75531187,10.1563686 3.50944026,11.1671966 4.49226284,11.919375 L5.60151284,10.810125 C4.33541627,9.36765229 4.14366735,7.27340871 5.12676284,5.625 C3.84259111,6.40890234 2.85374236,7.59552083 2.31426284,9 Z"
android:strokeWidth="1"
android:fillColor="#4A4A4F"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/ic_icon_show.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<path
android:pathData="M17.9495128,6.6625 C16.6702783,2.81216726 13.0573283,0.223378405 9.00013784,0.25 C4.94294738,0.223378405 1.32999732,2.81216726 0.0507628361,6.6625 C-0.0169209454,6.88010848 -0.0169209454,7.11314152 0.0507628361,7.33075 C1.32764302,11.1837517 4.94114947,13.7756647 9.00013784,13.75 C13.0573283,13.7766216 16.6702783,11.1878327 17.9495128,7.3375 C18.018604,7.11780399 18.018604,6.88219601 17.9495128,6.6625 Z M9.56263784,3.625 C10.4946184,3.625 11.2501378,4.38051948 11.2501378,5.3125 C11.2501378,6.24448052 10.4946184,7 9.56263784,7 C8.63065732,7 7.87513784,6.24448052 7.87513784,5.3125 C7.87513784,4.38051948 8.63065732,3.625 9.56263784,3.625 Z M9.00013784,11.5 C6.05554876,11.5263468 3.39835155,9.73789164 2.31426284,7 C2.85374236,5.59552083 3.84259111,4.40890234 5.12676284,3.625 C4.72125214,4.30581733 4.50492353,5.08258075 4.50013784,5.875 C4.50013787,8.36028135 6.51485649,10.3749999 9.00013784,10.3749999 C11.4854192,10.3749999 13.5001378,8.36028135 13.5001378,5.875 C13.4960332,5.08278883 13.2804853,4.30603972 12.8757628,3.625 C14.1599346,4.40890234 15.1487833,5.59552083 15.6882628,7 C14.603878,9.73866412 11.9455457,11.5272813 9.00013784,11.5 Z"
android:strokeWidth="1"
android:fillColor="#4A4A4F"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>
41 changes: 37 additions & 4 deletions app/src/main/res/layout/fragment_item_detail.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

Expand All @@ -19,6 +18,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="?attr/actionBarSize"
app:layout_constraintTop_toTopOf="parent">

<EditText
android:id="@+id/inputHostname"
android:layout_width="match_parent"
Expand All @@ -32,22 +32,35 @@
android:id="@+id/inputLayoutUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/inputLayoutHostname"
>
app:layout_constraintTop_toBottomOf="@id/inputLayoutHostname">

<EditText
android:id="@+id/inputUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/hint_username"
android:inputType="none"
android:singleLine="true" />
android:singleLine="true"
app:layout_constraintRight_toRightOf="@+id/inputLayoutUsername"
app:layout_constraintTop_toTopOf="@+id/inputLayoutUsername" />
</android.support.design.widget.TextInputLayout>

<ImageView
android:id="@+id/btnUsernameCopy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="@+id/inputLayoutUsername"
app:layout_constraintRight_toRightOf="@+id/inputLayoutUsername"
app:layout_constraintTop_toTopOf="@+id/inputLayoutUsername"
app:srcCompat="@drawable/ic_icon_copy" />

<android.support.design.widget.TextInputLayout
android:id="@+id/inputLayoutPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/inputLayoutUsername">

<EditText
android:id="@+id/inputPassword"
android:layout_width="match_parent"
Expand All @@ -56,4 +69,24 @@
android:inputType="none|textPassword"
android:singleLine="true" />
</android.support.design.widget.TextInputLayout>

<ImageView
android:id="@+id/btnPasswordCopy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="@+id/inputLayoutPassword"
app:layout_constraintRight_toRightOf="@+id/inputLayoutPassword"
app:layout_constraintTop_toTopOf="@+id/inputLayoutPassword"
app:srcCompat="@drawable/ic_icon_copy" />

<ImageView
android:id="@+id/btnPasswordToggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="@+id/inputLayoutPassword"
app:layout_constraintRight_toLeftOf="@+id/btnPasswordCopy"
app:layout_constraintTop_toTopOf="@+id/inputLayoutPassword"
app:srcCompat="@drawable/ic_icon_show" />
</android.support.constraint.ConstraintLayout>
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@
<string name="hint_hostname">Web address</string>
<string name="hint_username">Username</string>
<string name="hint_password">Password</string>
<string name="toast_username_copied">Username copied</string>
<string name="toast_password_copied">Password copied</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package mozilla.lockbox.presenter

import android.support.annotation.StringRes
import io.reactivex.Observable
import io.reactivex.observers.TestObserver
import io.reactivex.subjects.PublishSubject
Expand All @@ -21,12 +22,29 @@ import org.mozilla.sync15.logins.ServerPassword

class ItemDetailPresenterTest {
class FakeView : ItemDetailView {

override var itemId: String? = null
var item: ItemDetailViewModel? = null
val tapStub: PublishSubject<Unit> = PublishSubject.create<Unit>()

override val btnUsernameCopyClicks: Observable<Unit>
get() = tapStub

override val btnPasswordCopyClicks: Observable<Unit>
get() = tapStub

override val btnTogglePasswordClicks: Observable<Unit>
get() = tapStub

override fun updateItem(item: ItemDetailViewModel) {
this.item = item
}

override fun copyNotification(@StringRes strId: Int) {
// notification Test
}

override var isPasswordVisible: Boolean = false
}

class FakeDataStore : DataStore() {
Expand All @@ -38,6 +56,7 @@ class ItemDetailPresenterTest {

val view = FakeView()
val dataStore = FakeDataStore()

val subject = ItemDetailPresenter(view, dataStore = dataStore)

val dispatcherObserver = TestObserver.create<Action>()
Expand Down
Loading