aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--app/build.gradle.kts2
-rw-r--r--app/src/androidTest/java/com/zeapo/pwdstore/DecryptTest.kt12
-rw-r--r--app/src/androidTest/java/com/zeapo/pwdstore/EncryptTest.kt4
-rw-r--r--app/src/debug/res/drawable/ic_launcher_foreground.xml37
-rw-r--r--app/src/main/AndroidManifest.xml11
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordEntry.kt4
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordFragment.java4
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java10
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/PasswordStore.java26
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/SelectFolderActivity.kt2
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java8
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/SshKeyGen.java50
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/UserPreference.kt249
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.kt25
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.kt11
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.kt26
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt27
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt8
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt14
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java726
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitActivity.kt660
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt22
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt9
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt9
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt52
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt10
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java14
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java155
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.kt118
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt9
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java29
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.kt20
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.java110
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt81
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java133
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.kt116
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.java284
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt282
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/widget/MultiselectableLinearLayout.kt51
-rw-r--r--app/src/main/res/color/text_color.xml5
-rw-r--r--app/src/main/res/drawable/autofill_row_background.xml5
-rw-r--r--app/src/main/res/drawable/bottom_line.xml4
-rw-r--r--app/src/main/res/drawable/divider.xml2
-rw-r--r--app/src/main/res/drawable/ic_action_secure.xml2
-rw-r--r--app/src/main/res/drawable/ic_folder_tinted_24dp.xml (renamed from app/src/main/res/drawable/ic_folder_grey600_24dp.xml)2
-rw-r--r--app/src/main/res/drawable/ic_launcher_foreground.xml9
-rw-r--r--app/src/main/res/drawable/password_row_background.xml12
-rw-r--r--app/src/main/res/drawable/red_rectangle.xml2
-rw-r--r--app/src/main/res/layout/activity_git_clone.xml358
-rw-r--r--app/src/main/res/layout/activity_git_config.xml198
-rw-r--r--app/src/main/res/layout/activity_pwdstore.xml4
-rw-r--r--app/src/main/res/layout/autofill_instructions.xml17
-rw-r--r--app/src/main/res/layout/autofill_recycler_view.xml4
-rw-r--r--app/src/main/res/layout/autofill_row_layout.xml36
-rw-r--r--app/src/main/res/layout/decrypt_layout.xml423
-rw-r--r--app/src/main/res/layout/encrypt_layout.xml146
-rw-r--r--app/src/main/res/layout/fragment_autofill.xml16
-rw-r--r--app/src/main/res/layout/fragment_pwgen.xml7
-rw-r--r--app/src/main/res/layout/fragment_show_ssh_key.xml5
-rw-r--r--app/src/main/res/layout/fragment_ssh_keygen.xml18
-rw-r--r--app/src/main/res/layout/fragment_to_clone_or_not.xml14
-rw-r--r--app/src/main/res/layout/git_passphrase_layout.xml1
-rw-r--r--app/src/main/res/layout/password_recycler_view.xml4
-rw-r--r--app/src/main/res/layout/password_row_layout.xml18
-rw-r--r--app/src/main/res/values-night/colors.xml18
-rw-r--r--app/src/main/res/values-night/styles.xml7
-rw-r--r--app/src/main/res/values/attrs.xml9
-rw-r--r--app/src/main/res/values/bools.xml5
-rw-r--r--app/src/main/res/values/colors.xml31
-rw-r--r--app/src/main/res/values/strings.xml14
-rw-r--r--app/src/main/res/values/styles.xml48
-rw-r--r--app/src/main/res/xml/preference.xml92
73 files changed, 2500 insertions, 2459 deletions
diff --git a/.gitignore b/.gitignore
index ab3a14ea..71d6cdf7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,4 +34,7 @@ project.properties
.idea
*.iml
+# Visual Studio Code
+.vscode/
+
captures/
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 89add6f8..c1ea485e 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,4 +1,4 @@
-import org.gradle.api.JavaVersion.*
+import org.gradle.api.JavaVersion.VERSION_1_8
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
diff --git a/app/src/androidTest/java/com/zeapo/pwdstore/DecryptTest.kt b/app/src/androidTest/java/com/zeapo/pwdstore/DecryptTest.kt
index f0c502d9..448edc00 100644
--- a/app/src/androidTest/java/com/zeapo/pwdstore/DecryptTest.kt
+++ b/app/src/androidTest/java/com/zeapo/pwdstore/DecryptTest.kt
@@ -5,13 +5,17 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.SystemClock
-import androidx.test.platform.app.InstrumentationRegistry
+import android.util.Log
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import android.util.Log
import com.zeapo.pwdstore.crypto.PgpActivity
-import kotlinx.android.synthetic.main.decrypt_layout.*
+import kotlinx.android.synthetic.main.decrypt_layout.crypto_extra_show
+import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_category_decrypt
+import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_file
+import kotlinx.android.synthetic.main.decrypt_layout.crypto_password_show
+import kotlinx.android.synthetic.main.decrypt_layout.crypto_username_show
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.junit.Assert.assertEquals
diff --git a/app/src/androidTest/java/com/zeapo/pwdstore/EncryptTest.kt b/app/src/androidTest/java/com/zeapo/pwdstore/EncryptTest.kt
index 64e7e143..1b164c63 100644
--- a/app/src/androidTest/java/com/zeapo/pwdstore/EncryptTest.kt
+++ b/app/src/androidTest/java/com/zeapo/pwdstore/EncryptTest.kt
@@ -4,7 +4,9 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import androidx.test.espresso.Espresso.onView
-import androidx.test.espresso.action.ViewActions.*
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.action.ViewActions.scrollTo
+import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
diff --git a/app/src/debug/res/drawable/ic_launcher_foreground.xml b/app/src/debug/res/drawable/ic_launcher_foreground.xml
index 30de0468..9ca26a38 100644
--- a/app/src/debug/res/drawable/ic_launcher_foreground.xml
+++ b/app/src/debug/res/drawable/ic_launcher_foreground.xml
@@ -3,24 +3,23 @@
android:height="108dp"
android:viewportWidth="110.34687"
android:viewportHeight="110.34687">
- <group
- android:translateX="24.828047"
- android:translateY="24.828047">
- <path
- android:fillColor="#00000000"
- android:pathData="m18.8,30.2129v-11.546c0,-6.4144 5.1315,-11.546 11.546,-11.546 6.4144,0 11.546,5.1315 11.546,11.546v11.546"
- android:strokeWidth="5.349"
- android:strokeColor="#013e5b" />
- <path
- android:fillColor="#c74c00"
- android:pathData="M15.4099,21.8429L45.2811,21.8429A2.2639,2.2639 0,0 1,47.545 24.1068L47.545,53.977A2.2639,2.2639 0,0 1,45.2811 56.2409L15.4099,56.2409A2.2639,2.2639 0,0 1,13.146 53.977L13.146,24.1068A2.2639,2.2639 0,0 1,15.4099 21.8429z" />
- <path
- android:fillColor="#fff"
- android:pathData="m44.8267,37.6961 l-13.1408,-13.1393c-0.7569,-0.7566 -1.9838,-0.7566 -2.7408,0l-13.08,13.0785c-0.7567,0.7573 -0.7567,1.9846 0,2.7419l13.1415,13.14c0.7572,0.7567 1.9842,0.7567 2.7414,0l13.0778,-13.0785c0.7572,-0.7572 0.7572,-1.9849 0,-2.7421"
- android:strokeWidth=".35344" />
- <path
- android:fillColor="#f47a68"
- android:pathData="m30.3156,23.9881c-0.496,0 -0.992,0.1893 -1.3705,0.5676l-2.7282,2.7288 3.4612,3.4606c0.8044,-0.2715 1.727,-0.0892 2.368,0.5517 0.6237,0.624 0.8361,1.5493 0.5471,2.3828l3.3357,3.3357c0.8076,-0.2777 1.738,-0.098 2.3828,0.5476 0.9008,0.9005 0.9008,2.361 0,3.2615 -1.7823,1.7848 -4.7253,-0.1767 -3.7641,-2.5087l-3.1111,-3.1106c-2.2315,0.5285 -3.8934,-1.2655 -3.149,-3.1674l-0.6863,-0.6863l0,15.9165l5.4913,0l0,-5.8608c-0.0315,-0.7566 1.1201,-0.7566 1.0886,0l0,6.4043c0.0005,0.3013 -0.2438,0.5457 -0.545,0.5455l-6.5804,0c-0.3015,0.0005 -0.546,-0.2441 -0.5456,-0.5455l0,-17.4333c-0.0005,-0.0363 0.0029,-0.0728 0.0097,-0.1085l-1.6444,-1.6444 -9.0106,9.0084c-0.7567,0.7573 -0.7567,1.9848 0,2.7421l13.1415,13.14c0.7572,0.7567 1.9844,0.7567 2.7416,0l13.0778,-13.0785c0.7572,-0.7572 0.7572,-1.9849 0,-2.7421l-13.14,-13.1393c-0.3785,-0.3783 -0.8746,-0.5676 -1.3705,-0.5676zM29.9512,39.1825c0.1001,0 0.1808,0.0381 0.2426,0.1146 0.0648,0.0736 0.1326,0.1975 0.2032,0.371 0.0705,0.1706 0.1089,0.2615 0.1146,0.2733 0.0059,-0.0119 0.0424,-0.1026 0.11,-0.2733 0.0707,-0.1705 0.1401,-0.2946 0.2078,-0.371 0.0677,-0.0765 0.1513,-0.1146 0.2513,-0.1146 0.1412,0 0.2646,0.047 0.3705,0.1412 0.1059,0.0941 0.1592,0.2103 0.1592,0.3485 0,0.0676 -0.0179,0.1368 -0.0532,0.2073 -0.0323,0.0707 -0.0777,0.1444 -0.1366,0.2211 -0.056,0.0734 -0.1164,0.1571 -0.1812,0.2513 0.0648,-0.0089 0.1589,-0.0251 0.2825,-0.0486 0.1265,-0.0236 0.2268,-0.0354 0.3004,-0.0354 0.0969,0 0.1762,0.0224 0.238,0.0666 0.0648,0.0442 0.1118,0.1042 0.1413,0.1807 0.0294,0.0734 0.044,0.1544 0.044,0.2426 0,0.1384 -0.0337,0.2544 -0.1013,0.3485 -0.0677,0.0912 -0.1749,0.1372 -0.3219,0.1372 -0.0472,0 -0.14,-0.0119 -0.2784,-0.0354 -0.1382,-0.0236 -0.2397,-0.0386 -0.3045,-0.0446 0.1531,0.2177 0.2534,0.3652 0.3004,0.4417 0.047,0.0736 0.0706,0.1544 0.0706,0.2426 0,0.1412 -0.0533,0.2556 -0.1592,0.3439 -0.1028,0.0853 -0.2264,0.1279 -0.3705,0.1279 -0.1028,0 -0.1882,-0.0366 -0.2559,-0.11 -0.0646,-0.0767 -0.1309,-0.1955 -0.1986,-0.3572 -0.0646,-0.1647 -0.1028,-0.259 -0.1146,-0.2825 -0.0117,0.0235 -0.0516,0.1178 -0.1192,0.2825 -0.0648,0.1646 -0.1281,0.2836 -0.1899,0.3572 -0.0617,0.0735 -0.1454,0.11 -0.2513,0.11 -0.15,0 -0.2779,-0.0427 -0.3838,-0.1279 -0.1059,-0.0883 -0.1587,-0.2027 -0.1587,-0.3439 0,-0.0618 0.0156,-0.1263 0.0481,-0.194 0.0323,-0.0707 0.0665,-0.1311 0.1018,-0.1812 0.0382,-0.0499 0.1118,-0.1532 0.2206,-0.3091 -0.0736,0.0059 -0.1751,0.021 -0.3045,0.0446 -0.1294,0.0236 -0.2176,0.0354 -0.2646,0.0354 -0.1472,0 -0.2577,-0.046 -0.3311,-0.1372 -0.0707,-0.0941 -0.1059,-0.2101 -0.1059,-0.3485 0,-0.1411 0.0352,-0.2573 0.1059,-0.3485 0.0734,-0.0942 0.184,-0.1412 0.3311,-0.1412 0.0736,0 0.1628,0.0107 0.2687,0.0312 0.1059,0.0206 0.2062,0.038 0.3004,0.0527 -0.0824,-0.1177 -0.1648,-0.2366 -0.2472,-0.3572 -0.0824,-0.1206 -0.1233,-0.2282 -0.1233,-0.3224 0,-0.1382 0.0543,-0.2544 0.1633,-0.3485 0.1088,-0.0942 0.2351,-0.1412 0.3792,-0.1412zM29.9512,43.2235c0.1001,0 0.1808,0.0383 0.2426,0.1146 0.0648,0.0735 0.1326,0.197 0.2032,0.3705 0.0705,0.1707 0.1089,0.262 0.1146,0.2738 0.0059,-0.0119 0.0424,-0.1031 0.11,-0.2738 0.0707,-0.1705 0.1401,-0.2941 0.2078,-0.3705 0.0677,-0.0765 0.1513,-0.1146 0.2513,-0.1146 0.1412,0 0.2646,0.047 0.3705,0.1412 0.1059,0.0941 0.1592,0.2102 0.1592,0.3485 0,0.0675 -0.0179,0.1366 -0.0532,0.2073 -0.0323,0.0705 -0.0777,0.1441 -0.1366,0.2206 -0.056,0.0735 -0.1164,0.1576 -0.1812,0.2518 0.0648,-0.0089 0.1589,-0.0251 0.2825,-0.0486 0.1265,-0.0236 0.2268,-0.0353 0.3004,-0.0353 0.0969,0 0.1762,0.0219 0.238,0.0661 0.0648,0.0442 0.1118,0.1047 0.1413,0.1812 0.0294,0.0736 0.044,0.1542 0.044,0.2426 0,0.1382 -0.0337,0.2544 -0.1013,0.3485 -0.0677,0.0912 -0.1749,0.1366 -0.3219,0.1366 -0.0472,0 -0.14,-0.0119 -0.2784,-0.0354 -0.1382,-0.0236 -0.2397,-0.038 -0.3045,-0.044 0.1531,0.2177 0.2534,0.3647 0.3004,0.4411 0.047,0.0736 0.0706,0.1549 0.0706,0.2431 0,0.1411 -0.0533,0.2558 -0.1592,0.3439 -0.1028,0.0854 -0.2264,0.1279 -0.3705,0.1279 -0.1028,0 -0.1882,-0.0371 -0.2559,-0.1105 -0.0646,-0.0765 -0.1309,-0.1955 -0.1986,-0.3572 -0.0646,-0.1647 -0.1028,-0.259 -0.1146,-0.2825 -0.0117,0.0236 -0.0516,0.1178 -0.1192,0.2825 -0.0648,0.1648 -0.1281,0.2836 -0.1899,0.3572 -0.0617,0.0735 -0.1454,0.1105 -0.2513,0.1105 -0.15,0 -0.2779,-0.0425 -0.3838,-0.1279 -0.1059,-0.0881 -0.1587,-0.2028 -0.1587,-0.3439 0,-0.0617 0.0156,-0.1268 0.0481,-0.1945 0.0323,-0.0705 0.0665,-0.1311 0.1018,-0.1812 0.0382,-0.0499 0.1118,-0.1527 0.2206,-0.3086 -0.0736,0.0059 -0.1751,0.0205 -0.3045,0.044 -0.1294,0.0236 -0.2176,0.0354 -0.2646,0.0354 -0.1472,0 -0.2577,-0.0454 -0.3311,-0.1366 -0.0707,-0.0941 -0.1059,-0.2103 -0.1059,-0.3485 0,-0.1412 0.0352,-0.2573 0.1059,-0.3485 0.0734,-0.0942 0.184,-0.1412 0.3311,-0.1412 0.0736,0 0.1628,0.0101 0.2687,0.0307 0.1059,0.0204 0.2062,0.0384 0.3004,0.0532 -0.0824,-0.1177 -0.1648,-0.2371 -0.2472,-0.3577 -0.0824,-0.1206 -0.1233,-0.2278 -0.1233,-0.3219 0,-0.1384 0.0543,-0.2544 0.1633,-0.3485 0.1088,-0.0942 0.2351,-0.1412 0.3792,-0.1412z"
- android:strokeWidth="1.3358" />
+ <group android:translateX="24.828047"
+ android:translateY="24.828047">
+ <path
+ android:pathData="m18.8,30.2129v-11.546c0,-6.4144 5.1315,-11.546 11.546,-11.546 6.4144,0 11.546,5.1315 11.546,11.546v11.546"
+ android:strokeWidth="5.349"
+ android:fillColor="#00000000"
+ android:strokeColor="#013e5b"/>
+ <path
+ android:pathData="M15.4099,21.8429L45.2811,21.8429A2.2639,2.2639 0,0 1,47.545 24.1068L47.545,53.977A2.2639,2.2639 0,0 1,45.2811 56.2409L15.4099,56.2409A2.2639,2.2639 0,0 1,13.146 53.977L13.146,24.1068A2.2639,2.2639 0,0 1,15.4099 21.8429z"
+ android:fillColor="#c74c00"/>
+ <path
+ android:pathData="m44.8267,37.6961 l-13.1408,-13.1393c-0.7569,-0.7566 -1.9838,-0.7566 -2.7408,0l-13.08,13.0785c-0.7567,0.7573 -0.7567,1.9846 0,2.7419l13.1415,13.14c0.7572,0.7567 1.9842,0.7567 2.7414,0l13.0778,-13.0785c0.7572,-0.7572 0.7572,-1.9849 0,-2.7421"
+ android:strokeWidth=".35344"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="m30.3156,23.9881c-0.496,0 -0.992,0.1893 -1.3705,0.5676l-2.7282,2.7288 3.4612,3.4606c0.8044,-0.2715 1.727,-0.0892 2.368,0.5517 0.6237,0.624 0.8361,1.5493 0.5471,2.3828l3.3357,3.3357c0.8076,-0.2777 1.738,-0.098 2.3828,0.5476 0.9008,0.9005 0.9008,2.361 0,3.2615 -1.7823,1.7848 -4.7253,-0.1767 -3.7641,-2.5087l-3.1111,-3.1106c-2.2315,0.5285 -3.8934,-1.2655 -3.149,-3.1674l-0.6863,-0.6863l0,15.9165l5.4913,0l0,-5.8608c-0.0315,-0.7566 1.1201,-0.7566 1.0886,0l0,6.4043c0.0005,0.3013 -0.2438,0.5457 -0.545,0.5455l-6.5804,0c-0.3015,0.0005 -0.546,-0.2441 -0.5456,-0.5455l0,-17.4333c-0.0005,-0.0363 0.0029,-0.0728 0.0097,-0.1085l-1.6444,-1.6444 -9.0106,9.0084c-0.7567,0.7573 -0.7567,1.9848 0,2.7421l13.1415,13.14c0.7572,0.7567 1.9844,0.7567 2.7416,0l13.0778,-13.0785c0.7572,-0.7572 0.7572,-1.9849 0,-2.7421l-13.14,-13.1393c-0.3785,-0.3783 -0.8746,-0.5676 -1.3705,-0.5676zM29.9512,39.1825c0.1001,0 0.1808,0.0381 0.2426,0.1146 0.0648,0.0736 0.1326,0.1975 0.2032,0.371 0.0705,0.1706 0.1089,0.2615 0.1146,0.2733 0.0059,-0.0119 0.0424,-0.1026 0.11,-0.2733 0.0707,-0.1705 0.1401,-0.2946 0.2078,-0.371 0.0677,-0.0765 0.1513,-0.1146 0.2513,-0.1146 0.1412,0 0.2646,0.047 0.3705,0.1412 0.1059,0.0941 0.1592,0.2103 0.1592,0.3485 0,0.0676 -0.0179,0.1368 -0.0532,0.2073 -0.0323,0.0707 -0.0777,0.1444 -0.1366,0.2211 -0.056,0.0734 -0.1164,0.1571 -0.1812,0.2513 0.0648,-0.0089 0.1589,-0.0251 0.2825,-0.0486 0.1265,-0.0236 0.2268,-0.0354 0.3004,-0.0354 0.0969,0 0.1762,0.0224 0.238,0.0666 0.0648,0.0442 0.1118,0.1042 0.1413,0.1807 0.0294,0.0734 0.044,0.1544 0.044,0.2426 0,0.1384 -0.0337,0.2544 -0.1013,0.3485 -0.0677,0.0912 -0.1749,0.1372 -0.3219,0.1372 -0.0472,0 -0.14,-0.0119 -0.2784,-0.0354 -0.1382,-0.0236 -0.2397,-0.0386 -0.3045,-0.0446 0.1531,0.2177 0.2534,0.3652 0.3004,0.4417 0.047,0.0736 0.0706,0.1544 0.0706,0.2426 0,0.1412 -0.0533,0.2556 -0.1592,0.3439 -0.1028,0.0853 -0.2264,0.1279 -0.3705,0.1279 -0.1028,0 -0.1882,-0.0366 -0.2559,-0.11 -0.0646,-0.0767 -0.1309,-0.1955 -0.1986,-0.3572 -0.0646,-0.1647 -0.1028,-0.259 -0.1146,-0.2825 -0.0117,0.0235 -0.0516,0.1178 -0.1192,0.2825 -0.0648,0.1646 -0.1281,0.2836 -0.1899,0.3572 -0.0617,0.0735 -0.1454,0.11 -0.2513,0.11 -0.15,0 -0.2779,-0.0427 -0.3838,-0.1279 -0.1059,-0.0883 -0.1587,-0.2027 -0.1587,-0.3439 0,-0.0618 0.0156,-0.1263 0.0481,-0.194 0.0323,-0.0707 0.0665,-0.1311 0.1018,-0.1812 0.0382,-0.0499 0.1118,-0.1532 0.2206,-0.3091 -0.0736,0.0059 -0.1751,0.021 -0.3045,0.0446 -0.1294,0.0236 -0.2176,0.0354 -0.2646,0.0354 -0.1472,0 -0.2577,-0.046 -0.3311,-0.1372 -0.0707,-0.0941 -0.1059,-0.2101 -0.1059,-0.3485 0,-0.1411 0.0352,-0.2573 0.1059,-0.3485 0.0734,-0.0942 0.184,-0.1412 0.3311,-0.1412 0.0736,0 0.1628,0.0107 0.2687,0.0312 0.1059,0.0206 0.2062,0.038 0.3004,0.0527 -0.0824,-0.1177 -0.1648,-0.2366 -0.2472,-0.3572 -0.0824,-0.1206 -0.1233,-0.2282 -0.1233,-0.3224 0,-0.1382 0.0543,-0.2544 0.1633,-0.3485 0.1088,-0.0942 0.2351,-0.1412 0.3792,-0.1412zM29.9512,43.2235c0.1001,0 0.1808,0.0383 0.2426,0.1146 0.0648,0.0735 0.1326,0.197 0.2032,0.3705 0.0705,0.1707 0.1089,0.262 0.1146,0.2738 0.0059,-0.0119 0.0424,-0.1031 0.11,-0.2738 0.0707,-0.1705 0.1401,-0.2941 0.2078,-0.3705 0.0677,-0.0765 0.1513,-0.1146 0.2513,-0.1146 0.1412,0 0.2646,0.047 0.3705,0.1412 0.1059,0.0941 0.1592,0.2102 0.1592,0.3485 0,0.0675 -0.0179,0.1366 -0.0532,0.2073 -0.0323,0.0705 -0.0777,0.1441 -0.1366,0.2206 -0.056,0.0735 -0.1164,0.1576 -0.1812,0.2518 0.0648,-0.0089 0.1589,-0.0251 0.2825,-0.0486 0.1265,-0.0236 0.2268,-0.0353 0.3004,-0.0353 0.0969,0 0.1762,0.0219 0.238,0.0661 0.0648,0.0442 0.1118,0.1047 0.1413,0.1812 0.0294,0.0736 0.044,0.1542 0.044,0.2426 0,0.1382 -0.0337,0.2544 -0.1013,0.3485 -0.0677,0.0912 -0.1749,0.1366 -0.3219,0.1366 -0.0472,0 -0.14,-0.0119 -0.2784,-0.0354 -0.1382,-0.0236 -0.2397,-0.038 -0.3045,-0.044 0.1531,0.2177 0.2534,0.3647 0.3004,0.4411 0.047,0.0736 0.0706,0.1549 0.0706,0.2431 0,0.1411 -0.0533,0.2558 -0.1592,0.3439 -0.1028,0.0854 -0.2264,0.1279 -0.3705,0.1279 -0.1028,0 -0.1882,-0.0371 -0.2559,-0.1105 -0.0646,-0.0765 -0.1309,-0.1955 -0.1986,-0.3572 -0.0646,-0.1647 -0.1028,-0.259 -0.1146,-0.2825 -0.0117,0.0236 -0.0516,0.1178 -0.1192,0.2825 -0.0648,0.1648 -0.1281,0.2836 -0.1899,0.3572 -0.0617,0.0735 -0.1454,0.1105 -0.2513,0.1105 -0.15,0 -0.2779,-0.0425 -0.3838,-0.1279 -0.1059,-0.0881 -0.1587,-0.2028 -0.1587,-0.3439 0,-0.0617 0.0156,-0.1268 0.0481,-0.1945 0.0323,-0.0705 0.0665,-0.1311 0.1018,-0.1812 0.0382,-0.0499 0.1118,-0.1527 0.2206,-0.3086 -0.0736,0.0059 -0.1751,0.0205 -0.3045,0.044 -0.1294,0.0236 -0.2176,0.0354 -0.2646,0.0354 -0.1472,0 -0.2577,-0.0454 -0.3311,-0.1366 -0.0707,-0.0941 -0.1059,-0.2103 -0.1059,-0.3485 0,-0.1412 0.0352,-0.2573 0.1059,-0.3485 0.0734,-0.0942 0.184,-0.1412 0.3311,-0.1412 0.0736,0 0.1628,0.0101 0.2687,0.0307 0.1059,0.0204 0.2062,0.0384 0.3004,0.0532 -0.0824,-0.1177 -0.1648,-0.2371 -0.2472,-0.3577 -0.0824,-0.1206 -0.1233,-0.2278 -0.1233,-0.3219 0,-0.1384 0.0543,-0.2544 0.1633,-0.3485 0.1088,-0.0942 0.2351,-0.1412 0.3792,-0.1412z"
+ android:strokeWidth="1.3358"
+ android:fillColor="#f47a68"/>
</group>
</vector>
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e3bf13b9..abe12820 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,13 +28,8 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <activity
- android:name=".git.GitActivity"
- android:parentActivityName=".PasswordStore">
- <meta-data
- android:name="android.support.PARENT_ACTIVITY"
- android:value="com.zeapo.pwdstore.PasswordStore" />
- </activity>
+
+ <activity android:name=".git.GitActivity" />
<activity
android:name=".UserPreference"
@@ -67,7 +62,7 @@
android:name=".autofill.AutofillActivity"
android:documentLaunchMode="intoExisting"
android:excludeFromRecents="true"
- android:parentActivityName=".PasswordStore"
+ android:theme="@style/AppTheme"
tools:ignore="UnusedAttribute">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordEntry.kt b/app/src/main/java/com/zeapo/pwdstore/PasswordEntry.kt
index 52ab93d0..6b751b5c 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordEntry.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordEntry.kt
@@ -70,7 +70,7 @@ class PasswordEntry(private val content: String) {
val extraLines = extraContent!!.split("\n".toRegex())
for (line in extraLines) {
for (field in USERNAME_FIELDS) {
- if (line.toLowerCase().startsWith("$field:")) {
+ if (line.toLowerCase().startsWith("$field:", ignoreCase = true)) {
return line.split(": *".toRegex(), 2).toTypedArray()[1]
}
}
@@ -143,7 +143,7 @@ class PasswordEntry(private val content: String) {
val extraContent = if (passContent.size > 1) passContent[1] else ""
// if there is a HOTP URI, we must return the extra content with the counter incremented
return if (hasHotp()) {
- extraContent.replaceFirst("counter=[0-9]+".toRegex(), "counter=" + java.lang.Long.toString(hotpCounter!!))
+ extraContent.replaceFirst("counter=[0-9]+".toRegex(), "counter=" + (hotpCounter!!).toString())
} else extraContent
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.java b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.java
index f2c54ab8..33424353 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.java
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordFragment.java
@@ -211,8 +211,8 @@ public class PasswordFragment extends Fragment {
}
public void dismissActionMode() {
- if (recyclerAdapter != null && recyclerAdapter.mActionMode != null) {
- recyclerAdapter.mActionMode.finish();
+ if (recyclerAdapter != null && recyclerAdapter.getActionMode() != null) {
+ recyclerAdapter.getActionMode().finish();
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java b/app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java
index 0f2d22cf..6ec0529e 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordGeneratorDialogFragment.java
@@ -12,12 +12,14 @@ import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
-import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.widget.AppCompatEditText;
+import androidx.appcompat.widget.AppCompatTextView;
import androidx.fragment.app.DialogFragment;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.zeapo.pwdstore.pwgen.PasswordGenerator;
import org.jetbrains.annotations.NotNull;
@@ -37,7 +39,7 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
@SuppressLint("SetTextI18n")
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext());
final Activity callingActivity = requireActivity();
LayoutInflater inflater = callingActivity.getLayoutInflater();
@SuppressLint("InflateParams") final View view = inflater.inflate(R.layout.fragment_pwgen, null);
@@ -66,10 +68,10 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
checkBox = view.findViewById(R.id.pronounceable);
checkBox.setChecked(!prefs.getBoolean("s", true));
- TextView textView = view.findViewById(R.id.lengthNumber);
+ AppCompatEditText textView = view.findViewById(R.id.lengthNumber);
textView.setText(Integer.toString(prefs.getInt("length", 20)));
- TextView passwordText = view.findViewById(R.id.passwordText);
+ AppCompatTextView passwordText = view.findViewById(R.id.passwordText);
passwordText.setTypeface(monoTypeface);
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), (dialog, which) -> {
diff --git a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
index 95eb5a9b..469dac32 100644
--- a/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
+++ b/app/src/main/java/com/zeapo/pwdstore/PasswordStore.java
@@ -18,11 +18,10 @@ import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.AppCompatTextView;
import androidx.appcompat.widget.SearchView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
@@ -30,6 +29,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceManager;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import com.zeapo.pwdstore.crypto.PgpActivity;
import com.zeapo.pwdstore.git.GitActivity;
@@ -143,7 +143,7 @@ public class PasswordStore extends AppCompatActivity {
REQUEST_EXTERNAL_STORAGE));
snack.show();
View view = snack.getView();
- TextView tv = view.findViewById(com.google.android.material.R.id.snackbar_text);
+ AppCompatTextView tv = view.findViewById(com.google.android.material.R.id.snackbar_text);
tv.setTextColor(Color.WHITE);
tv.setMaxLines(10);
} else {
@@ -217,7 +217,7 @@ public class PasswordStore extends AppCompatActivity {
int id = item.getItemId();
Intent intent;
- AlertDialog.Builder initBefore = new AlertDialog.Builder(this)
+ final MaterialAlertDialogBuilder initBefore = new MaterialAlertDialogBuilder(this)
.setMessage(this.getResources().getString(R.string.creation_dialog_text))
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), null);
@@ -342,7 +342,7 @@ public class PasswordStore extends AppCompatActivity {
final Set<String> keyIds = settings.getStringSet("openpgp_key_ids_set", new HashSet<>());
if (keyIds.isEmpty())
- new AlertDialog.Builder(this)
+ new MaterialAlertDialogBuilder(this)
.setMessage(this.getResources().getString(R.string.key_dialog_text))
.setPositiveButton(this.getResources().getString(R.string.dialog_positive), (dialogInterface, i) -> {
Intent intent = new Intent(activity, UserPreference.class);
@@ -498,7 +498,7 @@ public class PasswordStore extends AppCompatActivity {
public void createPassword() {
if (!PasswordRepository.isInitialized()) {
- new AlertDialog.Builder(this)
+ new MaterialAlertDialogBuilder(this)
.setMessage(this.getResources().getString(R.string.creation_dialog_text))
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
}).show();
@@ -506,7 +506,7 @@ public class PasswordStore extends AppCompatActivity {
}
if (settings.getStringSet("openpgp_key_ids_set", new HashSet<>()).isEmpty()) {
- new AlertDialog.Builder(this)
+ new MaterialAlertDialogBuilder(this)
.setTitle(this.getResources().getString(R.string.no_key_selected_dialog_title))
.setMessage(this.getResources().getString(R.string.no_key_selected_dialog_text))
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
@@ -534,7 +534,7 @@ public class PasswordStore extends AppCompatActivity {
}
final int position = (int) it.next();
final PasswordItem item = adapter.getValues().get(position);
- new AlertDialog.Builder(this)
+ new MaterialAlertDialogBuilder(this)
.setMessage(getResources().getString(R.string.delete_dialog_text, item.getLongName()))
.setPositiveButton(getResources().getString(R.string.dialog_yes), (dialogInterface, i) -> {
item.getFile().delete();
@@ -642,6 +642,7 @@ public class PasswordStore extends AppCompatActivity {
refreshListAdapter();
break;
case GitActivity.REQUEST_INIT:
+ case NEW_REPO_BUTTON:
initializeRepositoryInfo();
break;
case GitActivity.REQUEST_SYNC:
@@ -651,9 +652,6 @@ public class PasswordStore extends AppCompatActivity {
case HOME:
checkLocalRepository();
break;
- case NEW_REPO_BUTTON:
- initializeRepositoryInfo();
- break;
case CLONE_REPO_BUTTON:
// duplicate code
if (settings.getBoolean("git_external", false) && settings.getString("git_external_repo", null) != null) {
@@ -708,7 +706,7 @@ public class PasswordStore extends AppCompatActivity {
if (destinationFile.exists()) {
Log.e(TAG, "Trying to move a file that already exists.");
// TODO: Add option to cancel overwrite. Will be easier once this is an async task.
- new AlertDialog.Builder(this)
+ new MaterialAlertDialogBuilder(this)
.setTitle(getResources().getString(R.string.password_exists_title))
.setMessage(getResources().getString(R.string.password_exists_message,
destinationLongName, sourceLongName))
@@ -739,7 +737,7 @@ public class PasswordStore extends AppCompatActivity {
private void initRepository(final int operation) {
PasswordRepository.closeRepository();
- new AlertDialog.Builder(this)
+ new MaterialAlertDialogBuilder(this)
.setTitle(this.getResources().getString(R.string.location_dialog_title))
.setMessage(this.getResources().getString(R.string.location_dialog_text))
.setPositiveButton(this.getResources().getString(R.string.location_hidden), (dialog, whichButton) -> {
@@ -768,7 +766,7 @@ public class PasswordStore extends AppCompatActivity {
intent.putExtra("operation", "git_external");
startActivityForResult(intent, operation);
} else {
- new AlertDialog.Builder(activity)
+ new MaterialAlertDialogBuilder(activity)
.setTitle(getResources().getString(R.string.directory_selected_title))
.setMessage(getResources().getString(R.string.directory_selected_message, externalRepo))
.setPositiveButton(getResources().getString(R.string.use), (dialog1, which) -> {
diff --git a/app/src/main/java/com/zeapo/pwdstore/SelectFolderActivity.kt b/app/src/main/java/com/zeapo/pwdstore/SelectFolderActivity.kt
index 2e894987..2eda0f73 100644
--- a/app/src/main/java/com/zeapo/pwdstore/SelectFolderActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/SelectFolderActivity.kt
@@ -24,7 +24,7 @@ class SelectFolderActivity : AppCompatActivity() {
passwordList = SelectFolderFragment()
val args = Bundle()
- args.putString("Path", PasswordRepository.getRepositoryDirectory(applicationContext).absolutePath)
+ args.putString("Path", PasswordRepository.getRepositoryDirectory(applicationContext)?.absolutePath)
passwordList.arguments = args
diff --git a/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java b/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java
index 144e8ab5..b17ed746 100644
--- a/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java
+++ b/app/src/main/java/com/zeapo/pwdstore/SelectFolderFragment.java
@@ -50,8 +50,12 @@ public class SelectFolderFragment extends Fragment {
String path = getArguments().getString("Path");
pathStack = new Stack<>();
- recyclerAdapter = new FolderRecyclerAdapter((SelectFolderActivity) requireActivity(), mListener,
- PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(requireActivity()), getSortOrder()));
+ recyclerAdapter = new FolderRecyclerAdapter(mListener,
+ PasswordRepository.getPasswords(
+ new File(path),
+ PasswordRepository.getRepositoryDirectory(requireActivity()), getSortOrder()
+ )
+ );
}
@Override
diff --git a/app/src/main/java/com/zeapo/pwdstore/SshKeyGen.java b/app/src/main/java/com/zeapo/pwdstore/SshKeyGen.java
index 593a9bbb..77fbcf27 100644
--- a/app/src/main/java/com/zeapo/pwdstore/SshKeyGen.java
+++ b/app/src/main/java/com/zeapo/pwdstore/SshKeyGen.java
@@ -2,8 +2,6 @@ package com.zeapo.pwdstore;
import android.annotation.SuppressLint;
import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
import android.app.ProgressDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
@@ -22,13 +20,19 @@ import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Spinner;
-import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.AppCompatTextView;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
import androidx.preference.PreferenceManager;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.android.material.textfield.TextInputEditText;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.KeyPair;
@@ -37,6 +41,7 @@ import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.ref.WeakReference;
+import java.nio.charset.StandardCharsets;
public class SshKeyGen extends AppCompatActivity {
@@ -50,7 +55,7 @@ public class SshKeyGen extends AppCompatActivity {
setTitle("Generate SSH Key");
if (savedInstanceState == null) {
- getFragmentManager().beginTransaction()
+ getSupportFragmentManager().beginTransaction()
.replace(android.R.id.content, new SshKeyGenFragment()).commit();
}
}
@@ -77,20 +82,20 @@ public class SshKeyGen extends AppCompatActivity {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.fragment_ssh_keygen, container, false);
- Typeface monoTypeface = Typeface.createFromAsset(getActivity().getAssets(), "fonts/sourcecodepro.ttf");
+ Typeface monoTypeface = Typeface.createFromAsset(requireContext().getAssets(), "fonts/sourcecodepro.ttf");
Spinner spinner = v.findViewById(R.id.length);
Integer[] lengths = new Integer[]{2048, 4096};
- ArrayAdapter<Integer> adapter = new ArrayAdapter<>(getActivity(),
+ ArrayAdapter<Integer> adapter = new ArrayAdapter<>(requireContext(),
android.R.layout.simple_spinner_dropdown_item, lengths);
spinner.setAdapter(adapter);
- ((EditText) v.findViewById(R.id.passphrase)).setTypeface(monoTypeface);
+ ((TextInputEditText) v.findViewById(R.id.passphrase)).setTypeface(monoTypeface);
- CheckBox checkbox = v.findViewById(R.id.show_passphrase);
+ final CheckBox checkbox = v.findViewById(R.id.show_passphrase);
checkbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
- EditText editText = v.findViewById(R.id.passphrase);
- int selection = editText.getSelectionEnd();
+ final TextInputEditText editText = v.findViewById(R.id.passphrase);
+ final int selection = editText.getSelectionEnd();
if (isChecked) {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
} else {
@@ -108,25 +113,27 @@ public class SshKeyGen extends AppCompatActivity {
public ShowSshKeyFragment() {
}
+ @NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- LayoutInflater inflater = getActivity().getLayoutInflater();
+ final FragmentActivity activity = requireActivity();
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext());
+ LayoutInflater inflater = activity.getLayoutInflater();
@SuppressLint("InflateParams") final View v = inflater.inflate(R.layout.fragment_show_ssh_key, null);
builder.setView(v);
- TextView textView = v.findViewById(R.id.public_key);
- File file = new File(getActivity().getFilesDir() + "/.ssh_key.pub");
+ AppCompatTextView textView = v.findViewById(R.id.public_key);
+ File file = new File(activity.getFilesDir() + "/.ssh_key.pub");
try {
- textView.setText(FileUtils.readFileToString(file));
+ textView.setText(FileUtils.readFileToString(file, StandardCharsets.UTF_8));
} catch (Exception e) {
System.out.println("Exception caught :(");
e.printStackTrace();
}
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), (dialog, which) -> {
- if (getActivity() instanceof SshKeyGen)
- getActivity().finish();
+ if (activity instanceof SshKeyGen)
+ activity.finish();
});
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), (dialog, which) -> {
@@ -139,8 +146,8 @@ public class SshKeyGen extends AppCompatActivity {
ad.setOnShowListener(dialog -> {
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
b.setOnClickListener(v1 -> {
- TextView textView1 = getDialog().findViewById(R.id.public_key);
- ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
+ AppCompatTextView textView1 = getDialog().findViewById(R.id.public_key);
+ ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("public key", textView1.getText().toString());
clipboard.setPrimaryClip(clip);
});
@@ -198,20 +205,19 @@ public class SshKeyGen extends AppCompatActivity {
if (e == null) {
Toast.makeText(weakReference.get(), "SSH-key generated", Toast.LENGTH_LONG).show();
DialogFragment df = new ShowSshKeyFragment();
- df.show(weakReference.get().getFragmentManager(), "public_key");
+ df.show(weakReference.get().getSupportFragmentManager(), "public_key");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(weakReference.get());
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("use_generated_key", true);
editor.apply();
} else {
- new AlertDialog.Builder(weakReference.get())
+ new MaterialAlertDialogBuilder(weakReference.get())
.setTitle("Error while trying to generate the ssh-key")
.setMessage(weakReference.get().getResources().getString(R.string.ssh_key_error_dialog_text) + e.getMessage())
.setPositiveButton(weakReference.get().getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
// pass
}).show();
}
-
}
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
index 349b5b1b..3384356f 100644
--- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt
@@ -8,19 +8,19 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
-import android.preference.CheckBoxPreference
-import android.preference.Preference
-import android.preference.PreferenceFragment
-import android.preference.PreferenceManager
import android.provider.DocumentsContract
import android.provider.Settings
import android.util.Log
import android.view.MenuItem
import android.view.accessibility.AccessibilityManager
import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
+import androidx.preference.CheckBoxPreference
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity
import com.zeapo.pwdstore.crypto.PgpActivity
import com.zeapo.pwdstore.git.GitActivity
@@ -35,74 +35,135 @@ import java.util.Calendar
import java.util.HashSet
import java.util.TimeZone
+typealias ClickListener = Preference.OnPreferenceClickListener
+typealias ChangeListener = Preference.OnPreferenceChangeListener
+
class UserPreference : AppCompatActivity() {
private lateinit var prefsFragment: PrefsFragment
- class PrefsFragment : PreferenceFragment() {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val callingActivity = activity as UserPreference
+ class PrefsFragment : PreferenceFragmentCompat() {
+ private var autofillDependencies = listOf<Preference?>()
+ private var autoFillEnablePreference: CheckBoxPreference? = null
+ private lateinit var callingActivity: UserPreference
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ callingActivity = requireActivity() as UserPreference
+ val context = requireContext()
val sharedPreferences = preferenceManager.sharedPreferences
addPreferencesFromResource(R.xml.preference)
- findPreference("app_version").summary = "Version: ${BuildConfig.VERSION_NAME}"
+ // Git preferences
+ val gitServerPreference = findPreference<Preference>("git_server_info")
+ val gitConfigPreference = findPreference<Preference>("git_config")
+ val sshKeyPreference = findPreference<Preference>("ssh_key")
+ val sshKeygenPreference = findPreference<Preference>("ssh_keygen")
+ val sshClearPassphrasePreference = findPreference<Preference>("ssh_key_clear_passphrase")
+ val clearHotpIncrementPreference = findPreference<Preference>("hotp_remember_clear_choice")
+ val viewSshKeyPreference = findPreference<Preference>("ssh_see_key")
+ val deleteRepoPreference = findPreference<Preference>("git_delete_repo")
+ val externalGitRepositoryPreference = findPreference<Preference>("git_external")
+ val selectExternalGitRepositoryPreference = findPreference<Preference>("pref_select_external")
+
+
+ // Crypto preferences
+ val keyPreference = findPreference<Preference>("openpgp_key_id_pref")
+
+ // General preferences
+ val clearAfterCopyPreference = findPreference<CheckBoxPreference>("clear_after_copy")
+ val clearClipboard20xPreference = findPreference<CheckBoxPreference>("clear_clipboard_20x")
+
+ // Autofill preferences
+ autoFillEnablePreference = findPreference<CheckBoxPreference>("autofill_enable")
+ val autoFillAppsPreference = findPreference<Preference>("autofill_apps")
+ val autoFillDefaultPreference = findPreference<CheckBoxPreference>("autofill_default")
+ val autoFillAlwaysShowDialogPreference = findPreference<CheckBoxPreference>("autofill_always")
+ autofillDependencies = listOf(
+ autoFillAppsPreference,
+ autoFillDefaultPreference,
+ autoFillAlwaysShowDialogPreference
+ )
+
+ // Misc preferences
+ val appVersionPreference = findPreference<Preference>("app_version")
+
+ selectExternalGitRepositoryPreference?.summary = sharedPreferences.getString("git_external_repo", getString(R.string.no_repo_selected))
+ viewSshKeyPreference?.isVisible = sharedPreferences.getBoolean("use_generated_key", false)
+ deleteRepoPreference?.isVisible = !sharedPreferences.getBoolean("git_external", false)
+ sshClearPassphrasePreference?.isVisible = sharedPreferences.getString("ssh_key_passphrase", null)?.isNotEmpty()
+ ?: false
+ clearHotpIncrementPreference?.isVisible = sharedPreferences.getBoolean("hotp_remember_check", false)
+ clearAfterCopyPreference?.isVisible = sharedPreferences.getString("general_show_time", "45")?.toInt() != 0
+ clearClipboard20xPreference?.isVisible = sharedPreferences.getString("general_show_time", "45")?.toInt() != 0
+ val selectedKeys = (sharedPreferences.getStringSet("openpgp_key_ids_set", null)
+ ?: HashSet<String>()).toTypedArray()
+ keyPreference?.summary = if (selectedKeys.isEmpty()) {
+ this.resources.getString(R.string.pref_no_key_selected)
+ } else {
+ selectedKeys.joinToString(separator = ";") { s ->
+ OpenPgpUtils.convertKeyIdToHex(java.lang.Long.valueOf(s))
+ }
+ }
+
+ // see if the autofill service is enabled and check the preference accordingly
+ autoFillEnablePreference?.isChecked = callingActivity.isServiceEnabled
+ autofillDependencies.forEach { it?.isVisible = callingActivity.isServiceEnabled }
+
+ appVersionPreference?.summary = "Version: ${BuildConfig.VERSION_NAME}"
- findPreference("openpgp_key_id_pref").onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ keyPreference?.onPreferenceClickListener = ClickListener {
val intent = Intent(callingActivity, PgpActivity::class.java)
intent.putExtra("OPERATION", "GET_KEY_ID")
startActivityForResult(intent, IMPORT_PGP_KEY)
true
}
- findPreference("ssh_key").onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ sshKeyPreference?.onPreferenceClickListener = ClickListener {
callingActivity.getSshKey()
true
}
- findPreference("ssh_keygen").onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ sshKeygenPreference?.onPreferenceClickListener = ClickListener {
callingActivity.makeSshKey(true)
true
}
- findPreference("ssh_see_key").onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ viewSshKeyPreference?.onPreferenceClickListener = ClickListener {
val df = SshKeyGen.ShowSshKeyFragment()
- df.show(fragmentManager, "public_key")
+ df.show(requireFragmentManager(), "public_key")
true
}
- findPreference("ssh_key_clear_passphrase").onPreferenceClickListener =
- Preference.OnPreferenceClickListener {
- sharedPreferences.edit().putString("ssh_key_passphrase", null).apply()
- it.isEnabled = false
- true
- }
+ sshClearPassphrasePreference?.onPreferenceClickListener = ClickListener {
+ sharedPreferences.edit().putString("ssh_key_passphrase", null).apply()
+ it.isVisible = false
+ true
+ }
- findPreference("hotp_remember_clear_choice").onPreferenceClickListener =
- Preference.OnPreferenceClickListener {
- sharedPreferences.edit().putBoolean("hotp_remember_check", false).apply()
- it.isEnabled = false
- true
- }
+ clearHotpIncrementPreference?.onPreferenceClickListener = ClickListener {
+ sharedPreferences.edit().putBoolean("hotp_remember_check", false).apply()
+ it.isVisible = false
+ true
+ }
- findPreference("git_server_info").onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ gitServerPreference?.onPreferenceClickListener = ClickListener {
val intent = Intent(callingActivity, GitActivity::class.java)
intent.putExtra("Operation", GitActivity.EDIT_SERVER)
startActivityForResult(intent, EDIT_GIT_INFO)
true
}
- findPreference("git_config").onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ gitConfigPreference?.onPreferenceClickListener = ClickListener {
val intent = Intent(callingActivity, GitActivity::class.java)
intent.putExtra("Operation", GitActivity.EDIT_GIT_CONFIG)
startActivityForResult(intent, EDIT_GIT_CONFIG)
true
}
- findPreference("git_delete_repo").onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ deleteRepoPreference?.onPreferenceClickListener = ClickListener {
val repoDir = PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext)
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(R.string.pref_dialog_delete_title)
.setMessage(resources.getString(R.string.dialog_delete_msg, repoDir))
.setCancelable(false)
@@ -110,8 +171,8 @@ class UserPreference : AppCompatActivity() {
try {
FileUtils.cleanDirectory(PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext))
PasswordRepository.closeRepository()
- } catch (e: Exception) {
- //TODO Handle the different cases of exceptions
+ } catch (ignored: Exception) {
+ // TODO Handle the different cases of exceptions
}
sharedPreferences.edit().putBoolean("repository_initialized", false).apply()
@@ -124,90 +185,77 @@ class UserPreference : AppCompatActivity() {
true
}
- val externalRepo = findPreference("pref_select_external")
- externalRepo.summary =
- sharedPreferences.getString("git_external_repo", callingActivity.getString(R.string.no_repo_selected))
- externalRepo.onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ selectExternalGitRepositoryPreference?.summary =
+ sharedPreferences.getString("git_external_repo", context.getString(R.string.no_repo_selected))
+ selectExternalGitRepositoryPreference?.onPreferenceClickListener = ClickListener {
callingActivity.selectExternalGitRepository()
true
}
val resetRepo = Preference.OnPreferenceChangeListener { _, o ->
- findPreference("git_delete_repo").isEnabled = !(o as Boolean)
+ deleteRepoPreference?.isVisible = !(o as Boolean)
PasswordRepository.closeRepository()
sharedPreferences.edit().putBoolean("repo_changed", true).apply()
true
}
- findPreference("pref_select_external").onPreferenceChangeListener = resetRepo
- findPreference("git_external").onPreferenceChangeListener = resetRepo
+ selectExternalGitRepositoryPreference?.onPreferenceChangeListener = resetRepo
+ externalGitRepositoryPreference?.onPreferenceChangeListener = resetRepo
- findPreference("autofill_apps").onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ autoFillAppsPreference?.onPreferenceClickListener = ClickListener {
val intent = Intent(callingActivity, AutofillPreferenceActivity::class.java)
startActivity(intent)
true
}
- findPreference("autofill_enable").onPreferenceClickListener = Preference.OnPreferenceClickListener {
- AlertDialog.Builder(callingActivity).setTitle(R.string.pref_autofill_enable_title)
- .setView(R.layout.autofill_instructions).setPositiveButton(R.string.dialog_ok) { _, _ ->
- val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
- startActivity(intent)
- }.setNegativeButton(R.string.dialog_cancel, null).setOnDismissListener {
- (findPreference("autofill_enable") as CheckBoxPreference).isChecked =
- (activity as UserPreference).isServiceEnabled
- }.show()
+ autoFillEnablePreference?.onPreferenceClickListener = ClickListener {
+ var isEnabled = callingActivity.isServiceEnabled
+ if (isEnabled) {
+ startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
+ } else {
+ MaterialAlertDialogBuilder(callingActivity)
+ .setTitle(R.string.pref_autofill_enable_title)
+ .setView(R.layout.autofill_instructions)
+ .setPositiveButton(R.string.dialog_ok) { _, _ ->
+ startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
+ }
+ .setNegativeButton(R.string.dialog_cancel, null)
+ .setOnDismissListener {
+ isEnabled = callingActivity.isServiceEnabled
+ autoFillEnablePreference?.isChecked = isEnabled
+ autofillDependencies.forEach { it?.isVisible = isEnabled }
+ }
+ .show()
+ }
true
}
- findPreference("export_passwords").apply {
- isEnabled = sharedPreferences.getBoolean("repository_initialized", false)
+ findPreference<Preference>("export_passwords")?.apply {
+ isVisible = sharedPreferences.getBoolean("repository_initialized", false)
onPreferenceClickListener = Preference.OnPreferenceClickListener {
callingActivity.exportPasswords()
true
}
}
- findPreference("general_show_time").onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? ->
- try {
- findPreference("clear_after_copy").isEnabled = newValue.toString().toInt() != 0
- findPreference("clear_clipboard_20x").isEnabled = newValue.toString().toInt() != 0
- true
- } catch (e: NumberFormatException) {
- false
- }
- }
+ findPreference<Preference>("general_show_time")?.onPreferenceChangeListener =
+ ChangeListener { _, newValue: Any? ->
+ try {
+ val isEnabled = newValue.toString().toInt() != 0
+ clearAfterCopyPreference?.isVisible = isEnabled
+ clearClipboard20xPreference?.isVisible = isEnabled
+ true
+ } catch (e: NumberFormatException) {
+ false
+ }
+ }
}
- override fun onStart() {
- super.onStart()
- val sharedPreferences = preferenceManager.sharedPreferences
- findPreference("pref_select_external").summary =
- preferenceManager.sharedPreferences.getString("git_external_repo", getString(R.string.no_repo_selected))
- findPreference("ssh_see_key").isEnabled = sharedPreferences.getBoolean("use_generated_key", false)
- findPreference("git_delete_repo").isEnabled = !sharedPreferences.getBoolean("git_external", false)
- findPreference("ssh_key_clear_passphrase").isEnabled = sharedPreferences.getString(
- "ssh_key_passphrase",
- null
- )?.isNotEmpty() ?: false
- findPreference("hotp_remember_clear_choice").isEnabled =
- sharedPreferences.getBoolean("hotp_remember_check", false)
- findPreference("clear_after_copy").isEnabled = sharedPreferences.getString("general_show_time", "45")?.toInt() != 0
- findPreference("clear_clipboard_20x").isEnabled = sharedPreferences.getString("general_show_time", "45")?.toInt() != 0
- val keyPref = findPreference("openpgp_key_id_pref")
- val selectedKeys = (sharedPreferences.getStringSet("openpgp_key_ids_set", null)
- ?: HashSet<String>()).toTypedArray()
- if (selectedKeys.isEmpty()) {
- keyPref.summary = this.resources.getString(R.string.pref_no_key_selected)
- } else {
- keyPref.summary = selectedKeys.joinToString(separator = ";") { s ->
- OpenPgpUtils.convertKeyIdToHex(java.lang.Long.valueOf(s))
- }
- }
-
- // see if the autofill service is enabled and check the preference accordingly
- (findPreference("autofill_enable") as CheckBoxPreference).isChecked =
- (activity as UserPreference).isServiceEnabled
+ override fun onResume() {
+ super.onResume()
+ val isEnabled = callingActivity.isServiceEnabled
+ autoFillEnablePreference?.isChecked = isEnabled
+ autofillDependencies.forEach { it?.isVisible = isEnabled }
}
}
@@ -220,19 +268,24 @@ class UserPreference : AppCompatActivity() {
}
prefsFragment = PrefsFragment()
- fragmentManager.beginTransaction().replace(android.R.id.content, prefsFragment).commit()
+ supportFragmentManager
+ .beginTransaction()
+ .replace(android.R.id.content, prefsFragment)
+ .commit()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
fun selectExternalGitRepository() {
- AlertDialog.Builder(this)
+ MaterialAlertDialogBuilder(this)
.setTitle(this.resources.getString(R.string.external_repository_dialog_title))
.setMessage(this.resources.getString(R.string.external_repository_dialog_text))
.setPositiveButton(R.string.dialog_ok) { _, _ ->
val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(Intent.createChooser(i, "Choose Directory"), SELECT_GIT_DIRECTORY)
- }.setNegativeButton(R.string.dialog_cancel, null).show()
+ }
+ .setNegativeButton(R.string.dialog_cancel, null)
+ .show()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -350,7 +403,7 @@ class UserPreference : AppCompatActivity() {
finish()
} catch (e: IOException) {
- AlertDialog.Builder(this)
+ MaterialAlertDialogBuilder(this)
.setTitle(this.resources.getString(R.string.ssh_key_error_dialog_title))
.setMessage(this.resources.getString(R.string.ssh_key_error_dialog_text) + e.message)
.setPositiveButton(this.resources.getString(R.string.dialog_ok), null)
@@ -372,7 +425,7 @@ class UserPreference : AppCompatActivity() {
Log.d(TAG, "Selected repository path is $repoPath")
if (Environment.getExternalStorageDirectory().path == repoPath) {
- AlertDialog.Builder(this)
+ MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.sdcard_root_warning_title))
.setMessage(getString(R.string.sdcard_root_warning_message))
.setPositiveButton("Remove everything") { _, _ ->
@@ -380,7 +433,9 @@ class UserPreference : AppCompatActivity() {
.edit()
.putString("git_external_repo", uri?.path)
.apply()
- }.setNegativeButton(R.string.dialog_cancel, null).show()
+ }
+ .setNegativeButton(R.string.dialog_cancel, null)
+ .show()
}
PreferenceManager.getDefaultSharedPreferences(applicationContext)
@@ -413,7 +468,7 @@ class UserPreference : AppCompatActivity() {
*/
private fun exportPasswords(targetDirectory: DocumentFile) {
- val repositoryDirectory = PasswordRepository.getRepositoryDirectory(applicationContext)
+ val repositoryDirectory = requireNotNull(PasswordRepository.getRepositoryDirectory(applicationContext))
val sourcePassDir = DocumentFile.fromFile(repositoryDirectory)
Log.d(TAG, "Copying ${repositoryDirectory.path} to $targetDirectory")
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.kt
index d01ccdc8..04b8d7ef 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.kt
@@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
+import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
@@ -15,21 +16,23 @@ import android.widget.EditText
import android.widget.ListView
import android.widget.RadioButton
import android.widget.RadioGroup
-import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
-import androidx.core.content.ContextCompat
+import androidx.appcompat.widget.AppCompatTextView
import androidx.fragment.app.DialogFragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.PasswordStore
import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.utils.resolveAttribute
import com.zeapo.pwdstore.utils.splitLines
+
class AutofillFragment : DialogFragment() {
private var adapter: ArrayAdapter<String>? = null
private var isWeb: Boolean = false
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val builder = AlertDialog.Builder(requireContext())
+ val builder = MaterialAlertDialogBuilder(requireContext())
// this fragment is only created from the settings page (AutofillPreferenceActivity)
// need to interact with the recyclerAdapter which is a member of activity
val callingActivity = requireActivity() as AutofillPreferenceActivity
@@ -51,9 +54,13 @@ class AutofillFragment : DialogFragment() {
builder.setTitle(appName)
view.findViewById<View>(R.id.webURL).visibility = View.GONE
} else {
- iconPackageName = "com.android.browser"
+ val browserIntent = Intent("android.intent.action.VIEW", Uri.parse("http://"))
+ val resolveInfo = requireContext().packageManager
+ .resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
+ iconPackageName = resolveInfo?.activityInfo?.packageName
builder.setTitle("Website")
- (view.findViewById<View>(R.id.webURL) as EditText).setText(packageName)
+ (view.findViewById<View>(R.id.webURL) as EditText).setText(packageName
+ ?: "com.android.browser")
}
try {
builder.setIcon(callingActivity.packageManager.getApplicationIcon(iconPackageName))
@@ -65,15 +72,17 @@ class AutofillFragment : DialogFragment() {
adapter = object : ArrayAdapter<String>(requireContext(), android.R.layout.simple_list_item_1, android.R.id.text1) {
// set text color to black because default is white...
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
- val textView = super.getView(position, convertView, parent) as TextView
- textView.setTextColor(ContextCompat.getColor(context, R.color.grey_black_1000))
+ val textView = super.getView(position, convertView, parent) as AppCompatTextView
+ textView.setTextColor(requireContext().resolveAttribute(android.R.attr.textColor))
return textView
}
}
(view.findViewById<View>(R.id.matched) as ListView).adapter = adapter
// delete items by clicking them
(view.findViewById<View>(R.id.matched) as ListView).onItemClickListener =
- AdapterView.OnItemClickListener { _, _, position, _ -> adapter!!.remove(adapter!!.getItem(position)) }
+ AdapterView.OnItemClickListener { _, _, position, _ ->
+ adapter!!.remove(adapter!!.getItem(position))
+ }
// set the existing preference, if any
val prefs: SharedPreferences = if (!isWeb) {
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.kt
index f4c9357b..7cd7fcb7 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.kt
@@ -7,8 +7,8 @@ import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.TextView
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.appcompat.widget.AppCompatTextView
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback
@@ -68,7 +68,6 @@ internal class AutofillRecyclerAdapter(
holder.name.text = app.appName
holder.secondary.visibility = View.VISIBLE
- holder.view.setBackgroundResource(R.color.grey_white_1000)
val prefs: SharedPreferences
prefs = if (app.appName != app.packageName) {
@@ -151,9 +150,9 @@ internal class AutofillRecyclerAdapter(
}
internal inner class ViewHolder(var view: View) : RecyclerView.ViewHolder(view), View.OnClickListener {
- var name: TextView = view.findViewById(R.id.app_name)
- var icon: ImageView = view.findViewById(R.id.app_icon)
- var secondary: TextView = view.findViewById(R.id.secondary_text)
+ var name: AppCompatTextView = view.findViewById(R.id.app_name)
+ var icon: AppCompatImageView = view.findViewById(R.id.app_icon)
+ var secondary: AppCompatTextView = view.findViewById(R.id.secondary_text)
var packageName: String? = null
var appName: String? = null
var isWeb: Boolean = false
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.kt
index e4d1d04b..d9f42b00 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.kt
@@ -12,7 +12,6 @@ import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
-import android.preference.PreferenceManager
import android.provider.Settings
import android.util.Log
import android.view.WindowManager
@@ -21,6 +20,8 @@ import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityWindowInfo
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
+import androidx.preference.PreferenceManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.PasswordEntry
import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.utils.PasswordRepository
@@ -30,7 +31,6 @@ import org.openintents.openpgp.IOpenPgpService2
import org.openintents.openpgp.OpenPgpError
import org.openintents.openpgp.util.OpenPgpApi
import org.openintents.openpgp.util.OpenPgpServiceConnection
-
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
@@ -291,7 +291,7 @@ class AutofillService : AccessibilityService() {
when (preference) {
"/first" -> {
- if (!PasswordRepository.isInitialized()) {
+ if (!PasswordRepository.isInitialized) {
PasswordRepository.initialize(this)
}
items = searchPasswords(PasswordRepository.getRepositoryDirectory(this), webViewTitle)
@@ -313,7 +313,7 @@ class AutofillService : AccessibilityService() {
when (preference) {
"/first" -> {
- if (!PasswordRepository.isInitialized()) {
+ if (!PasswordRepository.isInitialized) {
PasswordRepository.initialize(this)
}
items = searchPasswords(PasswordRepository.getRepositoryDirectory(this), appName)
@@ -326,7 +326,7 @@ class AutofillService : AccessibilityService() {
// Put the newline separated list of passwords from the SharedPreferences
// file into the items list.
private fun getPreferredPasswords(preference: String) {
- if (!PasswordRepository.isInitialized()) {
+ if (!PasswordRepository.isInitialized) {
PasswordRepository.initialize(this)
}
val preferredPasswords = preference.splitLines()
@@ -366,7 +366,7 @@ class AutofillService : AccessibilityService() {
dialog = null
}
- val builder = AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog)
+ val builder = MaterialAlertDialogBuilder(this, R.style.AppTheme_Dialog)
builder.setNegativeButton(R.string.dialog_cancel) { _, _ ->
dialog!!.dismiss()
dialog = null
@@ -391,7 +391,7 @@ class AutofillService : AccessibilityService() {
dialog = null
}
- val builder = AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog)
+ val builder = MaterialAlertDialogBuilder(this, R.style.AppTheme_Dialog)
builder.setNegativeButton(R.string.dialog_cancel) { _, _ ->
dialog!!.dismiss()
dialog = null
@@ -525,11 +525,13 @@ class AutofillService : AccessibilityService() {
}
OpenPgpApi.RESULT_CODE_ERROR -> {
val error = result.getParcelableExtra<OpenPgpError>(OpenPgpApi.RESULT_ERROR)
- Toast.makeText(this@AutofillService,
- "Error from OpenKeyChain : " + error.message,
- Toast.LENGTH_LONG).show()
- Log.e(Constants.TAG, "onError getErrorId:" + error.errorId)
- Log.e(Constants.TAG, "onError getMessage:" + error.message)
+ if (error != null) {
+ Toast.makeText(this@AutofillService,
+ "Error from OpenKeyChain : " + error.message,
+ Toast.LENGTH_LONG).show()
+ Log.e(Constants.TAG, "onError getErrorId:" + error.errorId)
+ Log.e(Constants.TAG, "onError getMessage:" + error.message)
+ }
}
}
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
index 665e8b7b..81fef413 100644
--- a/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PgpActivity.kt
@@ -12,7 +12,6 @@ import android.os.AsyncTask
import android.os.Bundle
import android.os.ConditionVariable
import android.os.Handler
-import android.preference.PreferenceManager
import android.text.TextUtils
import android.text.format.DateUtils
import android.text.method.PasswordTransformationMethod
@@ -29,8 +28,9 @@ import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
-import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
+import androidx.preference.PreferenceManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.PasswordEntry
import com.zeapo.pwdstore.PasswordGeneratorDialogFragment
import com.zeapo.pwdstore.R
@@ -156,8 +156,8 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
return true
}
- override fun onOptionsItemSelected(item: MenuItem?): Boolean {
- when (item?.itemId) {
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
android.R.id.home -> {
if (passwordEntry?.hotpIsIncremented() == false) {
setResult(RESULT_CANCELED)
@@ -196,10 +196,10 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
private fun handleUserInteractionRequest(result: Intent, requestCode: Int) {
Log.i(TAG, "RESULT_CODE_USER_INTERACTION_REQUIRED")
- val pi: PendingIntent = result.getParcelableExtra(RESULT_INTENT)
+ val pi: PendingIntent? = result.getParcelableExtra(RESULT_INTENT)
try {
this@PgpActivity.startIntentSenderFromChild(
- this@PgpActivity, pi.intentSender, requestCode,
+ this@PgpActivity, pi?.intentSender, requestCode,
null, 0, 0, 0
)
} catch (e: IntentSender.SendIntentException) {
@@ -219,10 +219,12 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
*
* Check in open-pgp-lib how their definitions and error code
*/
- val error: OpenPgpError = result.getParcelableExtra(RESULT_ERROR)
- showToast("Error from OpenKeyChain : " + error.message)
- Log.e(TAG, "onError getErrorId:" + error.errorId)
- Log.e(TAG, "onError getMessage:" + error.message)
+ val error: OpenPgpError? = result.getParcelableExtra(RESULT_ERROR)
+ if (error != null) {
+ showToast("Error from OpenKeyChain : " + error.message)
+ Log.e(TAG, "onError getErrorId:" + error.errorId)
+ Log.e(TAG, "onError getMessage:" + error.message)
+ }
}
private fun initOpenPgpApi() {
@@ -354,7 +356,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
val checkLayout = checkInflater.inflate(R.layout.otp_confirm_layout, null)
val rememberCheck: CheckBox =
checkLayout.findViewById(R.id.hotp_remember_checkbox)
- val dialogBuilder = AlertDialog.Builder(this)
+ val dialogBuilder = MaterialAlertDialogBuilder(this)
dialogBuilder.setView(checkLayout)
dialogBuilder.setMessage(R.string.dialog_update_body)
.setCancelable(false)
@@ -554,6 +556,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
RESULT_CODE_SUCCESS -> {
try {
val ids = result.getLongArrayExtra(OpenPgpApi.RESULT_KEY_IDS)
+ ?: LongArray(0)
val keys = ids.map { it.toString() }.toSet()
// use Long
@@ -754,7 +757,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
val extraText = findViewById<TextView>(R.id.crypto_extra_show)
- if (extraText?.text?.isNotEmpty() ?: false)
+ if (extraText?.text?.isNotEmpty() == true)
findViewById<View>(R.id.crypto_extra_show_layout)?.visibility = View.VISIBLE
if (showTime == 0) {
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt b/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt
index 31c65704..8b23c38e 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/BreakOutOfDetached.kt
@@ -1,7 +1,7 @@
package com.zeapo.pwdstore.git
import android.app.Activity
-import android.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.GitCommand
@@ -37,7 +37,7 @@ class BreakOutOfDetached(fileDir: File, callingActivity: Activity) : GitOperatio
override fun execute() {
val git = Git(repository)
if (!git.repository.repositoryState.isRebasing) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title))
.setMessage("The repository is not rebasing, no need to push to another branch")
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
@@ -59,7 +59,7 @@ class BreakOutOfDetached(fileDir: File, callingActivity: Activity) : GitOperatio
}
override fun onError(errorMessage: String) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occurred when checking out another branch operation $errorMessage")
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
@@ -68,7 +68,7 @@ class BreakOutOfDetached(fileDir: File, callingActivity: Activity) : GitOperatio
}
override fun onSuccess() {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.git_abort_and_push_title))
.setMessage("There was a conflict when trying to rebase. " +
"Your local master branch was pushed to another branch named conflicting-master-....\n" +
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt
index bf407838..8489eb1d 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/CloneOperation.kt
@@ -1,11 +1,10 @@
package com.zeapo.pwdstore.git
import android.app.Activity
-import android.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import org.eclipse.jgit.api.CloneCommand
import org.eclipse.jgit.api.Git
-
import java.io.File
/**
@@ -23,7 +22,10 @@ class CloneOperation(fileDir: File, callingActivity: Activity) : GitOperation(fi
* @return the current object
*/
fun setCommand(uri: String): CloneOperation {
- this.command = Git.cloneRepository().setCloneAllBranches(true).setDirectory(repository.workTree).setURI(uri)
+ this.command = Git.cloneRepository()
+ .setCloneAllBranches(true)
+ .setDirectory(repository?.workTree)
+ .setURI(uri)
return this
}
@@ -53,14 +55,12 @@ class CloneOperation(fileDir: File, callingActivity: Activity) : GitOperation(fi
}
override fun execute() {
- if (this.provider != null) {
- (this.command as CloneCommand).setCredentialsProvider(this.provider)
- }
+ (this.command as? CloneCommand)?.setCredentialsProvider(this.provider)
GitAsyncTask(callingActivity, true, false, this).execute(this.command)
}
override fun onError(errorMessage: String) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occured during the clone operation, "
+ callingActivity.resources.getString(R.string.jgit_error_dialog_text)
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java b/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java
deleted file mode 100644
index 9ef05f60..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.java
+++ /dev/null
@@ -1,726 +0,0 @@
-package com.zeapo.pwdstore.git;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-
-import com.zeapo.pwdstore.R;
-import com.zeapo.pwdstore.UserPreference;
-import com.zeapo.pwdstore.git.config.SshApiSessionFactory;
-import com.zeapo.pwdstore.utils.PasswordRepository;
-
-import org.apache.commons.io.FileUtils;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class GitActivity extends AppCompatActivity {
- public static final int REQUEST_PULL = 101;
- public static final int REQUEST_PUSH = 102;
- public static final int REQUEST_CLONE = 103;
- public static final int REQUEST_INIT = 104;
- public static final int EDIT_SERVER = 105;
- public static final int REQUEST_SYNC = 106;
- public static final int REQUEST_CREATE = 107;
- public static final int EDIT_GIT_CONFIG = 108;
- public static final int BREAK_OUT_OF_DETACHED = 109;
- private static final String TAG = "GitAct";
- private static final String emailPattern = "^[^@]+@[^@]+$";
- private Activity activity;
- private Context context;
- private String protocol;
- private String connectionMode;
- private String hostname;
- private SharedPreferences settings;
- private SshApiSessionFactory.IdentityBuilder identityBuilder;
- private SshApiSessionFactory.ApiIdentity identity;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- context = getApplicationContext();
- activity = this;
-
- settings = PreferenceManager.getDefaultSharedPreferences(this.context);
-
- protocol = settings.getString("git_remote_protocol", "ssh://");
- connectionMode = settings.getString("git_remote_auth", "ssh-key");
- int operationCode = getIntent().getExtras().getInt("Operation");
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- switch (operationCode) {
- case REQUEST_CLONE:
- case EDIT_SERVER:
- setContentView(R.layout.activity_git_clone);
- setTitle(R.string.title_activity_git_clone);
-
- final Spinner protcol_spinner = findViewById(R.id.clone_protocol);
- final Spinner connection_mode_spinner = findViewById(R.id.connection_mode);
-
- // init the spinner for connection modes
- final ArrayAdapter<CharSequence> connection_mode_adapter = ArrayAdapter.createFromResource(this,
- R.array.connection_modes, android.R.layout.simple_spinner_item);
- connection_mode_adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- connection_mode_spinner.setAdapter(connection_mode_adapter);
- connection_mode_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
- String selection = ((Spinner) findViewById(R.id.connection_mode)).getSelectedItem().toString();
- connectionMode = selection;
- settings.edit().putString("git_remote_auth", selection).apply();
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> adapterView) {
-
- }
- });
-
- // init the spinner for protocols
- ArrayAdapter<CharSequence> protocol_adapter = ArrayAdapter.createFromResource(this,
- R.array.clone_protocols, android.R.layout.simple_spinner_item);
- protocol_adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- protcol_spinner.setAdapter(protocol_adapter);
- protcol_spinner.setOnItemSelectedListener(
- new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
- protocol = ((Spinner) findViewById(R.id.clone_protocol)).getSelectedItem().toString();
- if (protocol.equals("ssh://")) {
- ((EditText) findViewById(R.id.clone_uri)).setHint("user@hostname:path");
-
- ((EditText) findViewById(R.id.server_port)).setHint(R.string.default_ssh_port);
-
- // select ssh-key auth mode as default and enable the spinner in case it was disabled
- connection_mode_spinner.setSelection(0);
- connection_mode_spinner.setEnabled(true);
-
- // however, if we have some saved that, that's more important!
- if (connectionMode.equalsIgnoreCase("ssh-key")) {
- connection_mode_spinner.setSelection(0);
- } else if (connectionMode.equalsIgnoreCase("OpenKeychain")) {
- connection_mode_spinner.setSelection(2);
- } else {
- connection_mode_spinner.setSelection(1);
- }
- } else {
- ((EditText) findViewById(R.id.clone_uri)).setHint("hostname/path");
-
- ((EditText) findViewById(R.id.server_port)).setHint(R.string.default_https_port);
-
- // select user/pwd auth-mode and disable the spinner
- connection_mode_spinner.setSelection(1);
- connection_mode_spinner.setEnabled(false);
- }
-
- updateURI();
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> adapterView) {
-
- }
- }
- );
-
- if (protocol.equals("ssh://")) {
- protcol_spinner.setSelection(0);
- } else {
- protcol_spinner.setSelection(1);
- }
-
- // init the server information
- final EditText server_url = findViewById(R.id.server_url);
- final EditText server_port = findViewById(R.id.server_port);
- final EditText server_path = findViewById(R.id.server_path);
- final EditText server_user = findViewById(R.id.server_user);
- final EditText server_uri = findViewById(R.id.clone_uri);
-
- server_url.setText(settings.getString("git_remote_server", ""));
- server_port.setText(settings.getString("git_remote_port", ""));
- server_user.setText(settings.getString("git_remote_username", ""));
- server_path.setText(settings.getString("git_remote_location", ""));
-
- server_url.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- if (server_url.isFocused())
- updateURI();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
- });
- server_port.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- if (server_port.isFocused())
- updateURI();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
- });
- server_user.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- if (server_user.isFocused())
- updateURI();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
- });
- server_path.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- if (server_path.isFocused())
- updateURI();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
- });
-
- server_uri.addTextChangedListener(new TextWatcher() {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- }
-
- @Override
- public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
- if (server_uri.isFocused())
- splitURI();
- }
-
- @Override
- public void afterTextChanged(Editable editable) {
- }
- });
-
- if (operationCode == EDIT_SERVER) {
- findViewById(R.id.clone_button).setVisibility(View.INVISIBLE);
- findViewById(R.id.save_button).setVisibility(View.VISIBLE);
- } else {
- findViewById(R.id.clone_button).setVisibility(View.VISIBLE);
- findViewById(R.id.save_button).setVisibility(View.INVISIBLE);
- }
-
- updateURI();
-
- break;
- case EDIT_GIT_CONFIG:
- setContentView(R.layout.activity_git_config);
- setTitle(R.string.title_activity_git_config);
-
- showGitConfig();
- break;
- case REQUEST_PULL:
- syncRepository(REQUEST_PULL);
- break;
-
- case REQUEST_PUSH:
- syncRepository(REQUEST_PUSH);
- break;
-
- case REQUEST_SYNC:
- syncRepository(REQUEST_SYNC);
- break;
- }
-
-
- }
-
- /**
- * Fills in the server_uri field with the information coming from other fields
- */
- private void updateURI() {
- EditText uri = findViewById(R.id.clone_uri);
- EditText server_url = findViewById(R.id.server_url);
- EditText server_port = findViewById(R.id.server_port);
- EditText server_path = findViewById(R.id.server_path);
- EditText server_user = findViewById(R.id.server_user);
-
- if (uri != null) {
- switch (protocol) {
- case "ssh://": {
- String hostname =
- server_user.getText()
- + "@" +
- server_url.getText().toString().trim()
- + ":";
- if (server_port.getText().toString().equals("22")) {
- hostname += server_path.getText().toString();
-
- findViewById(R.id.warn_url).setVisibility(View.GONE);
- } else {
- TextView warn_url = findViewById(R.id.warn_url);
- if (!server_path.getText().toString().matches("/.*") && !server_port.getText().toString().isEmpty()) {
- warn_url.setText(R.string.warn_malformed_url_port);
- warn_url.setVisibility(View.VISIBLE);
- } else {
- warn_url.setVisibility(View.GONE);
- }
- hostname += server_port.getText().toString() + server_path.getText().toString();
- }
-
- if (!hostname.equals("@:")) uri.setText(hostname);
- }
- break;
- case "https://": {
- StringBuilder hostname = new StringBuilder();
- hostname.append(server_url.getText().toString().trim());
-
- if (server_port.getText().toString().equals("443")) {
- hostname.append(server_path.getText().toString());
-
- findViewById(R.id.warn_url).setVisibility(View.GONE);
- } else {
- hostname.append("/");
- hostname.append(server_port.getText().toString())
- .append(server_path.getText().toString());
- }
-
- if (!hostname.toString().equals("@/")) uri.setText(hostname);
- }
- break;
- default:
- break;
- }
-
- }
- }
-
- /**
- * Splits the information in server_uri into the other fields
- */
- private void splitURI() {
- EditText server_uri = findViewById(R.id.clone_uri);
- EditText server_url = findViewById(R.id.server_url);
- EditText server_port = findViewById(R.id.server_port);
- EditText server_path = findViewById(R.id.server_path);
- EditText server_user = findViewById(R.id.server_user);
-
- String uri = server_uri.getText().toString();
- Pattern pattern = Pattern.compile("(.+)@([\\w\\d.]+):([\\d]+)*(.*)");
- Matcher matcher = pattern.matcher(uri);
- if (matcher.find()) {
- int count = matcher.groupCount();
- if (count > 1) {
- server_user.setText(matcher.group(1));
- server_url.setText(matcher.group(2));
- }
- if (count == 4) {
- server_port.setText(matcher.group(3));
- server_path.setText(matcher.group(4));
-
- TextView warn_url = findViewById(R.id.warn_url);
- if (!server_path.getText().toString().matches("/.*") && !server_port.getText().toString().isEmpty()) {
- warn_url.setText(R.string.warn_malformed_url_port);
- warn_url.setVisibility(View.VISIBLE);
- } else {
- warn_url.setVisibility(View.GONE);
- }
- }
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updateURI();
- }
-
- @Override
- protected void onDestroy() {
- // Do not leak the service connection
- if (identityBuilder != null) {
- identityBuilder.close();
- identityBuilder = null;
- }
- super.onDestroy();
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- // Inflate the menu; this adds items to the action bar if it is present.
- getMenuInflater().inflate(R.menu.git_clone, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // Handle action bar item clicks here. The action bar will
- // automatically handle clicks on the Home/Up button, so long
- // as you specify a parent activity in AndroidManifest.xml.
- int id = item.getItemId();
- if (id == R.id.user_pref) {
- try {
- Intent intent = new Intent(this, UserPreference.class);
- startActivity(intent);
- } catch (Exception e) {
- System.out.println("Exception caught :(");
- e.printStackTrace();
- }
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- /**
- * Saves the configuration found in the form
- */
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- private boolean saveConfiguration() {
- // remember the settings
- SharedPreferences.Editor editor = settings.edit();
-
- editor.putString("git_remote_server", ((EditText) findViewById(R.id.server_url)).getText().toString());
- editor.putString("git_remote_location", ((EditText) findViewById(R.id.server_path)).getText().toString());
- editor.putString("git_remote_username", ((EditText) findViewById(R.id.server_user)).getText().toString());
- editor.putString("git_remote_protocol", protocol);
- editor.putString("git_remote_auth", connectionMode);
- editor.putString("git_remote_port", ((EditText) findViewById(R.id.server_port)).getText().toString());
- editor.putString("git_remote_uri", ((EditText) findViewById(R.id.clone_uri)).getText().toString());
-
- // 'save' hostname variable for use by addRemote() either here or later
- // in syncRepository()
- hostname = ((EditText) findViewById(R.id.clone_uri)).getText().toString();
- String port = ((EditText) findViewById(R.id.server_port)).getText().toString();
- // don't ask the user, take off the protocol that he puts in
- hostname = hostname.replaceFirst("^.+://", "");
- ((TextView) findViewById(R.id.clone_uri)).setText(hostname);
-
- if (!protocol.equals("ssh://")) {
- hostname = protocol + hostname;
- } else {
- // if the port is explicitly given, jgit requires the ssh://
- if (!port.isEmpty() && !port.equals("22"))
- hostname = protocol + hostname;
-
- // did he forget the username?
- if (!hostname.matches("^.+@.+")) {
- new AlertDialog.Builder(this).
- setMessage(activity.getResources().getString(R.string.forget_username_dialog_text)).
- setPositiveButton(activity.getResources().getString(R.string.dialog_oops), null).
- show();
- return false;
- }
- }
- if (PasswordRepository.isInitialized() && settings.getBoolean("repository_initialized", false)) {
- // don't just use the clone_uri text, need to use hostname which has
- // had the proper protocol prepended
- PasswordRepository.addRemote("origin", hostname, true);
- }
-
- editor.apply();
- return true;
- }
-
- /**
- * Save the repository information to the shared preferences settings
- */
- public void saveConfiguration(View view) {
- if (!saveConfiguration())
- return;
- finish();
- }
-
- private void showGitConfig() {
- // init the server information
- final EditText git_user_name = findViewById(R.id.git_user_name);
- final EditText git_user_email = findViewById(R.id.git_user_email);
- final Button abort = findViewById(R.id.git_abort_rebase);
-
- git_user_name.setText(settings.getString("git_config_user_name", ""));
- git_user_email.setText(settings.getString("git_config_user_email", ""));
-
- // git status
- Repository repo = PasswordRepository.getRepository(PasswordRepository.getRepositoryDirectory(activity.getApplicationContext()));
- if (repo != null) {
- final TextView git_commit_hash = findViewById(R.id.git_commit_hash);
- try {
- ObjectId objectId = repo.resolve(Constants.HEAD);
- Ref ref = repo.getRef("refs/heads/master");
- String head = ref.getObjectId().equals(objectId) ? ref.getName() : "DETACHED";
- git_commit_hash.setText(String.format("%s (%s)", objectId.abbreviate(8).name(), head));
-
- // enable the abort button only if we're rebasing
- abort.setEnabled(repo.getRepositoryState().isRebasing());
- } catch (Exception e) {
- // ignore
- }
- }
- }
-
- private boolean saveGitConfigs() {
- // remember the settings
- SharedPreferences.Editor editor = settings.edit();
-
- String email = ((EditText) findViewById(R.id.git_user_email)).getText().toString();
- editor.putString("git_config_user_email", email);
- editor.putString("git_config_user_name", ((EditText) findViewById(R.id.git_user_name)).getText().toString());
-
- if (!email.matches(emailPattern)) {
- new AlertDialog.Builder(this).
- setMessage(activity.getResources().getString(R.string.invalid_email_dialog_text)).
- setPositiveButton(activity.getResources().getString(R.string.dialog_oops), null).
- show();
- return false;
- }
-
- editor.apply();
- return true;
- }
-
- public void applyGitConfigs(View view) {
- if (!saveGitConfigs())
- return;
-
- String git_user_name = settings.getString("git_config_user_name", "");
- String git_user_email = settings.getString("git_config_user_email", "");
-
- PasswordRepository.setUserName(git_user_name);
- PasswordRepository.setUserEmail(git_user_email);
-
- finish();
- }
-
- public void abortRebase(View view) {
- launchGitOperation(BREAK_OUT_OF_DETACHED);
- }
-
- /**
- * Clones the repository, the directory exists, deletes it
- */
- public void cloneRepository(View view) {
- if (PasswordRepository.getRepository(null) == null) {
- PasswordRepository.initialize(this);
- }
- File localDir = PasswordRepository.getRepositoryDirectory(context);
-
- if (!saveConfiguration())
- return;
-
- // Warn if non-empty folder unless it's a just-initialized store that has just a .git folder
- if (localDir.exists() && localDir.listFiles().length != 0
- && !(localDir.listFiles().length == 1 && localDir.listFiles()[0].getName().equals(".git"))) {
- new AlertDialog.Builder(this).
- setTitle(R.string.dialog_delete_title).
- setMessage(getResources().getString(R.string.dialog_delete_msg) + " " + localDir.toString()).
- setCancelable(false).
- setPositiveButton(R.string.dialog_delete,
- (dialog, id) -> {
- try {
- FileUtils.deleteDirectory(localDir);
- launchGitOperation(REQUEST_CLONE);
- } catch (IOException e) {
- //TODO Handle the exception correctly if we are unable to delete the directory...
- e.printStackTrace();
- new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
- }
-
- dialog.cancel();
- }
- ).
- setNegativeButton(R.string.dialog_do_not_delete,
- (dialog, id) -> dialog.cancel()
- ).
- show();
- } else {
- try {
- // Silently delete & replace the lone .git folder if it exists
- if (localDir.exists() && localDir.listFiles().length == 1 && localDir.listFiles()[0].getName().equals(".git")) {
- try {
- FileUtils.deleteDirectory(localDir);
- } catch (IOException e) {
- e.printStackTrace();
- new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
- }
- }
- } catch (Exception e) {
- //This is what happens when jgit fails :(
- //TODO Handle the diffent cases of exceptions
- e.printStackTrace();
- new AlertDialog.Builder(this).setMessage(e.getMessage()).show();
- }
- launchGitOperation(REQUEST_CLONE);
- }
- }
-
- /**
- * Syncs the local repository with the remote one (either pull or push)
- *
- * @param operation the operation to execute can be REQUEST_PULL or REQUEST_PUSH
- */
- private void syncRepository(int operation) {
- if (settings.getString("git_remote_username", "").isEmpty() ||
- settings.getString("git_remote_server", "").isEmpty() ||
- settings.getString("git_remote_location", "").isEmpty())
- new AlertDialog.Builder(this)
- .setMessage(activity.getResources().getString(R.string.set_information_dialog_text))
- .setPositiveButton(activity.getResources().getString(R.string.dialog_positive), (dialogInterface, i) -> {
- Intent intent = new Intent(activity, UserPreference.class);
- startActivityForResult(intent, REQUEST_PULL);
- })
- .setNegativeButton(activity.getResources().getString(R.string.dialog_negative), (dialogInterface, i) -> {
- // do nothing :(
- setResult(RESULT_OK);
- finish();
- })
- .show();
-
- else {
- // check that the remote origin is here, else add it
- PasswordRepository.addRemote("origin", hostname, false);
- launchGitOperation(operation);
- }
- }
-
- /**
- * Attempt to launch the requested GIT operation. Depending on the configured auth, it may not
- * be possible to launch the operation immediately. In that case, this function may launch an
- * intermediate activity instead, which will gather necessary information and post it back via
- * onActivityResult, which will then re-call this function. This may happen multiple times,
- * until either an error is encountered or the operation is successfully launched.
- *
- * @param operation The type of GIT operation to launch
- */
- protected void launchGitOperation(int operation) {
- GitOperation op;
- File localDir = PasswordRepository.getRepositoryDirectory(context);
-
- try {
-
- // Before launching the operation with OpenKeychain auth, we need to issue several requests
- // to the OpenKeychain API. IdentityBuild will take care of launching the relevant intents,
- // we just need to keep calling it until it returns a completed ApiIdentity.
- if (connectionMode.equalsIgnoreCase("OpenKeychain") && identity == null) {
- // Lazy initialization of the IdentityBuilder
- if (identityBuilder == null) {
- identityBuilder = new SshApiSessionFactory.IdentityBuilder(this);
- }
-
- // Try to get an ApiIdentity and bail if one is not ready yet. The builder will ensure
- // that onActivityResult is called with operation again, which will re-invoke us here
- identity = identityBuilder.tryBuild(operation);
- if (identity == null)
- return;
- }
-
- switch (operation) {
- case REQUEST_CLONE:
- case GitOperation.GET_SSH_KEY_FROM_CLONE:
- op = new CloneOperation(localDir, activity).setCommand(hostname);
- break;
-
- case REQUEST_PULL:
- op = new PullOperation(localDir, activity).setCommand();
- break;
-
- case REQUEST_PUSH:
- op = new PushOperation(localDir, activity).setCommand();
- break;
-
- case REQUEST_SYNC:
- op = new SyncOperation(localDir, activity).setCommands();
- break;
-
- case BREAK_OUT_OF_DETACHED:
- op = new BreakOutOfDetached(localDir, activity).setCommands();
- break;
-
- case SshApiSessionFactory.POST_SIGNATURE:
- return;
-
- default:
- Log.e(TAG, "Operation not recognized : " + operation);
- setResult(RESULT_CANCELED);
- finish();
- return;
- }
-
- op.executeAfterAuthentication(connectionMode,
- settings.getString("git_remote_username", "git"),
- new File(getFilesDir() + "/.ssh_key"),
- identity);
- } catch (Exception e) {
- e.printStackTrace();
- new AlertDialog.Builder(this).setMessage(e.getMessage()).show();
- }
- }
-
- protected void onActivityResult(int requestCode, int resultCode,
- Intent data) {
-
- // In addition to the pre-operation-launch series of intents for OpenKeychain auth
- // that will pass through here and back to launchGitOperation, there is one
- // synchronous operation that happens /after/ the operation has been launched in the
- // background thread - the actual signing of the SSH challenge. We pass through the
- // completed signature to the ApiIdentity, which will be blocked in the other thread
- // waiting for it.
- if (requestCode == SshApiSessionFactory.POST_SIGNATURE && identity != null)
- identity.postSignature(data);
-
- if (resultCode == RESULT_CANCELED) {
- setResult(RESULT_CANCELED);
- finish();
- } else if (resultCode == RESULT_OK) {
- // If an operation has been re-queued via this mechanism, let the
- // IdentityBuilder attempt to extract some updated state from the intent before
- // trying to re-launch the operation.
- if (identityBuilder != null) {
- identityBuilder.consume(data);
- }
- launchGitOperation(requestCode);
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
-
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.kt
new file mode 100644
index 00000000..11cc4c1b
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitActivity.kt
@@ -0,0 +1,660 @@
+package com.zeapo.pwdstore.git
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.util.Log
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Spinner
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.preference.PreferenceManager
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.textfield.TextInputEditText
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.UserPreference
+import com.zeapo.pwdstore.git.config.SshApiSessionFactory
+import com.zeapo.pwdstore.utils.PasswordRepository
+import org.apache.commons.io.FileUtils
+import org.eclipse.jgit.lib.Constants
+import java.io.File
+import java.io.IOException
+import java.util.regex.Pattern
+
+open class GitActivity : AppCompatActivity() {
+ private lateinit var context: Context
+ private lateinit var settings: SharedPreferences
+ private lateinit var protocol: String
+ private lateinit var connectionMode: String
+ private lateinit var hostname: String
+ private var identityBuilder: SshApiSessionFactory.IdentityBuilder? = null
+ private var identity: SshApiSessionFactory.ApiIdentity? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ context = requireNotNull(this)
+
+ settings = PreferenceManager.getDefaultSharedPreferences(this)
+
+ protocol = settings.getString("git_remote_protocol", null) ?: "ssh://"
+ connectionMode = settings.getString("git_remote_auth", null) ?: "ssh-key"
+ hostname = settings.getString("git_remote_location", null) ?: ""
+ val operationCode = intent.extras!!.getInt("Operation")
+
+ supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+
+ when (operationCode) {
+ REQUEST_CLONE, EDIT_SERVER -> {
+ setContentView(R.layout.activity_git_clone)
+ setTitle(R.string.title_activity_git_clone)
+
+ val protcolSpinner = findViewById<Spinner>(R.id.clone_protocol)
+ val connectionModeSpinner = findViewById<Spinner>(R.id.connection_mode)
+
+ // init the spinner for connection modes
+ val connectionModeAdapter = ArrayAdapter.createFromResource(this,
+ R.array.connection_modes, android.R.layout.simple_spinner_item)
+ connectionModeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ connectionModeSpinner.adapter = connectionModeAdapter
+ connectionModeSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
+ val selection = (findViewById<View>(R.id.connection_mode) as Spinner).selectedItem.toString()
+ connectionMode = selection
+ settings.edit().putString("git_remote_auth", selection).apply()
+ }
+
+ override fun onNothingSelected(adapterView: AdapterView<*>) {
+
+ }
+ }
+
+ // init the spinner for protocols
+ val protocolAdapter = ArrayAdapter.createFromResource(this,
+ R.array.clone_protocols, android.R.layout.simple_spinner_item)
+ protocolAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ protcolSpinner.adapter = protocolAdapter
+ protcolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(adapterView: AdapterView<*>, view: View, i: Int, l: Long) {
+ protocol = (findViewById<View>(R.id.clone_protocol) as Spinner).selectedItem.toString()
+ if (protocol == "ssh://") {
+
+ // select ssh-key auth mode as default and enable the spinner in case it was disabled
+ connectionModeSpinner.setSelection(0)
+ connectionModeSpinner.isEnabled = true
+
+ // however, if we have some saved that, that's more important!
+ when {
+ connectionMode.equals("ssh-key", ignoreCase = true) -> connectionModeSpinner.setSelection(0)
+ connectionMode.equals("OpenKeychain", ignoreCase = true) -> connectionModeSpinner.setSelection(2)
+ else -> connectionModeSpinner.setSelection(1)
+ }
+ } else {
+ // select user/pwd auth-mode and disable the spinner
+ connectionModeSpinner.setSelection(1)
+ connectionModeSpinner.isEnabled = false
+ }
+
+ updateURI()
+ }
+
+ override fun onNothingSelected(adapterView: AdapterView<*>) {
+
+ }
+ }
+
+ if (protocol == "ssh://") {
+ protcolSpinner.setSelection(0)
+ } else {
+ protcolSpinner.setSelection(1)
+ }
+
+ // init the server information
+ val serverUrl = findViewById<TextInputEditText>(R.id.server_url)
+ val serverPort = findViewById<TextInputEditText>(R.id.server_port)
+ val serverPath = findViewById<TextInputEditText>(R.id.server_path)
+ val serverUser = findViewById<TextInputEditText>(R.id.server_user)
+ val serverUri = findViewById<TextInputEditText>(R.id.clone_uri)
+
+ serverUrl.setText(settings.getString("git_remote_server", ""))
+ serverPort.setText(settings.getString("git_remote_port", ""))
+ serverUser.setText(settings.getString("git_remote_username", ""))
+ serverPath.setText(settings.getString("git_remote_location", ""))
+
+ serverUrl.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
+ if (serverUrl.isFocused)
+ updateURI()
+ }
+
+ override fun afterTextChanged(editable: Editable) {}
+ })
+ serverPort.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
+ if (serverPort.isFocused)
+ updateURI()
+ }
+
+ override fun afterTextChanged(editable: Editable) {}
+ })
+ serverUser.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
+ if (serverUser.isFocused)
+ updateURI()
+ }
+
+ override fun afterTextChanged(editable: Editable) {}
+ })
+ serverPath.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
+ if (serverPath.isFocused)
+ updateURI()
+ }
+
+ override fun afterTextChanged(editable: Editable) {}
+ })
+
+ serverUri.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {}
+
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i2: Int, i3: Int) {
+ if (serverUri.isFocused)
+ splitURI()
+ }
+
+ override fun afterTextChanged(editable: Editable) {}
+ })
+
+ if (operationCode == EDIT_SERVER) {
+ findViewById<View>(R.id.clone_button).visibility = View.INVISIBLE
+ findViewById<View>(R.id.save_button).visibility = View.VISIBLE
+ } else {
+ findViewById<View>(R.id.clone_button).visibility = View.VISIBLE
+ findViewById<View>(R.id.save_button).visibility = View.INVISIBLE
+ }
+
+ updateURI()
+ }
+ EDIT_GIT_CONFIG -> {
+ setContentView(R.layout.activity_git_config)
+ setTitle(R.string.title_activity_git_config)
+
+ showGitConfig()
+ }
+ REQUEST_PULL -> syncRepository(REQUEST_PULL)
+
+ REQUEST_PUSH -> syncRepository(REQUEST_PUSH)
+
+ REQUEST_SYNC -> syncRepository(REQUEST_SYNC)
+ }
+ }
+
+ /**
+ * Fills in the server_uri field with the information coming from other fields
+ */
+ private fun updateURI() {
+ val uri = findViewById<TextInputEditText>(R.id.clone_uri)
+ val serverUrl = findViewById<TextInputEditText>(R.id.server_url)
+ val serverPort = findViewById<TextInputEditText>(R.id.server_port)
+ val serverPath = findViewById<TextInputEditText>(R.id.server_path)
+ val serverUser = findViewById<TextInputEditText>(R.id.server_user)
+
+ if (uri != null) {
+ when (protocol) {
+ "ssh://" -> {
+ var hostname = (serverUser.text.toString()
+ + "@" +
+ serverUrl.text.toString().trim { it <= ' ' }
+ + ":")
+ if (serverPort.text.toString() == "22") {
+ hostname += serverPath.text.toString()
+
+ findViewById<View>(R.id.warn_url).visibility = View.GONE
+ } else {
+ val warnUrl = findViewById<AppCompatTextView>(R.id.warn_url)
+ if (!serverPath.text.toString().matches("/.*".toRegex()) && serverPort.text.toString().isNotEmpty()) {
+ warnUrl.setText(R.string.warn_malformed_url_port)
+ warnUrl.visibility = View.VISIBLE
+ } else {
+ warnUrl.visibility = View.GONE
+ }
+ hostname += serverPort.text.toString() + serverPath.text.toString()
+ }
+
+ if (hostname != "@:") uri.setText(hostname)
+ }
+ "https://" -> {
+ val hostname = StringBuilder()
+ hostname.append(serverUrl.text.toString().trim { it <= ' ' })
+
+ if (serverPort.text.toString() == "443") {
+ hostname.append(serverPath.text.toString())
+
+ findViewById<View>(R.id.warn_url).visibility = View.GONE
+ } else {
+ hostname.append("/")
+ hostname.append(serverPort.text.toString())
+ .append(serverPath.text.toString())
+ }
+
+ if (hostname.toString() != "@/") uri.setText(hostname)
+ }
+ else -> {
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Splits the information in server_uri into the other fields
+ */
+ private fun splitURI() {
+ val serverUri = findViewById<TextInputEditText>(R.id.clone_uri)
+ val serverUrl = findViewById<TextInputEditText>(R.id.server_url)
+ val serverPort = findViewById<TextInputEditText>(R.id.server_port)
+ val serverPath = findViewById<TextInputEditText>(R.id.server_path)
+ val serverUser = findViewById<TextInputEditText>(R.id.server_user)
+
+ val uri = serverUri.text.toString()
+ val pattern = Pattern.compile("(.+)@([\\w\\d.]+):([\\d]+)*(.*)")
+ val matcher = pattern.matcher(uri)
+ if (matcher.find()) {
+ val count = matcher.groupCount()
+ if (count > 1) {
+ serverUser.setText(matcher.group(1))
+ serverUrl.setText(matcher.group(2))
+ }
+ if (count == 4) {
+ serverPort.setText(matcher.group(3))
+ serverPath.setText(matcher.group(4))
+
+ val warnUrl = findViewById<AppCompatTextView>(R.id.warn_url)
+ if (!serverPath.text.toString().matches("/.*".toRegex()) && serverPort.text.toString().isNotEmpty()) {
+ warnUrl.setText(R.string.warn_malformed_url_port)
+ warnUrl.visibility = View.VISIBLE
+ } else {
+ warnUrl.visibility = View.GONE
+ }
+ }
+ }
+ }
+
+ public override fun onResume() {
+ super.onResume()
+ updateURI()
+ }
+
+ override fun onDestroy() {
+ // Do not leak the service connection
+ if (identityBuilder != null) {
+ identityBuilder!!.close()
+ identityBuilder = null
+ }
+ super.onDestroy()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ menuInflater.inflate(R.menu.git_clone, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.user_pref -> try {
+ val intent = Intent(this, UserPreference::class.java)
+ startActivity(intent)
+ return true
+ } catch (e: Exception) {
+ println("Exception caught :(")
+ e.printStackTrace()
+ }
+
+ android.R.id.home -> {
+ finish()
+ return true
+ }
+ }
+ return super.onOptionsItemSelected(item)
+ }
+
+ /**
+ * Saves the configuration found in the form
+ */
+ private fun saveConfiguration(): Boolean {
+ // remember the settings
+ val editor = settings.edit()
+
+ editor.putString("git_remote_server", (findViewById<View>(R.id.server_url) as TextInputEditText).text.toString())
+ editor.putString("git_remote_location", (findViewById<View>(R.id.server_path) as TextInputEditText).text.toString())
+ editor.putString("git_remote_username", (findViewById<View>(R.id.server_user) as TextInputEditText).text.toString())
+ editor.putString("git_remote_protocol", protocol)
+ editor.putString("git_remote_auth", connectionMode)
+ editor.putString("git_remote_port", (findViewById<View>(R.id.server_port) as TextInputEditText).text.toString())
+ editor.putString("git_remote_uri", (findViewById<View>(R.id.clone_uri) as TextInputEditText).text.toString())
+
+ // 'save' hostname variable for use by addRemote() either here or later
+ // in syncRepository()
+ hostname = (findViewById<View>(R.id.clone_uri) as TextInputEditText).text.toString()
+ val port = (findViewById<View>(R.id.server_port) as TextInputEditText).text.toString()
+ // don't ask the user, take off the protocol that he puts in
+ hostname = hostname.replaceFirst("^.+://".toRegex(), "")
+ (findViewById<View>(R.id.clone_uri) as TextInputEditText).setText(hostname)
+
+ if (protocol != "ssh://") {
+ hostname = protocol + hostname
+ } else {
+ // if the port is explicitly given, jgit requires the ssh://
+ if (port.isNotEmpty() && port != "22")
+ hostname = protocol + hostname
+
+ // did he forget the username?
+ if (!hostname.matches("^.+@.+".toRegex())) {
+ MaterialAlertDialogBuilder(this)
+ .setMessage(context.getString(R.string.forget_username_dialog_text))
+ .setPositiveButton(context.getString(R.string.dialog_oops), null)
+ .show()
+ return false
+ }
+ }
+ if (PasswordRepository.isInitialized && settings.getBoolean("repository_initialized", false)) {
+ // don't just use the clone_uri text, need to use hostname which has
+ // had the proper protocol prepended
+ PasswordRepository.addRemote("origin", hostname, true)
+ }
+
+ editor.apply()
+ return true
+ }
+
+ /**
+ * Save the repository information to the shared preferences settings
+ */
+ @Suppress("UNUSED_PARAMETER")
+ fun saveConfiguration(view: View) {
+ if (!saveConfiguration())
+ return
+ finish()
+ }
+
+ private fun showGitConfig() {
+ // init the server information
+ val username = findViewById<TextInputEditText>(R.id.git_user_name)
+ val email = findViewById<TextInputEditText>(R.id.git_user_email)
+ val abort = findViewById<MaterialButton>(R.id.git_abort_rebase)
+
+ username.setText(settings.getString("git_config_user_name", ""))
+ email.setText(settings.getString("git_config_user_email", ""))
+
+ // git status
+ val repo = PasswordRepository.getRepository(PasswordRepository.getRepositoryDirectory(context))
+ if (repo != null) {
+ val commitHash = findViewById<AppCompatTextView>(R.id.git_commit_hash)
+ try {
+ val objectId = repo.resolve(Constants.HEAD)
+ val ref = repo.getRef("refs/heads/master")
+ val head = if (ref.objectId.equals(objectId)) ref.name else "DETACHED"
+ commitHash.text = String.format("%s (%s)", objectId.abbreviate(8).name(), head)
+
+ // enable the abort button only if we're rebasing
+ val isRebasing = repo.repositoryState.isRebasing
+ abort.isEnabled = isRebasing
+ abort.alpha = if (isRebasing) 1.0f else 0.5f
+ } catch (e: Exception) {
+ // ignore
+ }
+
+ }
+ }
+
+ private fun saveGitConfigs(): Boolean {
+ // remember the settings
+ val editor = settings.edit()
+
+ val email = (findViewById<View>(R.id.git_user_email) as TextInputEditText).text!!.toString()
+ editor.putString("git_config_user_email", email)
+ editor.putString("git_config_user_name", (findViewById<View>(R.id.git_user_name) as TextInputEditText).text.toString())
+
+ if (!email.matches(emailPattern.toRegex())) {
+ MaterialAlertDialogBuilder(this)
+ .setMessage(context.getString(R.string.invalid_email_dialog_text))
+ .setPositiveButton(context.getString(R.string.dialog_oops), null)
+ .show()
+ return false
+ }
+
+ editor.apply()
+ return true
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun applyGitConfigs(view: View) {
+ if (!saveGitConfigs())
+ return
+ PasswordRepository.setUserName(settings.getString("git_config_user_name", null) ?: "")
+ PasswordRepository.setUserEmail(settings.getString("git_config_user_email", null) ?: "")
+ finish()
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun abortRebase(view: View) {
+ launchGitOperation(BREAK_OUT_OF_DETACHED)
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun resetToRemote(view: View) {
+ launchGitOperation(REQUEST_RESET)
+ }
+
+ /**
+ * Clones the repository, the directory exists, deletes it
+ */
+ @Suppress("UNUSED_PARAMETER")
+ fun cloneRepository(view: View) {
+ if (PasswordRepository.getRepository(null) == null) {
+ PasswordRepository.initialize(this)
+ }
+ val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory(context))
+
+ if (!saveConfiguration())
+ return
+
+ // Warn if non-empty folder unless it's a just-initialized store that has just a .git folder
+ if (localDir.exists() && localDir.listFiles()!!.isNotEmpty()
+ && !(localDir.listFiles()!!.size == 1 && localDir.listFiles()!![0].name == ".git")) {
+ MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.dialog_delete_title)
+ .setMessage(resources.getString(R.string.dialog_delete_msg) + " " + localDir.toString())
+ .setCancelable(false)
+ .setPositiveButton(R.string.dialog_delete
+ ) { dialog, _ ->
+ try {
+ FileUtils.deleteDirectory(localDir)
+ launchGitOperation(REQUEST_CLONE)
+ } catch (e: IOException) {
+ //TODO Handle the exception correctly if we are unable to delete the directory...
+ e.printStackTrace()
+ MaterialAlertDialogBuilder(this).setMessage(e.message).show()
+ }
+
+ dialog.cancel()
+ }
+ .setNegativeButton(R.string.dialog_do_not_delete
+ ) { dialog, _ -> dialog.cancel() }
+ .show()
+ } else {
+ try {
+ // Silently delete & replace the lone .git folder if it exists
+ if (localDir.exists() && localDir.listFiles()!!.size == 1 && localDir.listFiles()!![0].name == ".git") {
+ try {
+ FileUtils.deleteDirectory(localDir)
+ } catch (e: IOException) {
+ e.printStackTrace()
+ MaterialAlertDialogBuilder(this).setMessage(e.message).show()
+ }
+
+ }
+ } catch (e: Exception) {
+ //This is what happens when jgit fails :(
+ //TODO Handle the diffent cases of exceptions
+ e.printStackTrace()
+ MaterialAlertDialogBuilder(this).setMessage(e.message).show()
+ }
+
+ launchGitOperation(REQUEST_CLONE)
+ }
+ }
+
+ /**
+ * Syncs the local repository with the remote one (either pull or push)
+ *
+ * @param operation the operation to execute can be REQUEST_PULL or REQUEST_PUSH
+ */
+ private fun syncRepository(operation: Int) {
+ if (settings.getString("git_remote_username", "")!!.isEmpty() ||
+ settings.getString("git_remote_server", "")!!.isEmpty() ||
+ settings.getString("git_remote_location", "")!!.isEmpty())
+ MaterialAlertDialogBuilder(this)
+ .setMessage(context.getString(R.string.set_information_dialog_text))
+ .setPositiveButton(context.getString(R.string.dialog_positive)) { _, _ ->
+ val intent = Intent(context, UserPreference::class.java)
+ startActivityForResult(intent, REQUEST_PULL)
+ }
+ .setNegativeButton(context.getString(R.string.dialog_negative)) { _, _ ->
+ // do nothing :(
+ setResult(AppCompatActivity.RESULT_OK)
+ finish()
+ }
+ .show()
+ else {
+ // check that the remote origin is here, else add it
+ PasswordRepository.addRemote("origin", hostname, false)
+ launchGitOperation(operation)
+ }
+ }
+
+ /**
+ * Attempt to launch the requested GIT operation. Depending on the configured auth, it may not
+ * be possible to launch the operation immediately. In that case, this function may launch an
+ * intermediate activity instead, which will gather necessary information and post it back via
+ * onActivityResult, which will then re-call this function. This may happen multiple times,
+ * until either an error is encountered or the operation is successfully launched.
+ *
+ * @param operation The type of GIT operation to launch
+ */
+ private fun launchGitOperation(operation: Int) {
+ val op: GitOperation
+ val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory(context))
+
+ try {
+
+ // Before launching the operation with OpenKeychain auth, we need to issue several requests
+ // to the OpenKeychain API. IdentityBuild will take care of launching the relevant intents,
+ // we just need to keep calling it until it returns a completed ApiIdentity.
+ if (connectionMode.equals("OpenKeychain", ignoreCase = true) && identity == null) {
+ // Lazy initialization of the IdentityBuilder
+ if (identityBuilder == null) {
+ identityBuilder = SshApiSessionFactory.IdentityBuilder(this)
+ }
+
+ // Try to get an ApiIdentity and bail if one is not ready yet. The builder will ensure
+ // that onActivityResult is called with operation again, which will re-invoke us here
+ identity = identityBuilder!!.tryBuild(operation)
+ if (identity == null)
+ return
+ }
+
+ when (operation) {
+ REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> op = CloneOperation(localDir, this).setCommand(hostname)
+
+ REQUEST_PULL -> op = PullOperation(localDir, this).setCommand()
+
+ REQUEST_PUSH -> op = PushOperation(localDir, this).setCommand()
+
+ REQUEST_SYNC -> op = SyncOperation(localDir, this).setCommands()
+
+ BREAK_OUT_OF_DETACHED -> op = BreakOutOfDetached(localDir, this).setCommands()
+
+ REQUEST_RESET -> op = ResetToRemoteOperation(localDir, this).setCommands()
+
+ SshApiSessionFactory.POST_SIGNATURE -> return
+
+ else -> {
+ Log.e(TAG, "Operation not recognized : $operation")
+ setResult(AppCompatActivity.RESULT_CANCELED)
+ finish()
+ return
+ }
+ }
+
+ op.executeAfterAuthentication(connectionMode,
+ settings.getString("git_remote_username", "git")!!,
+ File("$filesDir/.ssh_key"),
+ identity)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ MaterialAlertDialogBuilder(this).setMessage(e.message).show()
+ }
+
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int,
+ data: Intent?) {
+
+ // In addition to the pre-operation-launch series of intents for OpenKeychain auth
+ // that will pass through here and back to launchGitOperation, there is one
+ // synchronous operation that happens /after/ the operation has been launched in the
+ // background thread - the actual signing of the SSH challenge. We pass through the
+ // completed signature to the ApiIdentity, which will be blocked in the other thread
+ // waiting for it.
+ if (requestCode == SshApiSessionFactory.POST_SIGNATURE && identity != null)
+ identity!!.postSignature(data)
+
+ if (resultCode == AppCompatActivity.RESULT_CANCELED) {
+ setResult(AppCompatActivity.RESULT_CANCELED)
+ finish()
+ } else if (resultCode == AppCompatActivity.RESULT_OK) {
+ // If an operation has been re-queued via this mechanism, let the
+ // IdentityBuilder attempt to extract some updated state from the intent before
+ // trying to re-launch the operation.
+ if (identityBuilder != null) {
+ identityBuilder!!.consume(data)
+ }
+ launchGitOperation(requestCode)
+ }
+ super.onActivityResult(requestCode, resultCode, data)
+ }
+
+ companion object {
+ const val REQUEST_PULL = 101
+ const val REQUEST_PUSH = 102
+ const val REQUEST_CLONE = 103
+ const val REQUEST_INIT = 104
+ const val EDIT_SERVER = 105
+ const val REQUEST_SYNC = 106
+ @Suppress("Unused")
+ const val REQUEST_CREATE = 107
+ const val EDIT_GIT_CONFIG = 108
+ const val BREAK_OUT_OF_DETACHED = 109
+ const val REQUEST_RESET = 110
+ private const val TAG = "GitAct"
+ private const val emailPattern = "^[^@]+@[^@]+$"
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt
index 5c945c30..4ab90e96 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/GitOperation.kt
@@ -3,15 +3,14 @@ package com.zeapo.pwdstore.git
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
-import android.preference.PreferenceManager
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.widget.CheckBox
import android.widget.EditText
import android.widget.LinearLayout
-import androidx.appcompat.app.AlertDialog
-
+import androidx.preference.PreferenceManager
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.jcraft.jsch.JSch
import com.jcraft.jsch.JSchException
import com.jcraft.jsch.KeyPair
@@ -21,12 +20,10 @@ import com.zeapo.pwdstore.git.config.GitConfigSessionFactory
import com.zeapo.pwdstore.git.config.SshApiSessionFactory
import com.zeapo.pwdstore.git.config.SshConfigSessionFactory
import com.zeapo.pwdstore.utils.PasswordRepository
-
import org.eclipse.jgit.api.GitCommand
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.transport.SshSessionFactory
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
-
import java.io.File
/**
@@ -37,7 +34,7 @@ import java.io.File
*/
abstract class GitOperation(fileDir: File, internal val callingActivity: Activity) {
- protected val repository: Repository = PasswordRepository.getRepository(fileDir)
+ protected val repository: Repository? = PasswordRepository.getRepository(fileDir)
internal var provider: UsernamePasswordCredentialsProvider? = null
internal var command: GitCommand<*>? = null
@@ -117,7 +114,7 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
showError: Boolean) {
if (connectionMode.equals("ssh-key", ignoreCase = true)) {
if (sshKey == null || !sshKey.exists()) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setMessage(callingActivity.resources.getString(R.string.ssh_preferences_dialog_text))
.setTitle(callingActivity.resources.getString(R.string.ssh_preferences_dialog_title))
.setPositiveButton(callingActivity.resources.getString(R.string.ssh_preferences_dialog_import)) { _, _ ->
@@ -170,7 +167,7 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
executeAfterAuthentication(connectionMode, username, sshKey, identity, true)
}
} else {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.passphrase_dialog_title))
.setMessage(callingActivity.resources.getString(R.string.passphrase_dialog_text))
.setView(dialogView)
@@ -195,10 +192,11 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
setAuthentication(sshKey, username, "").execute()
}
} catch (e: JSchException) {
- AlertDialog.Builder(callingActivity)
+ e.printStackTrace()
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle("Unable to open the ssh-key")
.setMessage("Please check that it was imported.")
- .setPositiveButton("Ok") { _, _ -> }
+ .setPositiveButton("Ok") { _, _ -> callingActivity.finish() }
.show()
}
@@ -211,7 +209,7 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
password.width = LinearLayout.LayoutParams.MATCH_PARENT
password.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.passphrase_dialog_title))
.setMessage(callingActivity.resources.getString(R.string.password_dialog_text))
.setView(password)
@@ -231,7 +229,7 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
* Action to execute on error
*/
open fun onError(errorMessage: String) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage(callingActivity.resources.getString(R.string.jgit_error_dialog_text) + errorMessage)
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt
index fa6c5445..90c46878 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/PullOperation.kt
@@ -1,11 +1,10 @@
package com.zeapo.pwdstore.git
import android.app.Activity
-import android.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.PullCommand
-
import java.io.File
/**
@@ -30,14 +29,12 @@ class PullOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
}
override fun execute() {
- if (this.provider != null) {
- (this.command as PullCommand).setCredentialsProvider(this.provider)
- }
+ (this.command as? PullCommand)?.setCredentialsProvider(this.provider)
GitAsyncTask(callingActivity, true, false, this).execute(this.command)
}
override fun onError(errorMessage: String) {
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occured during the pull operation, "
+ callingActivity.resources.getString(R.string.jgit_error_dialog_text)
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt
index 9674e5b0..0015c4d1 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/PushOperation.kt
@@ -1,11 +1,10 @@
package com.zeapo.pwdstore.git
import android.app.Activity
-import android.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.PushCommand
-
import java.io.File
/**
@@ -30,15 +29,13 @@ class PushOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
}
override fun execute() {
- if (this.provider != null) {
- (this.command as PushCommand).setCredentialsProvider(this.provider)
- }
+ (this.command as? PushCommand)?.setCredentialsProvider(this.provider)
GitAsyncTask(callingActivity, true, false, this).execute(this.command)
}
override fun onError(errorMessage: String) {
// TODO handle the "Nothing to push" case
- AlertDialog.Builder(callingActivity)
+ MaterialAlertDialogBuilder(callingActivity)
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage(callingActivity.getString(R.string.jgit_error_push_dialog_text) + errorMessage)
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt
new file mode 100644
index 00000000..1bab0981
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/git/ResetToRemoteOperation.kt
@@ -0,0 +1,52 @@
+package com.zeapo.pwdstore.git
+
+import android.app.Activity
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.zeapo.pwdstore.R
+import org.eclipse.jgit.api.AddCommand
+import org.eclipse.jgit.api.FetchCommand
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.api.ResetCommand
+import java.io.File
+
+/**
+ * Creates a new git operation
+ *
+ * @param fileDir the git working tree directory
+ * @param callingActivity the calling activity
+ */
+class ResetToRemoteOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
+ private var addCommand: AddCommand? = null
+ private var fetchCommand: FetchCommand? = null
+ private var resetCommand: ResetCommand? = null
+
+ /**
+ * Sets the command
+ *
+ * @return the current object
+ */
+ fun setCommands(): ResetToRemoteOperation {
+ val git = Git(repository)
+ this.addCommand = git.add().addFilepattern(".")
+ this.fetchCommand = git.fetch().setRemote("origin")
+ this.resetCommand = git.reset().setRef("origin/master").setMode(ResetCommand.ResetType.HARD)
+ return this
+ }
+
+ override fun execute() {
+ this.fetchCommand?.setCredentialsProvider(this.provider)
+ GitAsyncTask(callingActivity, true, false, this)
+ .execute(this.addCommand, this.fetchCommand, this.resetCommand)
+ }
+
+ override fun onError(errorMessage: String) {
+ MaterialAlertDialogBuilder(callingActivity)
+ .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
+ .setMessage("Error occured during the sync operation, "
+ + "\nPlease check the FAQ for possible reasons why this error might occur."
+ + callingActivity.resources.getString(R.string.jgit_error_dialog_text)
+ + errorMessage)
+ .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
+ .show()
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt b/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt
index 8712a200..a0fd52b2 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/git/SyncOperation.kt
@@ -1,7 +1,7 @@
package com.zeapo.pwdstore.git
import android.app.Activity
-import android.app.AlertDialog
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.R
import org.eclipse.jgit.api.AddCommand
import org.eclipse.jgit.api.CommitCommand
@@ -9,7 +9,6 @@ import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.PullCommand
import org.eclipse.jgit.api.PushCommand
import org.eclipse.jgit.api.StatusCommand
-
import java.io.File
/**
@@ -42,14 +41,15 @@ class SyncOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
override fun execute() {
if (this.provider != null) {
- this.pullCommand!!.setCredentialsProvider(this.provider)
- this.pushCommand!!.setCredentialsProvider(this.provider)
+ this.pullCommand?.setCredentialsProvider(this.provider)
+ this.pushCommand?.setCredentialsProvider(this.provider)
}
GitAsyncTask(callingActivity, true, false, this).execute(this.addCommand, this.statusCommand, this.commitCommand, this.pullCommand, this.pushCommand)
}
override fun onError(errorMessage: String) {
- AlertDialog.Builder(callingActivity).setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
+ MaterialAlertDialogBuilder(callingActivity)
+ .setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
.setMessage("Error occured during the sync operation, "
+ "\nPlease check the FAQ for possible reasons why this error might occur."
+ callingActivity.resources.getString(R.string.jgit_error_dialog_text)
diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java b/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java
index 085beadd..dbb5b7b1 100644
--- a/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java
+++ b/app/src/main/java/com/zeapo/pwdstore/git/config/SshApiSessionFactory.java
@@ -5,8 +5,9 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender;
-import androidx.appcompat.app.AlertDialog;
+import androidx.annotation.NonNull;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.jcraft.jsch.Identity;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
@@ -49,9 +50,9 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
this.identity = identity;
}
+ @NonNull
@Override
- protected JSch
- getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException {
+ protected JSch getJSch(@NonNull final OpenSshConfig.Host hc, @NonNull FS fs) throws JSchException {
JSch jsch = super.getJSch(hc, fs);
jsch.removeAllIdentity();
jsch.addIdentity(identity, null);
@@ -59,7 +60,7 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
}
@Override
- protected void configure(OpenSshConfig.Host hc, Session session) {
+ protected void configure(@NonNull OpenSshConfig.Host hc, Session session) {
session.setConfig("StrictHostKeyChecking", "no");
session.setConfig("PreferredAuthentications", "publickey");
@@ -204,8 +205,9 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
@Override
public void onError() {
- new AlertDialog.Builder(callingActivity).setMessage(callingActivity.getString(
- R.string.openkeychain_ssh_api_connect_fail)).show();
+ new MaterialAlertDialogBuilder(callingActivity)
+ .setMessage(callingActivity.getString(
+ R.string.openkeychain_ssh_api_connect_fail)).show();
}
});
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java
deleted file mode 100644
index eba6a02e..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.java
+++ /dev/null
@@ -1,155 +0,0 @@
-package com.zeapo.pwdstore.utils;
-
-import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.graphics.Color;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.zeapo.pwdstore.R;
-
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.TreeSet;
-
-public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRecyclerAdapter.ViewHolder> {
- final Set<Integer> selectedItems = new TreeSet<>();
- private final Activity activity;
- private final ArrayList<PasswordItem> values;
-
- EntryRecyclerAdapter(Activity activity, ArrayList<PasswordItem> values) {
- this.activity = activity;
- this.values = values;
- }
-
- // Return the size of your dataset (invoked by the layout manager)
- @Override
- public int getItemCount() {
- return values.size();
- }
-
- public ArrayList<PasswordItem> getValues() {
- return this.values;
- }
-
- public void clear() {
- this.values.clear();
- this.notifyDataSetChanged();
- }
-
- public void addAll(ArrayList<PasswordItem> list) {
- this.values.addAll(list);
- this.notifyDataSetChanged();
- }
-
- public void add(PasswordItem item) {
- this.values.add(item);
- this.notifyItemInserted(getItemCount());
- }
-
- void toggleSelection(int position) {
- if (!selectedItems.remove(position)) {
- selectedItems.add(position);
- }
- }
-
- // use this after an item is removed to update the positions of items in set
- // that followed the removed position
- public void updateSelectedItems(int position, Set<Integer> selectedItems) {
- Set<Integer> temp = new TreeSet<>();
- for (int selected : selectedItems) {
- if (selected > position) {
- temp.add(selected - 1);
- } else {
- temp.add(selected);
- }
- }
- selectedItems.clear();
- selectedItems.addAll(temp);
- }
-
- public void remove(int position) {
- this.values.remove(position);
- this.notifyItemRemoved(position);
-
- // keep selectedItems updated so we know what to notifyItemChanged
- // (instead of just using notifyDataSetChanged)
- updateSelectedItems(position, selectedItems);
- }
-
- @NonNull
- View.OnLongClickListener getOnLongClickListener(ViewHolder holder, PasswordItem pass) {
- return v -> false;
- }
-
- // Replace the contents of a view (invoked by the layout manager)
- @SuppressLint("SetTextI18n")
- @Override
- public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
- final PasswordItem pass = getValues().get(position);
- holder.name.setText(pass.toString());
- if (pass.getType() == PasswordItem.TYPE_CATEGORY) {
- holder.typeImage.setImageResource(R.drawable.ic_folder_grey600_24dp);
- } else {
- holder.typeImage.setImageResource(R.drawable.ic_action_secure);
- holder.name.setText(pass.toString());
- }
-
- holder.type.setText(pass.getFullPathToParent().replaceAll("(^/)|(/$)", ""));
-
- holder.view.setOnClickListener(getOnClickListener(holder, pass));
-
- holder.view.setOnLongClickListener(getOnLongClickListener(holder, pass));
-
- // after removal, everything is rebound for some reason; views are shuffled?
- boolean selected = selectedItems.contains(position);
- holder.view.setSelected(selected);
- if (selected) {
- holder.itemView.setBackgroundResource(R.color.deep_orange_200);
- holder.type.setTextColor(Color.BLACK);
- } else {
- holder.itemView.setBackgroundColor(Color.alpha(1));
- holder.type.setTextColor(ContextCompat.getColor(activity, R.color.grey_500));
- }
- }
-
- @NonNull
- protected abstract View.OnClickListener getOnClickListener(ViewHolder holder, PasswordItem pass);
-
- // Create new views (invoked by the layout manager)
- @Override
- @NonNull
- public PasswordRecyclerAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
- int viewType) {
- // create a new view
- View v = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.password_row_layout, parent, false);
- return new ViewHolder(v);
- }
-
- // Provide a reference to the views for each data item
- // Complex data items may need more than one view per item, and
- // you provide access to all the views for a data item in a view holder
- static class ViewHolder extends RecyclerView.ViewHolder {
- // each data item is just a string in this case
- public final View view;
- public final TextView name;
- final TextView type;
- final ImageView typeImage;
-
- ViewHolder(View v) {
- super(v);
- view = v;
- name = view.findViewById(R.id.label);
- type = view.findViewById(R.id.type);
- typeImage = view.findViewById(R.id.type_image);
- }
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.kt
new file mode 100644
index 00000000..f52756f4
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/EntryRecyclerAdapter.kt
@@ -0,0 +1,118 @@
+package com.zeapo.pwdstore.utils
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.recyclerview.widget.RecyclerView
+
+import com.zeapo.pwdstore.R
+import com.zeapo.pwdstore.widget.MultiselectableLinearLayout
+
+import java.util.ArrayList
+import java.util.TreeSet
+
+abstract class EntryRecyclerAdapter internal constructor(val values: ArrayList<PasswordItem>) : RecyclerView.Adapter<EntryRecyclerAdapter.ViewHolder>() {
+ internal val selectedItems: MutableSet<Int> = TreeSet()
+
+ // Return the size of your dataset (invoked by the layout manager)
+ override fun getItemCount(): Int {
+ return values.size
+ }
+
+ fun clear() {
+ this.values.clear()
+ this.notifyDataSetChanged()
+ }
+
+ fun addAll(list: ArrayList<PasswordItem>) {
+ this.values.addAll(list)
+ this.notifyDataSetChanged()
+ }
+
+ fun add(item: PasswordItem) {
+ this.values.add(item)
+ this.notifyItemInserted(itemCount)
+ }
+
+ internal fun toggleSelection(position: Int) {
+ if (!selectedItems.remove(position)) {
+ selectedItems.add(position)
+ }
+ }
+
+ // use this after an item is removed to update the positions of items in set
+ // that followed the removed position
+ fun updateSelectedItems(position: Int, selectedItems: MutableSet<Int>) {
+ val temp = TreeSet<Int>()
+ for (selected in selectedItems) {
+ if (selected > position) {
+ temp.add(selected - 1)
+ } else {
+ temp.add(selected)
+ }
+ }
+ selectedItems.clear()
+ selectedItems.addAll(temp)
+ }
+
+ fun remove(position: Int) {
+ this.values.removeAt(position)
+ this.notifyItemRemoved(position)
+
+ // keep selectedItems updated so we know what to notifyItemChanged
+ // (instead of just using notifyDataSetChanged)
+ updateSelectedItems(position, selectedItems)
+ }
+
+ internal open fun getOnLongClickListener(holder: ViewHolder, pass: PasswordItem): View.OnLongClickListener {
+ return View.OnLongClickListener { false }
+ }
+
+ // Replace the contents of a view (invoked by the layout manager)
+ @SuppressLint("SetTextI18n")
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val pass = values[position]
+ holder.name.text = pass.toString()
+ if (pass.type == PasswordItem.TYPE_CATEGORY) {
+ holder.typeImage.setImageResource(R.drawable.ic_folder_tinted_24dp)
+ } else {
+ holder.typeImage.setImageResource(R.drawable.ic_action_secure)
+ holder.name.text = pass.toString()
+ }
+
+ holder.type.text = pass.fullPathToParent.replace("(^/)|(/$)".toRegex(), "")
+
+ holder.view.setOnClickListener(getOnClickListener(holder, pass))
+
+ holder.view.setOnLongClickListener(getOnLongClickListener(holder, pass))
+
+ // after removal, everything is rebound for some reason; views are shuffled?
+ val selected = selectedItems.contains(position)
+ holder.view.isSelected = selected
+ (holder.itemView as MultiselectableLinearLayout).setMultiSelected(selected)
+ }
+
+ protected abstract fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener
+
+ // Create new views (invoked by the layout manager)
+ override fun onCreateViewHolder(parent: ViewGroup,
+ viewType: Int): ViewHolder {
+ // create a new view
+ val v = LayoutInflater.from(parent.context)
+ .inflate(R.layout.password_row_layout, parent, false)
+ return ViewHolder(v)
+ }
+
+ // Provide a reference to the views for each data item
+ // Complex data items may need more than one view per item, and
+ // you provide access to all the views for a data item in a view holder
+ class ViewHolder(// each data item is just a string in this case
+ val view: View) : RecyclerView.ViewHolder(view) {
+ val name: AppCompatTextView = view.findViewById(R.id.label)
+ val type: AppCompatTextView = view.findViewById(R.id.type)
+ val typeImage: AppCompatImageView = view.findViewById(R.id.type_image)
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
index a2be3ddc..7c2a28c3 100644
--- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt
@@ -1,5 +1,14 @@
package com.zeapo.pwdstore.utils
+import android.content.Context
+import android.util.TypedValue
+
fun String.splitLines(): Array<String> {
return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
}
+
+fun Context.resolveAttribute(attr: Int): Int {
+ val typedValue = TypedValue()
+ this.theme.resolveAttribute(attr, typedValue, true)
+ return typedValue.data
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java
deleted file mode 100644
index 1754e0f9..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.zeapo.pwdstore.utils;
-
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-import com.zeapo.pwdstore.SelectFolderActivity;
-import com.zeapo.pwdstore.SelectFolderFragment;
-
-import java.util.ArrayList;
-
-public class FolderRecyclerAdapter extends EntryRecyclerAdapter {
- private final SelectFolderFragment.OnFragmentInteractionListener listener;
-
- // Provide a suitable constructor (depends on the kind of dataset)
- public FolderRecyclerAdapter(SelectFolderActivity activity, SelectFolderFragment.OnFragmentInteractionListener listener, ArrayList<PasswordItem> values) {
- super(activity, values);
- this.listener = listener;
- }
-
- @NonNull
- protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
- return v -> {
- listener.onFragmentInteraction(pass);
- notifyItemChanged(holder.getAdapterPosition());
- };
- }
-
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.kt
new file mode 100644
index 00000000..4f0ca4c8
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/FolderRecyclerAdapter.kt
@@ -0,0 +1,20 @@
+package com.zeapo.pwdstore.utils
+
+import android.view.View
+
+import com.zeapo.pwdstore.SelectFolderFragment
+
+import java.util.ArrayList
+
+class FolderRecyclerAdapter(private val listener: SelectFolderFragment.OnFragmentInteractionListener,
+ values: ArrayList<PasswordItem>
+) : EntryRecyclerAdapter(values) {
+
+ override fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener {
+ return View.OnClickListener {
+ listener.onFragmentInteraction(pass)
+ notifyItemChanged(holder.adapterPosition)
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.java b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.java
deleted file mode 100644
index 1a498721..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.java
+++ /dev/null
@@ -1,110 +0,0 @@
-package com.zeapo.pwdstore.utils;
-
-import androidx.annotation.NonNull;
-
-import com.zeapo.pwdstore.crypto.PgpActivity;
-
-import java.io.File;
-
-public class PasswordItem implements Comparable {
-
- public final static char TYPE_CATEGORY = 'c';
- public final static char TYPE_PASSWORD = 'p';
-
- private final char type;
- private final String name;
- private final PasswordItem parent;
- private final File file;
- private final String fullPathToParent;
- private final String longName;
-
- /**
- * Create a password item
- * <p>
- * Make it protected so that we use a builder
- */
- private PasswordItem(String name, PasswordItem parent, char type, File file, File rootDir) {
- this.name = name;
- this.parent = parent;
- this.type = type;
- this.file = file;
- fullPathToParent = file.getAbsolutePath()
- .replace(rootDir.getAbsolutePath(), "")
- .replace(file.getName(), "");
- longName = PgpActivity.getLongName(fullPathToParent, rootDir.getAbsolutePath(), toString());
- }
-
- /**
- * Create a new Category item
- */
- public static PasswordItem newCategory(String name, File file, PasswordItem parent, File rootDir) {
- return new PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir);
- }
-
- /**
- * Create a new parentless category item
- */
- public static PasswordItem newCategory(String name, File file, File rootDir) {
- return new PasswordItem(name, null, TYPE_CATEGORY, file, rootDir);
- }
-
- /**
- * Create a new password item
- */
- public static PasswordItem newPassword(String name, File file, PasswordItem parent, File rootDir) {
- return new PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir);
- }
-
- /**
- * Create a new parentless password item
- */
- public static PasswordItem newPassword(String name, File file, File rootDir) {
- return new PasswordItem(name, null, TYPE_PASSWORD, file, rootDir);
- }
-
- public char getType() {
- return this.type;
- }
-
- String getName() {
- return this.name;
- }
-
- public PasswordItem getParent() {
- return this.parent;
- }
-
- public File getFile() {
- return this.file;
- }
-
- public String getFullPathToParent() {
- return this.fullPathToParent;
- }
-
- public String getLongName() {
- return longName;
- }
-
- @NonNull
- @Override
- public String toString() {
- return this.getName().replace(".gpg", "");
- }
-
- @Override
- public boolean equals(Object o) {
- // Makes it possible to have a category and a password with the same name
- return o != null
- && o.getClass() == PasswordItem.class
- && ((PasswordItem) o).getFile().equals(this.getFile());
- }
-
- @Override
- public int compareTo(@NonNull Object o) {
- PasswordItem other = (PasswordItem) o;
- // Appending the type will make the sort type dependent
- return (this.getType() + this.getName())
- .compareToIgnoreCase(other.getType() + other.getName());
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt
new file mode 100644
index 00000000..ddecd2da
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt
@@ -0,0 +1,81 @@
+package com.zeapo.pwdstore.utils
+
+import com.zeapo.pwdstore.crypto.PgpActivity
+import java.io.File
+
+data class PasswordItem(
+ val name: String,
+ val parent: PasswordItem? = null,
+ val type: Char,
+ val file: File,
+ val rootDir: File
+) : Comparable<PasswordItem> {
+ val fullPathToParent = file.absolutePath
+ .replace(rootDir.absolutePath, "")
+ .replace(file.name, "")
+
+ val longName = PgpActivity.getLongName(
+ fullPathToParent,
+ rootDir.absolutePath,
+ toString())
+
+ override fun equals(other: Any?): Boolean {
+ return (other is PasswordItem) && (other.file == file)
+ }
+
+ override fun compareTo(other: PasswordItem): Int {
+ return (type + name).compareTo(other.type + other.name, ignoreCase = true)
+ }
+
+ override fun toString(): String {
+ return name.replace(".gpg", "")
+ }
+
+ override fun hashCode(): Int {
+ return 0
+ }
+
+ companion object {
+ const val TYPE_CATEGORY = 'c'
+ const val TYPE_PASSWORD = 'p'
+
+ @JvmStatic
+ fun newCategory(
+ name: String,
+ file: File,
+ parent: PasswordItem,
+ rootDir: File
+ ): PasswordItem {
+ return PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir)
+ }
+
+ @JvmStatic
+ fun newCategory(
+ name: String,
+ file: File,
+ rootDir: File
+ ): PasswordItem {
+ return PasswordItem(name, null, TYPE_CATEGORY, file, rootDir)
+ }
+
+ @JvmStatic
+ fun newPassword(
+ name: String,
+ file: File,
+ parent: PasswordItem,
+ rootDir: File
+ ): PasswordItem {
+ return PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir)
+ }
+
+ @JvmStatic
+ fun newPassword(
+ name: String,
+ file: File,
+ rootDir: File
+ ): PasswordItem {
+ return PasswordItem(name, null, TYPE_PASSWORD, file, rootDir)
+ }
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java
deleted file mode 100644
index 9170e408..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.java
+++ /dev/null
@@ -1,133 +0,0 @@
-package com.zeapo.pwdstore.utils;
-
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.view.ActionMode;
-
-import com.zeapo.pwdstore.PasswordFragment;
-import com.zeapo.pwdstore.PasswordStore;
-import com.zeapo.pwdstore.R;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.TreeSet;
-
-public class PasswordRecyclerAdapter extends EntryRecyclerAdapter {
- private final PasswordStore activity;
- private final PasswordFragment.OnFragmentInteractionListener listener;
- public ActionMode mActionMode;
- private Boolean canEdit;
- private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
-
- // Called when the action mode is created; startActionMode() was called
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- // Inflate a menu resource providing context menu items
- mode.getMenuInflater().inflate(R.menu.context_pass, menu);
- // hide the fab
- activity.findViewById(R.id.fab).setVisibility(View.GONE);
- return true;
- }
-
- // Called each time the action mode is shown. Always called after onCreateActionMode, but
- // may be called multiple times if the mode is invalidated.
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- if (canEdit) {
- menu.findItem(R.id.menu_edit_password).setVisible(true);
- } else {
- menu.findItem(R.id.menu_edit_password).setVisible(false);
- }
- return true; // Return false if nothing is done
- }
-
- // Called when the user selects a contextual menu item
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_delete_password:
- activity.deletePasswords(PasswordRecyclerAdapter.this, new TreeSet<>(selectedItems));
- mode.finish(); // Action picked, so close the CAB
- return true;
- case R.id.menu_edit_password:
- activity.editPassword(getValues().get(selectedItems.iterator().next()));
- mode.finish();
- return true;
- case R.id.menu_move_password:
- ArrayList<PasswordItem> selectedPasswords = new ArrayList<>();
- for (Integer id : selectedItems) {
- selectedPasswords.add(getValues().get(id));
- }
- activity.movePasswords(selectedPasswords);
- default:
- return false;
- }
- }
-
- // Called when the user exits the action mode
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- for (Iterator<Integer> it = selectedItems.iterator(); it.hasNext(); ) {
- // need the setSelected line in onBind
- notifyItemChanged(it.next());
- it.remove();
- }
- mActionMode = null;
- // show the fab
- activity.findViewById(R.id.fab).setVisibility(View.VISIBLE);
- }
- };
-
- // Provide a suitable constructor (depends on the kind of dataset)
- public PasswordRecyclerAdapter(PasswordStore activity, PasswordFragment.OnFragmentInteractionListener listener, ArrayList<PasswordItem> values) {
- super(activity, values);
- this.activity = activity;
- this.listener = listener;
- }
-
- @Override
- @NonNull
- protected View.OnLongClickListener getOnLongClickListener(final ViewHolder holder, final PasswordItem pass) {
- return v -> {
- if (mActionMode != null) {
- return false;
- }
- toggleSelection(holder.getAdapterPosition());
- canEdit = pass.getType() == PasswordItem.TYPE_PASSWORD;
- // Start the CAB using the ActionMode.Callback
- mActionMode = activity.startSupportActionMode(mActionModeCallback);
- mActionMode.setTitle("" + selectedItems.size());
- mActionMode.invalidate();
- notifyItemChanged(holder.getAdapterPosition());
- return true;
- };
- }
-
- @Override
- @NonNull
- protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
- return v -> {
- if (mActionMode != null) {
- toggleSelection(holder.getAdapterPosition());
- mActionMode.setTitle("" + selectedItems.size());
- if (selectedItems.isEmpty()) {
- mActionMode.finish();
- } else if (selectedItems.size() == 1 && !canEdit) {
- if (getValues().get(selectedItems.iterator().next()).getType() == PasswordItem.TYPE_PASSWORD) {
- canEdit = true;
- mActionMode.invalidate();
- }
- } else if (selectedItems.size() >= 1 && canEdit) {
- canEdit = false;
- mActionMode.invalidate();
- }
- } else {
- listener.onFragmentInteraction(pass);
- }
- notifyItemChanged(holder.getAdapterPosition());
- };
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.kt
new file mode 100644
index 00000000..d7493a48
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRecyclerAdapter.kt
@@ -0,0 +1,116 @@
+package com.zeapo.pwdstore.utils
+
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import androidx.appcompat.view.ActionMode
+
+import com.zeapo.pwdstore.PasswordFragment
+import com.zeapo.pwdstore.PasswordStore
+import com.zeapo.pwdstore.R
+
+import java.util.ArrayList
+import java.util.TreeSet
+
+class PasswordRecyclerAdapter(private val activity: PasswordStore,
+ private val listener: PasswordFragment.OnFragmentInteractionListener,
+ values: ArrayList<PasswordItem>
+) : EntryRecyclerAdapter(values) {
+ var actionMode: ActionMode? = null
+ private var canEdit: Boolean = false
+ private val actionModeCallback = object : ActionMode.Callback {
+
+ // Called when the action mode is created; startActionMode() was called
+ override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
+ // Inflate a menu resource providing context menu items
+ mode.menuInflater.inflate(R.menu.context_pass, menu)
+ // hide the fab
+ activity.findViewById<View>(R.id.fab).visibility = View.GONE
+ return true
+ }
+
+ // Called each time the action mode is shown. Always called after onCreateActionMode, but
+ // may be called multiple times if the mode is invalidated.
+ override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
+ menu.findItem(R.id.menu_edit_password).isVisible = canEdit
+ return true // Return false if nothing is done
+ }
+
+ // Called when the user selects a contextual menu item
+ override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.menu_delete_password -> {
+ activity.deletePasswords(this@PasswordRecyclerAdapter, TreeSet(selectedItems))
+ mode.finish() // Action picked, so close the CAB
+ return true
+ }
+ R.id.menu_edit_password -> {
+ activity.editPassword(values[selectedItems.iterator().next()])
+ mode.finish()
+ return true
+ }
+ R.id.menu_move_password -> {
+ val selectedPasswords = ArrayList<PasswordItem>()
+ for (id in selectedItems) {
+ selectedPasswords.add(values[id])
+ }
+ activity.movePasswords(selectedPasswords)
+ return false
+ }
+ else -> return false
+ }
+ }
+
+ // Called when the user exits the action mode
+ override fun onDestroyActionMode(mode: ActionMode) {
+ val it = selectedItems.iterator()
+ while (it.hasNext()) {
+ // need the setSelected line in onBind
+ notifyItemChanged(it.next())
+ it.remove()
+ }
+ actionMode = null
+ // show the fab
+ activity.findViewById<View>(R.id.fab).visibility = View.VISIBLE
+ }
+ }
+
+ override fun getOnLongClickListener(holder: ViewHolder, pass: PasswordItem): View.OnLongClickListener {
+ return View.OnLongClickListener {
+ if (actionMode != null) {
+ return@OnLongClickListener false
+ }
+ toggleSelection(holder.adapterPosition)
+ canEdit = pass.type == PasswordItem.TYPE_PASSWORD
+ // Start the CAB using the ActionMode.Callback
+ actionMode = activity.startSupportActionMode(actionModeCallback)
+ actionMode?.title = "" + selectedItems.size
+ actionMode?.invalidate()
+ notifyItemChanged(holder.adapterPosition)
+ true
+ }
+ }
+
+ override fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener {
+ return View.OnClickListener {
+ if (actionMode != null) {
+ toggleSelection(holder.adapterPosition)
+ actionMode?.title = "" + selectedItems.size
+ if (selectedItems.isEmpty()) {
+ actionMode?.finish()
+ } else if (selectedItems.size == 1 && (canEdit.not())) {
+ if (values[selectedItems.iterator().next()].type == PasswordItem.TYPE_PASSWORD) {
+ canEdit = true
+ actionMode?.invalidate()
+ }
+ } else if (selectedItems.size >= 1 && canEdit) {
+ canEdit = false
+ actionMode?.invalidate()
+ }
+ } else {
+ listener.onFragmentInteraction(pass)
+ }
+ notifyItemChanged(holder.adapterPosition)
+ }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.java b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.java
deleted file mode 100644
index 9c27a448..00000000
--- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.java
+++ /dev/null
@@ -1,284 +0,0 @@
-package com.zeapo.pwdstore.utils;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.util.Log;
-
-import org.apache.commons.io.filefilter.FileFilterUtils;
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
-import org.eclipse.jgit.transport.RefSpec;
-import org.eclipse.jgit.transport.RemoteConfig;
-import org.eclipse.jgit.transport.URIish;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
-
-import static java.util.Collections.sort;
-
-public class PasswordRepository {
-
- private static Repository repository;
-
- protected PasswordRepository() {
- }
-
- /**
- * Returns the git repository
- *
- * @param localDir needed only on the creation
- * @return the git repository
- */
- public static Repository getRepository(File localDir) {
- if (repository == null && localDir != null) {
- FileRepositoryBuilder builder = new FileRepositoryBuilder();
- try {
- repository = builder.setGitDir(localDir)
- .readEnvironment()
- .build();
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- return repository;
- }
-
- public static boolean isInitialized() {
- return repository != null;
- }
-
- public static void createRepository(File localDir) throws Exception {
- localDir.delete();
-
- Git.init().setDirectory(localDir).call();
- getRepository(localDir);
- }
-
- // TODO add multiple remotes support for pull/push
- public static void addRemote(String name, String url, Boolean replace) {
- StoredConfig storedConfig = repository.getConfig();
- Set<String> remotes = storedConfig.getSubsections("remote");
-
- if (!remotes.contains(name)) {
- try {
- URIish uri = new URIish(url);
- RefSpec refSpec = new RefSpec("+refs/head/*:refs/remotes/" + name + "/*");
-
- RemoteConfig remoteConfig = new RemoteConfig(storedConfig, name);
- remoteConfig.addFetchRefSpec(refSpec);
- remoteConfig.addPushRefSpec(refSpec);
- remoteConfig.addURI(uri);
- remoteConfig.addPushURI(uri);
-
- remoteConfig.update(storedConfig);
-
- storedConfig.save();
- } catch (Exception e) {
- e.printStackTrace();
- }
- } else if (replace) {
- try {
- URIish uri = new URIish(url);
-
- RemoteConfig remoteConfig = new RemoteConfig(storedConfig, name);
- // remove the first and eventually the only uri
- if (remoteConfig.getURIs().size() > 0) {
- remoteConfig.removeURI(remoteConfig.getURIs().get(0));
- }
- if (remoteConfig.getPushURIs().size() > 0) {
- remoteConfig.removePushURI(remoteConfig.getPushURIs().get(0));
- }
-
- remoteConfig.addURI(uri);
- remoteConfig.addPushURI(uri);
-
- remoteConfig.update(storedConfig);
-
- storedConfig.save();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- public static void closeRepository() {
- if (repository != null) repository.close();
- repository = null;
- }
-
- public static File getRepositoryDirectory(Context context) {
- File dir = null;
- SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
-
- if (settings.getBoolean("git_external", false)) {
- String external_repo = settings.getString("git_external_repo", null);
- if (external_repo != null) {
- dir = new File(external_repo);
- }
- } else {
- dir = new File(context.getFilesDir() + "/store");
- }
-
- return dir;
- }
-
- public static Repository initialize(Context context) {
- File dir = getRepositoryDirectory(context);
- SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
-
- if (dir == null) {
- return null;
- }
-
- // uninitialize the repo if the dir does not exist or is absolutely empty
- if (!dir.exists() || !dir.isDirectory() || dir.listFiles().length == 0) {
- settings.edit().putBoolean("repository_initialized", false).apply();
- } else {
- settings.edit().putBoolean("repository_initialized", true).apply();
- }
-
- // create the repository static variable in PasswordRepository
- return PasswordRepository.getRepository(new File(dir.getAbsolutePath() + "/.git"));
- }
-
- /**
- * Gets the password items in the root directory
- *
- * @return a list of passwords in the root direcotyr
- */
- public static ArrayList<PasswordItem> getPasswords(File rootDir, PasswordSortOrder sortOrder) {
- return getPasswords(rootDir, rootDir, sortOrder);
- }
-
- /**
- * Gets the .gpg files in a directory
- *
- * @param path the directory path
- * @return the list of gpg files in that directory
- */
- public static ArrayList<File> getFilesList(File path) {
- if (path == null || !path.exists()) return new ArrayList<>();
-
- Log.d("REPO", "current path: " + path.getPath());
- List<File> directories = Arrays.asList(path.listFiles((FileFilter) FileFilterUtils.directoryFileFilter()));
- List<File> files = Arrays.asList(path.listFiles((FileFilter) FileFilterUtils.suffixFileFilter(".gpg")));
-
- ArrayList<File> items = new ArrayList<>();
- items.addAll(directories);
- items.addAll(files);
-
- return items;
- }
-
- /**
- * Gets the passwords (PasswordItem) in a directory
- *
- * @param path the directory path
- * @return a list of password items
- */
- public static ArrayList<PasswordItem> getPasswords(File path, File rootDir, PasswordSortOrder sortOrder) {
- //We need to recover the passwords then parse the files
- ArrayList<File> passList = getFilesList(path);
-
- if (passList.size() == 0) return new ArrayList<>();
-
- ArrayList<PasswordItem> passwordList = new ArrayList<>();
-
- for (File file : passList) {
- if (file.isFile()) {
- if (!file.isHidden()) {
- passwordList.add(PasswordItem.newPassword(file.getName(), file, rootDir));
- }
- } else {
- if (!file.isHidden()) {
- passwordList.add(PasswordItem.newCategory(file.getName(), file, rootDir));
- }
- }
- }
- sort(passwordList, sortOrder.comparator);
- return passwordList;
- }
-
- /**
- * Sets the git user name
- *
- * @param username username
- */
- public static void setUserName(String username) {
- setStringConfig("user", null, "name", username);
- }
-
- /**
- * Sets the git user email
- *
- * @param email email
- */
- public static void setUserEmail(String email) {
- setStringConfig("user", null, "email", email);
- }
-
- /**
- * Sets a git config value
- *
- * @param section config section name
- * @param subsection config subsection name
- * @param name config name
- * @param value the value to be set
- */
- private static void setStringConfig(String section, String subsection, String name, String value) {
- if (isInitialized()) {
- StoredConfig config = repository.getConfig();
- config.setString(section, subsection, name, value);
- try {
- config.save();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- public enum PasswordSortOrder {
-
- FOLDER_FIRST(new Comparator<PasswordItem>() {
- @Override
- public int compare(PasswordItem p1, PasswordItem p2) {
- return (p1.getType() + p1.getName())
- .compareToIgnoreCase(p2.getType() + p2.getName());
- }
- }),
-
- INDEPENDENT(new Comparator<PasswordItem>() {
- @Override
- public int compare(PasswordItem p1, PasswordItem p2) {
- return p1.getName().compareToIgnoreCase(p2.getName());
- }
- }),
-
- FILE_FIRST(new Comparator<PasswordItem>() {
- @Override
- public int compare(PasswordItem p1, PasswordItem p2) {
- return (p2.getType() + p1.getName())
- .compareToIgnoreCase(p1.getType() + p2.getName());
- }
- });
-
- private Comparator<PasswordItem> comparator;
-
- PasswordSortOrder(Comparator<PasswordItem> comparator) {
- this.comparator = comparator;
- }
-
- public static PasswordSortOrder getSortOrder(SharedPreferences settings) {
- return valueOf(settings.getString("sort_order", FOLDER_FIRST.name()));
- }
- }
-}
diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt
new file mode 100644
index 00000000..9e94e400
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt
@@ -0,0 +1,282 @@
+package com.zeapo.pwdstore.utils
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.preference.PreferenceManager
+import org.apache.commons.io.filefilter.FileFilterUtils
+import org.eclipse.jgit.api.Git
+import org.eclipse.jgit.lib.Repository
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder
+import org.eclipse.jgit.transport.RefSpec
+import org.eclipse.jgit.transport.RemoteConfig
+import org.eclipse.jgit.transport.URIish
+import java.io.File
+import java.io.FileFilter
+import java.util.Comparator
+
+open class PasswordRepository protected constructor() {
+
+ @Suppress("Unused")
+ enum class PasswordSortOrder(val comparator: Comparator<PasswordItem>) {
+
+ FOLDER_FIRST(Comparator { p1: PasswordItem, p2: PasswordItem ->
+ (p1.type + p1.name)
+ .compareTo(p2.type + p2.name, ignoreCase = true)
+ }),
+
+ INDEPENDENT(Comparator { p1: PasswordItem, p2: PasswordItem ->
+ p1.name.compareTo(p2.name, ignoreCase = true)
+ }),
+
+ FILE_FIRST(Comparator { p1: PasswordItem, p2: PasswordItem ->
+ (p2.type + p1.name).compareTo(p1.type + p2.name, ignoreCase = true)
+ });
+
+
+ companion object {
+ @JvmStatic
+ fun getSortOrder(settings: SharedPreferences): PasswordSortOrder {
+ return valueOf(settings.getString("sort_order", null) ?: FOLDER_FIRST.name)
+ }
+ }
+ }
+
+ companion object {
+
+ private var repository: Repository? = null
+
+ /**
+ * Returns the git repository
+ *
+ * @param localDir needed only on the creation
+ * @return the git repository
+ */
+ @JvmStatic
+ fun getRepository(localDir: File?): Repository? {
+ if (repository == null && localDir != null) {
+ val builder = FileRepositoryBuilder()
+ try {
+ repository = builder.setGitDir(localDir)
+ .readEnvironment()
+ .build()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ return null
+ }
+
+ }
+ return repository
+ }
+
+ @JvmStatic
+ val isInitialized: Boolean
+ get() = repository != null
+
+ @JvmStatic
+ @Throws(Exception::class)
+ fun createRepository(localDir: File) {
+ localDir.delete()
+
+ Git.init().setDirectory(localDir).call()
+ getRepository(localDir)
+ }
+
+ // TODO add multiple remotes support for pull/push
+ @JvmStatic
+ fun addRemote(name: String, url: String, replace: Boolean?) {
+ val storedConfig = repository!!.config
+ val remotes = storedConfig.getSubsections("remote")
+
+ if (!remotes.contains(name)) {
+ try {
+ val uri = URIish(url)
+ val refSpec = RefSpec("+refs/head/*:refs/remotes/$name/*")
+
+ val remoteConfig = RemoteConfig(storedConfig, name)
+ remoteConfig.addFetchRefSpec(refSpec)
+ remoteConfig.addPushRefSpec(refSpec)
+ remoteConfig.addURI(uri)
+ remoteConfig.addPushURI(uri)
+
+ remoteConfig.update(storedConfig)
+
+ storedConfig.save()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+ } else if (replace!!) {
+ try {
+ val uri = URIish(url)
+
+ val remoteConfig = RemoteConfig(storedConfig, name)
+ // remove the first and eventually the only uri
+ if (remoteConfig.urIs.size > 0) {
+ remoteConfig.removeURI(remoteConfig.urIs[0])
+ }
+ if (remoteConfig.pushURIs.size > 0) {
+ remoteConfig.removePushURI(remoteConfig.pushURIs[0])
+ }
+
+ remoteConfig.addURI(uri)
+ remoteConfig.addPushURI(uri)
+
+ remoteConfig.update(storedConfig)
+
+ storedConfig.save()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+ }
+ }
+
+ @JvmStatic
+ fun closeRepository() {
+ if (repository != null) repository!!.close()
+ repository = null
+ }
+
+ @JvmStatic
+ fun getRepositoryDirectory(context: Context): File? {
+ var dir: File? = null
+ val settings = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
+
+ if (settings.getBoolean("git_external", false)) {
+ val externalRepo = settings.getString("git_external_repo", null)
+ if (externalRepo != null) {
+ dir = File(externalRepo)
+ }
+ } else {
+ dir = File(context.filesDir.toString() + "/store")
+ }
+
+ return dir
+ }
+
+ @JvmStatic
+ fun initialize(context: Context): Repository? {
+ val dir = getRepositoryDirectory(context)
+ val settings = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
+
+ if (dir == null) {
+ return null
+ }
+
+ // uninitialize the repo if the dir does not exist or is absolutely empty
+ if (!dir.exists() || !dir.isDirectory || dir.listFiles()!!.isEmpty()) {
+ settings.edit().putBoolean("repository_initialized", false).apply()
+ } else {
+ settings.edit().putBoolean("repository_initialized", true).apply()
+ }
+
+ // create the repository static variable in PasswordRepository
+ return getRepository(File(dir.absolutePath + "/.git"))
+ }
+
+ /**
+ * Gets the password items in the root directory
+ *
+ * @return a list of passwords in the root direcotyr
+ */
+ @JvmStatic
+ fun getPasswords(rootDir: File, sortOrder: PasswordSortOrder): ArrayList<PasswordItem> {
+ return getPasswords(rootDir, rootDir, sortOrder)
+ }
+
+ /**
+ * Gets the .gpg files in a directory
+ *
+ * @param path the directory path
+ * @return the list of gpg files in that directory
+ */
+ @JvmStatic
+ fun getFilesList(path: File?): ArrayList<File> {
+ if (path == null || !path.exists()) return ArrayList()
+
+ val directories = (path.listFiles(FileFilterUtils.directoryFileFilter() as FileFilter)
+ ?: emptyArray()).toList()
+ val files = (path.listFiles(FileFilterUtils.suffixFileFilter(".gpg") as FileFilter)
+ ?: emptyArray()).toList()
+
+ val items = ArrayList<File>()
+ items.addAll(directories)
+ items.addAll(files)
+
+ return items
+ }
+
+ /**
+ * Gets the passwords (PasswordItem) in a directory
+ *
+ * @param path the directory path
+ * @return a list of password items
+ */
+ @JvmStatic
+ fun getPasswords(path: File, rootDir: File, sortOrder: PasswordSortOrder): ArrayList<PasswordItem> {
+ //We need to recover the passwords then parse the files
+ val passList = getFilesList(path)
+
+ if (passList.size == 0) return ArrayList()
+
+ val passwordList = ArrayList<PasswordItem>()
+
+ for (file in passList) {
+ if (file.isFile) {
+ if (!file.isHidden) {
+ passwordList.add(PasswordItem.newPassword(file.name, file, rootDir))
+ }
+ } else {
+ if (!file.isHidden) {
+ passwordList.add(PasswordItem.newCategory(file.name, file, rootDir))
+ }
+ }
+ }
+ passwordList.sortWith(sortOrder.comparator)
+ return passwordList
+ }
+
+ /**
+ * Sets the git user name
+ *
+ * @param username username
+ */
+ @JvmStatic
+ fun setUserName(username: String) {
+ setStringConfig("user", null, "name", username)
+ }
+
+ /**
+ * Sets the git user email
+ *
+ * @param email email
+ */
+ @JvmStatic
+ fun setUserEmail(email: String) {
+ setStringConfig("user", null, "email", email)
+ }
+
+ /**
+ * Sets a git config value
+ *
+ * @param section config section name
+ * @param subsection config subsection name
+ * @param name config name
+ * @param value the value to be set
+ */
+ @JvmStatic
+ @Suppress("SameParameterValue")
+ private fun setStringConfig(section: String, subsection: String?, name: String, value: String) {
+ if (isInitialized) {
+ val config = repository!!.config
+ config.setString(section, subsection, name, value)
+ try {
+ config.save()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/widget/MultiselectableLinearLayout.kt b/app/src/main/java/com/zeapo/pwdstore/widget/MultiselectableLinearLayout.kt
new file mode 100644
index 00000000..7ceea1db
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/widget/MultiselectableLinearLayout.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright © 2017-2018 WireGuard LLC.
+ * Copyright © 2018-2019 Harsh Shandilya <msfjarvis@gmail.com>. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.zeapo.pwdstore.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.LinearLayout
+import com.zeapo.pwdstore.R
+
+class MultiselectableLinearLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
+ private var multiselected: Boolean = false
+
+ override fun onCreateDrawableState(extraSpace: Int): IntArray {
+ if (multiselected) {
+ val drawableState = super.onCreateDrawableState(extraSpace + 1)
+ View.mergeDrawableStates(drawableState, STATE_MULTISELECTED)
+ return drawableState
+ }
+ return super.onCreateDrawableState(extraSpace)
+ }
+
+ fun setMultiSelected(on: Boolean) {
+ if (!multiselected) {
+ multiselected = true
+ refreshDrawableState()
+ }
+ isActivated = on
+ }
+
+ fun setSingleSelected(on: Boolean) {
+ if (multiselected) {
+ multiselected = false
+ refreshDrawableState()
+ }
+ isActivated = on
+ }
+
+ companion object {
+ private val STATE_MULTISELECTED = intArrayOf(R.attr.state_multiselected)
+ }
+} \ No newline at end of file
diff --git a/app/src/main/res/color/text_color.xml b/app/src/main/res/color/text_color.xml
deleted file mode 100644
index 9e70185e..00000000
--- a/app/src/main/res/color/text_color.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android" >
- <item android:state_enabled="false" android:color="@color/grey_500"/>
- <item android:color="@color/blue_grey_900"/>
-</selector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/autofill_row_background.xml b/app/src/main/res/drawable/autofill_row_background.xml
deleted file mode 100644
index 05e887ca..00000000
--- a/app/src/main/res/drawable/autofill_row_background.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_selected="true" android:drawable="@color/blue_grey_200" />
- <item android:drawable="@color/grey_white_1000" />
-</selector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/bottom_line.xml b/app/src/main/res/drawable/bottom_line.xml
index 323b03e0..e67c96fa 100644
--- a/app/src/main/res/drawable/bottom_line.xml
+++ b/app/src/main/res/drawable/bottom_line.xml
@@ -2,13 +2,13 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
- <solid android:color="@color/blue_grey_500"/>
+ <solid android:color="?android:attr/textColor"/>
</shape>
</item>
<item android:bottom="2dp">
<shape android:shape="rectangle">
- <solid android:color="@android:color/white" />
+ <solid android:color="?android:attr/windowBackground" />
</shape>
</item>
</layer-list>
diff --git a/app/src/main/res/drawable/divider.xml b/app/src/main/res/drawable/divider.xml
index cf2134ff..23a88317 100644
--- a/app/src/main/res/drawable/divider.xml
+++ b/app/src/main/res/drawable/divider.xml
@@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:height="1dp" />
- <solid android:color="@color/grey_300" />
+ <solid android:color="?attr/colorPrimaryDark" />
</shape> \ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_action_secure.xml b/app/src/main/res/drawable/ic_action_secure.xml
index 8f7b7539..af182711 100644
--- a/app/src/main/res/drawable/ic_action_secure.xml
+++ b/app/src/main/res/drawable/ic_action_secure.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="@color/grey_600">
+ android:tint="?attr/passwordIconColor">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
diff --git a/app/src/main/res/drawable/ic_folder_grey600_24dp.xml b/app/src/main/res/drawable/ic_folder_tinted_24dp.xml
index dbd73db5..51380a42 100644
--- a/app/src/main/res/drawable/ic_folder_grey600_24dp.xml
+++ b/app/src/main/res/drawable/ic_folder_tinted_24dp.xml
@@ -3,7 +3,7 @@
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
- android:tint="@color/grey_600">
+ android:tint="?attr/passwordIconColor">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/>
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
index 318de001..4072a948 100644
--- a/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -1,9 +1,8 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt"
- android:width="108dp"
- android:height="108dp"
- android:viewportWidth="110.34687"
- android:viewportHeight="110.34687">
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="110.34687"
+ android:viewportHeight="110.34687">
<group android:translateX="24.828047"
android:translateY="24.828047">
<path
diff --git a/app/src/main/res/drawable/password_row_background.xml b/app/src/main/res/drawable/password_row_background.xml
new file mode 100644
index 00000000..6806b12f
--- /dev/null
+++ b/app/src/main/res/drawable/password_row_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item>
+ <selector>
+ <item app:state_multiselected="true" android:state_activated="true">
+ <color android:color="@color/list_multiselect_background" />
+ </item>
+ </selector>
+ </item>
+ <item android:drawable="?attr/selectableItemBackground" />
+</layer-list> \ No newline at end of file
diff --git a/app/src/main/res/drawable/red_rectangle.xml b/app/src/main/res/drawable/red_rectangle.xml
index a6c48d0f..8213a7e3 100644
--- a/app/src/main/res/drawable/red_rectangle.xml
+++ b/app/src/main/res/drawable/red_rectangle.xml
@@ -13,7 +13,7 @@
<item>
<shape android:shape="rectangle" android:dither="true">
<corners android:radius="2dp" />
- <solid android:color="@android:color/holo_red_light" />
+ <solid android:color="#FF0000" />
<padding android:bottom="8dp"
android:left="8dp"
diff --git a/app/src/main/res/layout/activity_git_clone.xml b/app/src/main/res/layout/activity_git_clone.xml
index c96a6201..c4612765 100644
--- a/app/src/main/res/layout/activity_git_clone.xml
+++ b/app/src/main/res/layout/activity_git_clone.xml
@@ -1,211 +1,187 @@
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin"
- android:paddingTop="@dimen/activity_vertical_margin"
- android:paddingBottom="@dimen/activity_vertical_margin"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:padding="@dimen/activity_horizontal_margin"
tools:context="com.zeapo.pwdstore.git.GitActivity"
- android:background="@android:color/white">
-
- <LinearLayout
+ android:background="?android:attr/windowBackground">
+
+ <androidx.appcompat.widget.AppCompatTextView
+ style="@style/TextAppearance.MaterialComponents.Headline5"
+ android:id="@+id/server_label"
+ android:textStyle="bold"
+ android:textSize="24sp"
+ android:text="@string/server_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_margin="8dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/server_name"
- android:textStyle="bold"
- style="@android:style/TextAppearance.Large"
- android:gravity="start"
- android:paddingBottom="6dp"
- android:textColor="@color/blue_grey_500"
- android:background="@drawable/bottom_line"/>
-
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="center_vertical">
-
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/server_protocol"
- android:id="@+id/label_server_protocol"
- android:layout_centerVertical="true"
- android:layout_alignParentStart="true" />
-
- <Spinner
- android:id="@+id/clone_protocol"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_toEndOf="@+id/label_server_protocol" />
- </RelativeLayout>
-
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="center_vertical">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/server_user"
- android:id="@+id/label_server_user"
- android:layout_centerVertical="true"
- android:paddingBottom="8dp"
- android:layout_alignParentStart="true" />
-
- <EditText
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:hint="@string/server_user_hint"
- android:id="@+id/server_user"
- android:layout_marginStart="8dp"
- android:layout_toEndOf="@+id/label_server_user"
- android:layout_alignParentEnd="true"
- android:inputType="textWebEmailAddress"/>
- </RelativeLayout>
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="center_vertical">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/server_url"
- android:paddingBottom="8dp"
- android:id="@+id/label_server_url"
- android:layout_centerVertical="true"
- android:layout_alignParentStart="true" />
-
- <EditText
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:hint="@string/server_url_hint"
- android:id="@+id/server_url"
- android:layout_marginStart="8dp"
- android:layout_toEndOf="@+id/label_server_url"
- android:layout_toStartOf="@+id/label_server_port"
- android:inputType="textWebEmailAddress"
- tools:ignore="TextFields" />
-
- <TextView
- android:id="@+id/label_server_port"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_toStartOf="@+id/server_port"
- android:paddingBottom="8dp"
- android:text=":"
- tools:ignore="HardcodedText" />
-
- <EditText
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:hint="@string/server_port_hint"
- android:id="@+id/server_port"
- android:layout_alignParentEnd="true"
- android:inputType="number"/>
-
- </RelativeLayout>
-
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="center_vertical">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/server_path"
- android:paddingBottom="8dp"
- android:id="@+id/label_server_path"
- android:layout_centerVertical="true"
- android:layout_alignParentStart="true" />
-
- <EditText
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:hint="@string/server_path_hint"
- android:layout_marginStart="8dp"
- android:id="@+id/server_path"
- android:layout_toEndOf="@+id/label_server_path"
- android:layout_alignParentEnd="true"
- android:inputType="textWebEmailAddress"/>
- </RelativeLayout>
-
-
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/server_resulting_url"
- android:textStyle="bold"
- style="@android:style/TextAppearance.Large"
- android:gravity="start"
- android:paddingBottom="6dp"
- android:textColor="@color/blue_grey_500"
- android:background="@drawable/bottom_line"/>
-
- <EditText
- android:id="@+id/clone_uri"
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/label_server_protocol"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/server_protocol"
+ android:layout_margin="8dp"
+ app:layout_constraintTop_toBottomOf="@id/server_label"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <Spinner
+ android:id="@+id/clone_protocol"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ app:layout_constraintTop_toBottomOf="@id/server_label"
+ app:layout_constraintStart_toEndOf="@id/label_server_protocol" />
+
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
+ android:id="@+id/server_user_layout"
+ android:hint="@string/server_user"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ app:hintEnabled="true"
+ app:layout_constraintTop_toBottomOf="@id/label_server_protocol">
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/server_user"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="@string/repository_uri"
- android:inputType="textWebEmailAddress"
- tools:ignore="TextFields" />
+ android:inputType="textWebEmailAddress" />
+ </com.google.android.material.textfield.TextInputLayout>
- <TextView
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
+ android:id="@+id/label_server_url"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:hint="@string/server_url"
+ app:hintEnabled="true"
+ app:layout_constraintTop_toBottomOf="@id/server_user_layout"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/label_server_port">
+
+ <com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@drawable/red_rectangle"
- android:textColor="@android:color/white"
- android:visibility="gone"
- android:id="@+id/warn_url"/>
+ android:id="@+id/server_url"
+ android:inputType="textWebEmailAddress" />
- <RelativeLayout
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
+ android:id="@+id/label_server_port"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:hint="@string/server_port_hint"
+ app:hintEnabled="true"
+ app:layout_constraintStart_toEndOf="@id/label_server_url"
+ app:layout_constraintTop_toBottomOf="@id/server_user_layout"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintDimensionRatio="1:0.8">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/server_port"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:layout_gravity="center_vertical">
-
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/connection_mode"
- android:id="@+id/label_connection_mode"
- android:layout_centerVertical="true"
- android:layout_alignParentStart="true" />
-
- <Spinner
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/connection_mode"
- android:layout_toEndOf="@+id/label_connection_mode" />
- </RelativeLayout>
-
- <Button
- android:id="@+id/clone_button"
- android:text="@string/clone_button"
+ android:inputType="number" />
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
+ android:id="@+id/label_server_path"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:hint="@string/server_path"
+ app:hintEnabled="true"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/label_server_url">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/server_path"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:onClick="cloneRepository"/>
- <Button
- android:id="@+id/save_button"
- android:text="@string/crypto_save"
+ android:inputType="textWebEmailAddress"/>
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
+ android:id="@+id/label_clone_uri"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/repository_uri"
+ android:editable="false"
+ android:layout_margin="8dp"
+ app:hintEnabled="true"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/label_server_path">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/clone_uri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:onClick="saveConfiguration"/>
- </LinearLayout>
+ android:inputType="textWebEmailAddress"/>
-</ScrollView>
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/red_rectangle"
+ android:textColor="@android:color/white"
+ android:visibility="gone"
+ android:id="@+id/warn_url"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/label_clone_uri"/>
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/label_connection_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/connection_mode"
+ android:layout_margin="16dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/warn_url" />
+
+ <Spinner
+ android:id="@+id/connection_mode"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="16dp"
+ app:layout_constraintTop_toBottomOf="@id/warn_url"
+ app:layout_constraintStart_toEndOf="@id/label_connection_mode" />
+
+ <com.google.android.material.button.MaterialButton
+ style="@style/Widget.MaterialComponents.Button"
+ android:id="@+id/clone_button"
+ android:text="@string/clone_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:onClick="cloneRepository"
+ android:textColor="?android:attr/windowBackground"
+ android:layout_marginTop="8dp"
+ app:backgroundTint="?attr/colorSecondary"
+ app:layout_constraintTop_toBottomOf="@id/label_connection_mode"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <com.google.android.material.button.MaterialButton
+ style="@style/Widget.MaterialComponents.Button"
+ android:id="@+id/save_button"
+ android:text="@string/crypto_save"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:onClick="saveConfiguration"
+ android:textColor="?android:attr/windowBackground"
+ android:layout_marginTop="8dp"
+ app:backgroundTint="?attr/colorSecondary"
+ app:layout_constraintTop_toBottomOf="@id/label_connection_mode"
+ app:layout_constraintStart_toStartOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/activity_git_config.xml b/app/src/main/res/layout/activity_git_config.xml
index 2283353b..bb2eac8b 100644
--- a/app/src/main/res/layout/activity_git_config.xml
+++ b/app/src/main/res/layout/activity_git_config.xml
@@ -1,143 +1,119 @@
<androidx.constraintlayout.widget.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:id="@+id/scrollView2"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@android:color/white"
+ android:padding="@dimen/activity_horizontal_margin"
+ android:background="?android:attr/windowBackground"
tools:context="com.zeapo.pwdstore.git.GitActivity"
tools:layout_editor_absoluteX="0dp"
tools:layout_editor_absoluteY="81dp">
-
- <TextView
- android:id="@+id/textView5"
- style="@android:style/TextAppearance.Large"
- android:layout_width="344dp"
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/username_input_layout"
+ style="@style/TextInputLayoutBase"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
- android:layout_marginTop="8dp"
- android:background="@drawable/bottom_line"
- android:gravity="start"
- android:paddingBottom="6dp"
- android:text="@string/git_config"
- android:textColor="@color/blue_grey_500"
- android:textStyle="bold"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
-
- <TextView
- android:id="@+id/label_git_user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
- android:paddingBottom="8dp"
- android:text="@string/git_user_name"
- app:layout_constraintBaseline_toBaselineOf="@+id/git_user_name"
- app:layout_constraintStart_toStartOf="parent" />
-
- <EditText
- android:id="@+id/git_user_name"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_marginStart="8dp"
- android:layout_marginTop="8dp"
+ android:layout_margin="8dp"
android:hint="@string/git_user_name_hint"
- android:inputType="textWebEmailAddress"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toEndOf="@+id/label_git_user_name"
- app:layout_constraintTop_toBottomOf="@+id/textView5" />
+ app:hintEnabled="true"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ tools:layout_editor_absoluteY="64dp">
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/git_user_name"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textNoSuggestions|textVisiblePassword" />
- <TextView
- android:id="@+id/label_git_user_email"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:paddingBottom="8dp"
- android:text="@string/git_user_email"
- app:layout_constraintBaseline_toBaselineOf="@+id/git_user_email"
- app:layout_constraintEnd_toEndOf="@+id/label_git_user_name"
- app:layout_constraintStart_toStartOf="@+id/label_git_user_name" />
+ </com.google.android.material.textfield.TextInputLayout>
- <EditText
- android:id="@+id/git_user_email"
- android:layout_width="0dp"
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
+ android:id="@+id/email_input_layout"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_marginTop="8dp"
- android:hint="@string/git_user_email_hint"
- android:inputType="textWebEmailAddress"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintStart_toStartOf="@+id/git_user_name"
- app:layout_constraintTop_toBottomOf="@+id/git_user_name" />
-
-
- <Button
+ android:layout_margin="8dp"
+ android:hint="@string/git_user_email"
+ app:hintEnabled="true"
+ app:layout_constraintTop_toBottomOf="@id/username_input_layout"
+ app:layout_constraintStart_toStartOf="parent">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/git_user_email"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textEmailAddress" />
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.google.android.material.button.MaterialButton
+ style="@style/Widget.MaterialComponents.Button"
android:id="@+id/save_button"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_marginTop="8dp"
- android:onClick="applyGitConfigs"
+ android:layout_margin="8dp"
android:text="@string/crypto_save"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/git_user_email" />
+ android:onClick="applyGitConfigs"
+ android:textColor="?android:attr/windowBackground"
+ app:backgroundTint="?attr/colorSecondary"
+ app:layout_constraintTop_toBottomOf="@id/email_input_layout"
+ app:layout_constraintEnd_toEndOf="parent"/>
- <TextView
- android:id="@+id/textView6"
- style="@android:style/TextAppearance.Large"
- android:layout_width="344dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:background="@drawable/bottom_line"
- android:gravity="start"
- android:paddingBottom="6dp"
- android:text="@string/hackish_tools"
- android:textColor="@color/blue_grey_500"
+ <androidx.appcompat.widget.AppCompatTextView
+ style="@style/TextAppearance.MaterialComponents.Headline5"
android:textStyle="bold"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/save_button" />
-
- <Button
- android:id="@+id/git_abort_rebase"
- android:layout_width="0dp"
+ android:textSize="24sp"
+ android:id="@+id/git_tools_title"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginEnd="8dp"
- android:layout_marginStart="8dp"
- android:layout_marginTop="8dp"
- android:onClick="abortRebase"
- android:text="@string/abort_rebase"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/textView7" />
+ android:layout_margin="8dp"
+ android:text="@string/hackish_tools"
+ app:layout_constraintTop_toBottomOf="@id/save_button"
+ app:layout_constraintStart_toStartOf="parent" />
- <TextView
- android:id="@+id/textView7"
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/commit_hash_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
- android:layout_marginTop="8dp"
+ android:layout_margin="8dp"
android:text="@string/commit_hash"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/textView6" />
+ app:layout_constraintTop_toBottomOf="@id/git_tools_title"
+ app:layout_constraintStart_toStartOf="parent"/>
- <TextView
+ <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/git_commit_hash"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="16dp"
- android:text="HASH"
+ android:layout_margin="8dp"
android:textStyle="bold"
- app:layout_constraintBaseline_toBaselineOf="@+id/textView7"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toRightOf="parent"
- app:layout_constraintStart_toEndOf="@+id/textView7"
- tools:ignore="HardcodedText" />
+ app:layout_constraintTop_toBottomOf="@id/git_tools_title"
+ app:layout_constraintStart_toEndOf="@id/commit_hash_label"
+ tools:text="HASH"/>
+ <com.google.android.material.button.MaterialButton
+ style="@style/Widget.MaterialComponents.Button"
+ android:id="@+id/git_abort_rebase"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:text="@string/abort_rebase"
+ android:onClick="abortRebase"
+ android:textColor="?android:attr/windowBackground"
+ app:backgroundTint="?attr/colorSecondary"
+ app:layout_constraintTop_toBottomOf="@id/commit_hash_label" />
+
+ <com.google.android.material.button.MaterialButton
+ style="@style/Widget.MaterialComponents.Button"
+ android:id="@+id/git_reset_to_remote"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:text="@string/reset_to_remote"
+ android:onClick="resetToRemote"
+ android:textColor="?android:attr/windowBackground"
+ app:backgroundTint="?attr/colorSecondary"
+ app:layout_constraintTop_toBottomOf="@id/git_abort_rebase" />
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/layout/activity_pwdstore.xml b/app/src/main/res/layout/activity_pwdstore.xml
index dd30d3fc..e76b88a1 100644
--- a/app/src/main/res/layout/activity_pwdstore.xml
+++ b/app/src/main/res/layout/activity_pwdstore.xml
@@ -1,4 +1,4 @@
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -11,4 +11,4 @@
android:layout_height="match_parent"
android:orientation="vertical"/>
-</RelativeLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/autofill_instructions.xml b/app/src/main/res/layout/autofill_instructions.xml
index 333cd5fa..d75320d3 100644
--- a/app/src/main/res/layout/autofill_instructions.xml
+++ b/app/src/main/res/layout/autofill_instructions.xml
@@ -12,14 +12,13 @@
android:paddingRight="24dp"
android:paddingTop="20dp">
- <TextView
- android:id="@+id/textView"
+ <androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_autofill_enable_msg"
android:textSize="16sp" />
- <ImageView
+ <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -29,8 +28,7 @@
android:src="@drawable/autofill_ins_1"
android:contentDescription="@string/autofill_ins_1_hint" />
- <ImageView
- android:id="@+id/imageView2"
+ <androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
@@ -38,15 +36,13 @@
android:src="@drawable/autofill_ins_2"
android:contentDescription="@string/autofill_ins_2_hint" />
- <TextView
- android:id="@+id/textView3"
+ <androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_autofill_enable_msg2"
android:textSize="16sp" />
- <ImageView
- android:id="@+id/imageView3"
+ <androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent"
android:layout_height="114dp"
android:layout_marginBottom="8dp"
@@ -54,8 +50,7 @@
android:src="@drawable/autofill_ins_3"
android:contentDescription="@string/autofill_ins_3_hint" />
- <TextView
- android:id="@+id/textView4"
+ <androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pref_autofill_enable_msg3"
diff --git a/app/src/main/res/layout/autofill_recycler_view.xml b/app/src/main/res/layout/autofill_recycler_view.xml
index c0bbacdb..30b72c04 100644
--- a/app/src/main/res/layout/autofill_recycler_view.xml
+++ b/app/src/main/res/layout/autofill_recycler_view.xml
@@ -27,8 +27,8 @@
android:layout_gravity="bottom|end"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
- app:backgroundTint="@color/blue_grey_500"
- app:rippleColor="@color/blue_grey_50"
+ app:backgroundTint="?attr/colorSecondary"
+ app:rippleColor="?attr/colorSecondary"
app:borderWidth="0dp"
android:layout_margin="@dimen/fab_compat_margin"
android:layout_alignParentBottom="true"
diff --git a/app/src/main/res/layout/autofill_row_layout.xml b/app/src/main/res/layout/autofill_row_layout.xml
index e07a73d3..12354077 100644
--- a/app/src/main/res/layout/autofill_row_layout.xml
+++ b/app/src/main/res/layout/autofill_row_layout.xml
@@ -1,21 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
+<LinearLayout android:layout_width="match_parent"
android:layout_height="64dp"
- android:background="@drawable/autofill_row_background"
- android:orientation="vertical">
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ xmlns:android="http://schemas.android.com/apk/res/android">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:background="?android:attr/selectableItemBackground"
- android:gravity="center_vertical"
- android:orientation="horizontal"
- android:paddingLeft="@dimen/activity_horizontal_margin"
- android:paddingRight="@dimen/activity_horizontal_margin">
-
- <ImageView
+ <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/app_icon"
android:layout_width="48dp"
android:layout_height="48dp"
@@ -24,24 +17,21 @@
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginLeft="8dp"
+ android:layout_marginStart="8dp"
android:gravity="center_vertical"
- android:orientation="vertical"
- tools:ignore="RtlHardcoded">
+ android:orientation="vertical" >
- <TextView
+ <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
- <TextView
+ <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/secondary_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/grey_600" />
+ android:textColor="?android:attr/textColor" />
</LinearLayout>
- </LinearLayout>
-
</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/decrypt_layout.xml b/app/src/main/res/layout/decrypt_layout.xml
index 4536042a..17ab55ed 100644
--- a/app/src/main/res/layout/decrypt_layout.xml
+++ b/app/src/main/res/layout/decrypt_layout.xml
@@ -3,228 +3,251 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/background"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:background="?android:attr/windowBackground"
android:orientation="vertical"
tools:context="com.zeapo.pwdstore.crypto.PgpActivity">
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
- <LinearLayout
- android:layout_width="match_parent"
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_password_category_decrypt"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <TextView
- android:id="@+id/crypto_password_category_decrypt"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginStart="16dp"
- android:textColor="@color/grey_500"
- android:textIsSelectable="false"
- android:textSize="18sp"
- tools:text="CATEGORY HERE" />
-
- <TextView
- android:id="@+id/crypto_password_file"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/activity_horizontal_margin"
- android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
- android:textColor="@color/accent"
- android:textSize="24sp"
- android:textStyle="bold"
- tools:text="PASSWORD FILE NAME HERE" />
-
- <TextView
- android:id="@+id/crypto_password_last_changed"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginStart="16dp"
- android:textColor="@color/grey_500"
- android:textIsSelectable="false"
- android:textSize="18sp"
- tools:text="LAST CHANGED HERE" />
- </LinearLayout>
-
- <ImageView
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="16dp"
+ android:textColor="?android:attr/textColor"
+ android:textIsSelectable="false"
+ android:textSize="18sp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="CATEGORY HERE" />
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_password_file"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/activity_horizontal_margin"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
+ android:textColor="?attr/colorSecondary"
+ android:textSize="24sp"
+ android:textStyle="bold"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/crypto_password_category_decrypt"
+ tools:text="PASSWORD FILE NAME HERE" />
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_password_last_changed"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="16dp"
+ android:textColor="?android:attr/textColor"
+ android:textIsSelectable="false"
+ android:textSize="18sp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/crypto_password_file"
+ tools:text="LAST CHANGED HERE" />
+
+
+ <androidx.appcompat.widget.AppCompatImageView
+ android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:src="@drawable/divider"
+ app:layout_constraintTop_toBottomOf="@id/crypto_password_last_changed"
tools:ignore="ContentDescription" />
- <LinearLayout
+ <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/crypto_container_decrypt"
- android:layout_width="fill_parent"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_vertical_margin"
- android:orientation="vertical"
- android:visibility="invisible">
+ android:visibility="invisible"
+ tools:visibility="visible"
+ app:layout_constraintTop_toBottomOf="@id/divider">
- <GridLayout
- android:id="@+id/crypto_password_show_layout"
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_password_show_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/password"
+ android:textColor="?android:attr/textColor"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:textStyle="bold" />
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_password_show"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill"
+ android:gravity="bottom"
+ android:textColor="?android:attr/textColor"
+ app:layout_constraintStart_toEndOf="@id/crypto_password_show_label"
+ app:layout_constraintBaseline_toBaselineOf="@id/crypto_password_show_label"
+ android:typeface="monospace" />
+
+ <ProgressBar
+ android:id="@+id/pbLoading"
+ style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <TextView
- android:id="@+id/crypto_password_show_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_column="0"
- android:layout_row="0"
- android:text="@string/password"
- android:textColor="@android:color/black"
- android:textStyle="bold" />
-
- <TextView
- android:id="@+id/crypto_password_show"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_column="2"
- android:layout_gravity="fill"
- android:gravity="bottom"
- android:layout_row="0"
- android:textColor="@android:color/black"
- android:typeface="monospace" />
-
- <ProgressBar
- android:id="@+id/pbLoading"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="match_parent"
- android:layout_height="8dp"
- android:layout_column="0"
- android:layout_columnSpan="3"
- android:layout_marginBottom="8dp"
- android:layout_marginTop="8dp"
- android:layout_row="1" />
-
- <Button
- android:id="@+id/crypto_password_toggle_show"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_column="0"
- android:layout_columnSpan="3"
- android:layout_row="2"
- android:text="@string/show_password" />
- </GridLayout>
-
-
- <RelativeLayout
- android:id="@+id/crypto_extra_show_layout"
+ android:layout_height="8dp"
+ android:layout_marginBottom="8dp"
+ android:visibility="invisible"
+ android:layout_marginTop="8dp"
+ app:layout_constraintTop_toBottomOf="@id/crypto_password_show_label"/>
+
+ <com.google.android.material.button.MaterialButton
+ style="@style/Widget.MaterialComponents.Button"
+ android:id="@+id/crypto_password_toggle_show"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textColor="?android:attr/windowBackground"
+ android:text="@string/show_password"
+ android:layout_marginTop="8dp"
+ app:layout_constraintTop_toBottomOf="@id/crypto_password_show_label"
+ app:backgroundTint="?attr/colorSecondary"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/crypto_extra_show_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="invisible"
+ app:layout_constraintTop_toBottomOf="@id/crypto_container_decrypt"
+ tools:visibility="visible">
+
+ <androidx.appcompat.widget.AppCompatImageButton
+ android:id="@+id/crypto_copy_username"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentTop="true"
+ android:background="?android:attr/windowBackground"
+ android:contentDescription="@string/copy_username"
+ android:src="@drawable/ic_content_copy"
+ android:visibility="invisible"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ tools:visibility="visible"/>
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_username_show_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:layout_toStartOf="@id/crypto_copy_username"
+ android:text="@string/username"
+ android:textColor="?android:attr/textColor"
+ android:textStyle="bold"
+ android:visibility="invisible"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:visibility="visible"/>
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_username_show"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/crypto_username_show_label"
+ android:layout_toStartOf="@id/crypto_copy_username"
+ android:textColor="?android:attr/textColor"
+ android:textIsSelectable="true"
+ android:typeface="monospace"
+ android:visibility="invisible"
+ app:layout_constraintTop_toBottomOf="@id/crypto_username_show_label"
+ app:layout_constraintStart_toStartOf="parent"
+ tools:visibility="visible"/>
+
+ <androidx.appcompat.widget.AppCompatImageView
+ android:id="@+id/crypto_copy_otp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_below="@id/crypto_username_show"
+ android:background="?android:attr/windowBackground"
+ android:contentDescription="@string/copy_otp"
+ android:src="@drawable/ic_content_copy"
+ android:visibility="invisible"
+ app:layout_constraintTop_toTopOf="@id/crypto_otp_show_label"
+ app:layout_constraintEnd_toEndOf="parent"
+ tools:visibility="visible"/>
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_otp_show_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/crypto_username_show"
+ android:layout_toStartOf="@id/crypto_copy_otp"
+ android:text="@string/otp"
+ android:textColor="?android:attr/textColor"
+ app:layout_constraintTop_toBottomOf="@id/crypto_username_show"
+ app:layout_constraintStart_toStartOf="parent"
+ android:textStyle="bold" />
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_otp_show"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/crypto_otp_show_label"
+ android:layout_toStartOf="@id/crypto_copy_otp"
+ android:textColor="?android:attr/textColor"
+ android:textIsSelectable="true"
+ app:layout_constraintTop_toBottomOf="@id/crypto_otp_show_label"
+ android:typeface="monospace" />
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_extra_show_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/crypto_otp_show"
+ android:text="@string/extra_content"
+ android:textColor="?android:attr/textColor"
+ app:layout_constraintTop_toBottomOf="@id/crypto_otp_show"
+ app:layout_constraintStart_toStartOf="parent"
+ android:textStyle="bold" />
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_extra_show"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:layout_below="@id/crypto_extra_show_label"
+ android:textColor="?android:attr/textColor"
+ android:textIsSelectable="true"
+ app:layout_constraintTop_toBottomOf="@id/crypto_extra_show_label"
+ android:typeface="monospace" />
+
+ <ToggleButton
+ android:id="@+id/crypto_extra_toggle_show"
+ style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:visibility="invisible">
-
- <ImageButton
- android:id="@+id/crypto_copy_username"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_alignParentTop="true"
- android:background="@color/background"
- android:contentDescription="@string/copy_username"
- android:src="@drawable/ic_content_copy"
- android:visibility="invisible" />
-
- <TextView
- android:id="@+id/crypto_username_show_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_alignParentTop="true"
- android:layout_toStartOf="@id/crypto_copy_username"
- android:text="@string/username"
- android:textColor="@android:color/black"
- android:textStyle="bold"
- android:visibility="invisible" />
-
- <TextView
- android:id="@+id/crypto_username_show"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/crypto_username_show_label"
- android:layout_toStartOf="@id/crypto_copy_username"
- android:textColor="@android:color/black"
- android:textIsSelectable="true"
- android:typeface="monospace"
- android:visibility="invisible" />
-
- <ImageButton
- android:id="@+id/crypto_copy_otp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_below="@id/crypto_username_show"
- android:background="@color/background"
- android:contentDescription="@string/copy_otp"
- android:src="@drawable/ic_content_copy"
- android:visibility="invisible" />
-
- <TextView
- android:id="@+id/crypto_otp_show_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/crypto_username_show"
- android:layout_toStartOf="@id/crypto_copy_otp"
- android:text="@string/otp"
- android:textColor="@android:color/black"
- android:textStyle="bold" />
-
- <TextView
- android:id="@+id/crypto_otp_show"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/crypto_otp_show_label"
- android:layout_toStartOf="@id/crypto_copy_otp"
- android:textColor="@android:color/black"
- android:textIsSelectable="true"
- android:typeface="monospace" />
-
- <TextView
- android:id="@+id/crypto_extra_show_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/crypto_otp_show"
- android:text="@string/extra_content"
- android:textColor="@android:color/black"
- android:textStyle="bold" />
-
- <TextView
- android:id="@+id/crypto_extra_show"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_below="@id/crypto_extra_show_label"
- android:textColor="@android:color/black"
- android:textIsSelectable="true"
- android:typeface="monospace" />
-
- <ToggleButton
- android:id="@+id/crypto_extra_toggle_show"
- style="@style/Widget.AppCompat.Button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/crypto_extra_show"
- android:layout_alignParentStart="true"
- android:checked="false"
- android:paddingTop="8dp"
- android:textOff="@string/show_extra"
- android:textOn="@string/hide_extra" />
-
- </RelativeLayout>
- </LinearLayout>
-
- </LinearLayout>
+ android:layout_below="@id/crypto_extra_show"
+ android:layout_alignParentStart="true"
+ android:checked="false"
+ android:paddingTop="8dp"
+ android:textColor="?android:attr/windowBackground"
+ android:textOff="@string/show_extra"
+ android:textOn="@string/hide_extra"
+ app:layout_constraintTop_toBottomOf="@id/crypto_extra_show"
+ android:backgroundTint="?attr/colorSecondary"/>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
diff --git a/app/src/main/res/layout/encrypt_layout.xml b/app/src/main/res/layout/encrypt_layout.xml
index f84792e8..41fa78ee 100644
--- a/app/src/main/res/layout/encrypt_layout.xml
+++ b/app/src/main/res/layout/encrypt_layout.xml
@@ -1,89 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/background"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:background="?android:attr/windowBackground"
android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin"
tools:context="com.zeapo.pwdstore.crypto.PgpActivity">
- <ScrollView
- android:id="@+id/crypto_scroll_view"
- android:layout_width="fill_parent"
- android:layout_height="0dp"
- android:layout_marginBottom="@dimen/activity_vertical_margin"
- android:layout_marginTop="@dimen/activity_vertical_margin"
- android:layout_weight="1">
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/crypto_password_category"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/activity_horizontal_margin"
+ android:textColor="?android:attr/textColor"
+ android:textIsSelectable="false"
+ android:textSize="18sp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="CATEGORY HERE" />
- <LinearLayout
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
+ android:id="@+id/name_input_layout"
+ android:layout_gravity="center_vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:hint="@string/crypto_name_hint"
+ app:hintEnabled="true"
+ app:layout_constraintTop_toBottomOf="@id/crypto_password_category"
+ app:layout_constraintStart_toStartOf="parent">
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/crypto_password_file_edit"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_height="wrap_content"/>
+ </com.google.android.material.textfield.TextInputLayout>
- <TextView
- android:id="@+id/crypto_password_category"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_marginStart="@dimen/activity_horizontal_margin"
- android:textColor="@color/grey_500"
- android:textIsSelectable="false"
- android:textSize="18sp"
- tools:text="CATEGORY HERE" />
-
- <EditText
- android:id="@+id/crypto_password_file_edit"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:hint="@string/crypto_name_hint"
- android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium"
- android:textColor="@color/accent"
- android:textSize="24sp"
- tools:ignore="TextFields" />
-
- <TextView
- android:id="@+id/textView2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/crypto_pass_label"
- android:textStyle="bold" />
-
- <EditText
- android:id="@+id/crypto_password_edit"
- android:layout_width="match_parent"
- android:hint="@string/crypto_password_edit_hint"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:inputType="textVisiblePassword"
- android:typeface="monospace" />
-
- <Button
- android:id="@+id/generate_password"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="end"
- android:text="@string/pwd_generate_button" />
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
+ android:id="@+id/password_input_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:hint="@string/crypto_pass_label"
+ app:hintEnabled="true"
+ app:layout_constraintTop_toBottomOf="@id/name_input_layout"
+ app:layout_constraintStart_toStartOf="parent">
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/crypto_password_edit"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </com.google.android.material.textfield.TextInputLayout>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dp"
- android:text="@string/crypto_extra_label"
- android:textStyle="bold" />
+ <com.google.android.material.button.MaterialButton
+ style="@style/Widget.MaterialComponents.Button"
+ android:id="@+id/generate_password"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:text="@string/pwd_generate_button"
+ android:textColor="?android:attr/windowBackground"
+ app:backgroundTint="?attr/colorSecondary"
+ app:layout_constraintTop_toBottomOf="@id/password_input_layout"
+ app:layout_constraintEnd_toEndOf="parent"/>
- <EditText
- android:id="@+id/crypto_extra_edit"
- android:layout_width="match_parent"
- android:layout_height="fill_parent"
- android:hint="@string/crypto_extra_edit_hint"
- android:enabled="true"
- android:inputType="textMultiLine|textVisiblePassword"
- android:typeface="monospace"
- android:visibility="visible" />
- </LinearLayout>
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
+ android:id="@+id/extra_input_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:hint="@string/crypto_extra_label"
+ app:hintEnabled="true"
+ app:layout_constraintTop_toBottomOf="@id/generate_password"
+ app:layout_constraintStart_toStartOf="parent">
- </ScrollView>
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/crypto_extra_edit"
+ android:inputType="textMultiLine|textVisiblePassword"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
-</LinearLayout> \ No newline at end of file
+ </com.google.android.material.textfield.TextInputLayout>
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_autofill.xml b/app/src/main/res/layout/fragment_autofill.xml
index 7f09f0cb..d780e6f6 100644
--- a/app/src/main/res/layout/fragment_autofill.xml
+++ b/app/src/main/res/layout/fragment_autofill.xml
@@ -14,15 +14,13 @@
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- app:hintTextAppearance="@style/TextAppearance.AppCompat">
-
- <EditText
+ android:hint="URL"
+ app:hintEnabled="true">
+ <com.google.android.material.textfield.TextInputEditText
android:id="@+id/webURL"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="URL"
- android:inputType="textUri"
- tools:ignore="HardcodedText" />
+ android:inputType="textUri"/>
</com.google.android.material.textfield.TextInputLayout>
<RadioGroup
@@ -61,13 +59,15 @@
android:layout_gravity="center_horizontal"
android:layout_weight="1"/>
- <Button
- style="?android:attr/buttonStyleSmall"
+ <com.google.android.material.button.MaterialButton
+ style="@style/Widget.MaterialComponents.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+"
android:id="@+id/matchButton"
android:layout_gravity="center_horizontal"
+ android:textColor="?android:attr/windowBackground"
+ app:backgroundTint="?attr/colorSecondary"
tools:ignore="HardcodedText" />
<RadioButton
diff --git a/app/src/main/res/layout/fragment_pwgen.xml b/app/src/main/res/layout/fragment_pwgen.xml
index eab23f7c..df01c62e 100644
--- a/app/src/main/res/layout/fragment_pwgen.xml
+++ b/app/src/main/res/layout/fragment_pwgen.xml
@@ -13,7 +13,7 @@
android:paddingTop="20dp"
tools:context=".MainActivityFragment">
- <TextView
+ <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/passwordText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -34,8 +34,7 @@
android:layout_weight="1"
android:orientation="vertical">
- <TextView
- android:id="@+id/include"
+ <androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
@@ -75,7 +74,7 @@
android:layout_weight="1"
android:orientation="vertical">
- <TextView
+ <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/length"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/app/src/main/res/layout/fragment_show_ssh_key.xml b/app/src/main/res/layout/fragment_show_ssh_key.xml
index f9b3b469..78ece084 100644
--- a/app/src/main/res/layout/fragment_show_ssh_key.xml
+++ b/app/src/main/res/layout/fragment_show_ssh_key.xml
@@ -12,14 +12,13 @@
android:paddingRight="24dp"
android:paddingTop="20dp">
- <TextView
+ <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/public_key"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textIsSelectable="true" />
- <TextView
- android:id="@+id/public_key_tip"
+ <androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ssh_keygen_tip"
diff --git a/app/src/main/res/layout/fragment_ssh_keygen.xml b/app/src/main/res/layout/fragment_ssh_keygen.xml
index 12961292..adfeb5e0 100644
--- a/app/src/main/res/layout/fragment_ssh_keygen.xml
+++ b/app/src/main/res/layout/fragment_ssh_keygen.xml
@@ -11,8 +11,7 @@
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin">
- <TextView
- android:id="@+id/label_length"
+ <androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="48dp"
android:gravity="center_vertical"
@@ -25,16 +24,17 @@
android:spinnerMode="dropdown" />
<com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
- app:hintTextAppearance="@style/TextAppearance.AppCompat">
+ android:hint="@string/ssh_keygen_passphrase"
+ app:hintEnabled="true">
- <EditText
+ <com.google.android.material.textfield.TextInputEditText
android:id="@+id/passphrase"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="@string/ssh_keygen_passphrase"
android:importantForAccessibility="no"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
@@ -48,21 +48,21 @@
android:text="@string/ssh_keygen_show_passphrase" />
<com.google.android.material.textfield.TextInputLayout
+ style="@style/TextInputLayoutBase"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
- app:hintTextAppearance="@style/TextAppearance.AppCompat">
+ android:hint="@string/ssh_keygen_comment"
+ app:hintEnabled="true">
- <EditText
+ <com.google.android.material.textfield.TextInputEditText
android:id="@+id/comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:hint="@string/ssh_keygen_comment"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<Button
- android:id="@+id/generate_ssh_key"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
diff --git a/app/src/main/res/layout/fragment_to_clone_or_not.xml b/app/src/main/res/layout/fragment_to_clone_or_not.xml
index 7d9341ba..cef35e1f 100644
--- a/app/src/main/res/layout/fragment_to_clone_or_not.xml
+++ b/app/src/main/res/layout/fragment_to_clone_or_not.xml
@@ -2,7 +2,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/blue_grey_500"
+ android:background="?attr/colorPrimary"
android:orientation="vertical"
tools:context="com.zeapo.pwdstore.PasswordStore">
@@ -11,7 +11,7 @@
android:layout_height="0dp"
android:layout_weight="1">
- <ImageView
+ <androidx.appcompat.widget.AppCompatImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView"
@@ -21,7 +21,7 @@
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
- <TextView
+ <androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
@@ -32,7 +32,6 @@
android:textStyle="bold"/>
<Button
- android:id="@+id/main_settings_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="openSettings"
@@ -48,20 +47,19 @@
</RelativeLayout>
<RelativeLayout
- android:id="@+id/myRectangleView"
android:layout_width="match_parent"
android:layout_height="48dp"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
- android:background="@android:color/white">
+ android:background="?android:attr/windowBackground">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/borderlessButtonStyle"
+ android:backgroundTint="?android:attr/windowBackground"
android:onClick="createNewRepository"
android:text="@string/initialize"
- android:id="@+id/button"
android:layout_alignTop="@+id/main_clone_button"
android:layout_alignParentStart="true"
android:textSize="12sp" />
@@ -70,7 +68,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/borderlessButtonStyle"
- android:textColor="@color/teal_A700"
+ android:backgroundTint="?android:attr/windowBackground"
android:onClick="cloneExistingRepository"
android:text="@string/clone"
android:layout_alignParentBottom="true"
diff --git a/app/src/main/res/layout/git_passphrase_layout.xml b/app/src/main/res/layout/git_passphrase_layout.xml
index fb0c0514..6fb4ee13 100644
--- a/app/src/main/res/layout/git_passphrase_layout.xml
+++ b/app/src/main/res/layout/git_passphrase_layout.xml
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
diff --git a/app/src/main/res/layout/password_recycler_view.xml b/app/src/main/res/layout/password_recycler_view.xml
index d10db5fc..5e87351b 100644
--- a/app/src/main/res/layout/password_recycler_view.xml
+++ b/app/src/main/res/layout/password_recycler_view.xml
@@ -21,8 +21,8 @@
android:layout_gravity="bottom|end"
app:elevation="6dp"
app:pressedTranslationZ="12dp"
- app:backgroundTint="@color/accent"
- app:rippleColor="@color/blue_grey_50"
+ app:backgroundTint="?attr/colorSecondary"
+ app:rippleColor="?attr/colorSecondary"
app:borderWidth="0dp"
android:layout_margin="@dimen/fab_compat_margin"
android:layout_alignParentBottom="true"
diff --git a/app/src/main/res/layout/password_row_layout.xml b/app/src/main/res/layout/password_row_layout.xml
index 66616cc3..71ad8cf4 100644
--- a/app/src/main/res/layout/password_row_layout.xml
+++ b/app/src/main/res/layout/password_row_layout.xml
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.zeapo.pwdstore.widget.MultiselectableLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="?android:attr/activatedBackgroundIndicator">
+ android:background="@drawable/password_row_background">
<LinearLayout
android:layout_width="match_parent"
@@ -15,7 +15,7 @@
android:paddingRight="16dp"
android:gravity="start">
- <ImageView
+ <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/type_image"
android:layout_width="80dp"
android:layout_height="32dp"
@@ -24,7 +24,7 @@
android:alpha="0.5"
android:contentDescription="@string/folder_icon_hint"
android:paddingEnd="8dp"
- android:src="@drawable/ic_folder_grey600_24dp"
+ android:src="@drawable/ic_folder_tinted_24dp"
tools:ignore="RtlSymmetry" />
<LinearLayout
@@ -34,24 +34,24 @@
android:layout_gravity="bottom"
android:orientation="vertical">
- <TextView
+ <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="start"
android:singleLine="true"
android:text="TYPE"
- android:textColor="@color/grey_500"
+ android:textColor="?android:attr/textColor"
android:textSize="14sp"
tools:ignore="HardcodedText" />
- <TextView
+ <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="FILE_NAME"
- android:textColor="@android:color/black"
+ android:textColor="?android:attr/textColor"
android:textSize="18sp"
tools:ignore="HardcodedText" />
@@ -59,4 +59,4 @@
</LinearLayout>
-</LinearLayout> \ No newline at end of file
+</com.zeapo.pwdstore.widget.MultiselectableLinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
new file mode 100644
index 00000000..55d52ca2
--- /dev/null
+++ b/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- Base palette -->
+ <color name="primary_color">#FF111111</color>
+ <color name="primary_light_color">#FF373737</color>
+ <color name="primary_dark_color">#FF000000</color>
+ <color name="secondary_color">#FFFF7539</color>
+ <color name="secondary_light_color">#FFFFa667</color>
+ <color name="secondary_dark_color">#FFC54506</color>
+ <color name="primary_text_color">#FFFFFFFF</color>
+ <color name="secondary_text_color">#FFFFFFFF</color>
+
+ <!-- Theme variables -->
+ <color name="window_background">@color/primary_color</color>
+ <color name="password_icon_color">#FAFAFA</color>
+ <color name="color_surface">#FF111111</color>
+ <color name="list_multiselect_background">#1AEEEEEE</color>
+</resources>
diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml
new file mode 100644
index 00000000..7fda5217
--- /dev/null
+++ b/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,7 @@
+<resources>
+ <style name="TextInputLayoutBase" parent="@style/Widget.MaterialComponents.TextInputLayout.FilledBox.Dense">
+ <item name="boxBackgroundColor">?attr/colorPrimaryVariant</item>
+ <item name="boxStrokeColor">?attr/colorSecondary</item>
+ <item name="hintTextColor">?attr/colorOnPrimary</item>
+ </style>
+</resources>
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..1a044c40
--- /dev/null
+++ b/app/src/main/res/values/attrs.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <declare-styleable name="PasswordIconStyle">
+ <attr name="passwordIconColor" format="reference|color" />
+ </declare-styleable>
+ <declare-styleable name="Multiselected">
+ <attr name="state_multiselected" format="boolean"/>
+ </declare-styleable>
+</resources> \ No newline at end of file
diff --git a/app/src/main/res/values/bools.xml b/app/src/main/res/values/bools.xml
new file mode 100644
index 00000000..b02fcc05
--- /dev/null
+++ b/app/src/main/res/values/bools.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <bool name="light_status_bar">false</bool>
+ <bool name="light_navigation_bar">false</bool>
+</resources>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index ef263161..98234581 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,19 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <color name="accent">#ff7043</color>
- <color name="background">#eee</color>
- <color name="blue_grey_50">#eceff1</color>
- <color name="blue_grey_500">#607d8b</color>
- <color name="blue_grey_200">#b0bec5</color>
- <color name="blue_grey_700">#455a64</color>
- <color name="blue_grey_900">#263238</color>
- <color name="deep_orange_200">#ffab91</color>
- <color name="grey_300">#e0e0e0</color>
- <color name="grey_500">#9e9e9e</color>
- <color name="grey_600">#757575</color>
- <color name="grey_white_1000">#ffffff</color>
- <color name="grey_black_1000">#000000</color>
- <color name="teal_900">#004d40</color>
- <color name="teal_A700">#00bfa5</color>
+ <!-- Base palette -->
+ <color name="primary_color">#607d8b</color>
+ <color name="primary_light_color">#8eacbb</color>
+ <color name="primary_dark_color">#34515e</color>
+ <color name="secondary_color">#ff7043</color>
+ <color name="secondary_light_color">#ffa270</color>
+ <color name="secondary_dark_color">#c63f17</color>
+ <color name="primary_text_color">#212121</color>
+ <color name="secondary_text_color">#ffffff</color>
+
+ <!-- Theme variables -->
+ <color name="window_background">#eceff1</color>
<color name="ic_launcher_background">#D4F1EA</color>
+ <color name="password_icon_color">#757575</color>
+ <color name="color_control_normal">@color/primary_text_color</color>
+ <color name="color_surface">#FFFFFF</color>
+ <color name="list_multiselect_background">#EEEEEE</color>
</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8e54490f..e39558b1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -74,7 +74,7 @@
<string name="server_protocol">Protocol</string>
<string name="server_url">Server URL</string>
<string name="server_url_hint" translatable="false">server.com</string>
- <string name="server_port_hint">22</string>
+ <string name="server_port_hint">Port</string>
<string name="default_ssh_port">22</string>
<string name="default_https_port">443</string>
<string name="server_path">Repo path</string>
@@ -90,16 +90,16 @@
<!-- Git Config fragment -->
<string name="git_config" translatable="false">Git config</string>
<string name="git_user_name" translatable="false">Username</string>
- <string name="git_user_name_hint">User name</string>
+ <string name="git_user_name_hint">Username</string>
<string name="git_user_email">Email</string>
<string name="git_user_email_hint">email</string>
<string name="invalid_email_dialog_text">Please enter a valid email address</string>
<string name="clone_button">Clone!</string>
<!-- PGP Handler -->
- <string name="crypto_name_hint">name</string>
+ <string name="crypto_name_hint">Name</string>
<string name="crypto_pass_label">Password</string>
- <string name="crypto_extra_label">Extra</string>
+ <string name="crypto_extra_label">Extra content</string>
<string name="crypto_select">Select</string>
<string name="crypto_cancel">Cancel</string>
<string name="crypto_save">Save</string>
@@ -138,8 +138,8 @@
<string name="pref_no_key_selected">No key selected</string>
<string name="pref_general_title">General</string>
<string name="pref_password_title">Password Show Time</string>
- <string name="pref_password_dialog_title">Set the time you want the password to be in clipboard. 0 means forever.</string>
- <string name="pref_copy_title">Automatically Copy Password</string>
+ <string name="pref_password_dialog_title">Set the time (in seconds) you want the password to be in clipboard. 0 means forever.</string>
+ <string name="pref_copy_title">Automatically copy Password</string>
<string name="pref_copy_dialog_title">Automatically copy the password to the clipboard after decryption was successful.</string>
<string name="ssh_key_success_dialog_title">SSH-key imported</string>
<string name="ssh_key_error_dialog_title">Error while trying to import the ssh-key</string>
@@ -251,8 +251,8 @@
<string name="remember_the_passphrase">Remember the passphrase in the app configuration (insecure)</string>
<string name="hackish_tools">Hackish tools</string>
<string name="abort_rebase">Abort rebase and push new branch</string>
+ <string name="reset_to_remote">Hard reset to remote branch</string>
<string name="commit_hash">Commit hash</string>
- <string name="crypto_password_edit_hint" translatable="false">p@ssw0rd!</string>
<string name="crypto_extra_edit_hint">username: something other extra content</string>
<string name="get_last_changed_failed">Failed to get last changed date</string>
<string name="hotp_pending">Tap copy to calculate HOTP</string>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 0b0bc4e6..ad6a61ff 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,22 +1,46 @@
-<resources>
+<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
- <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
- <item name="colorPrimary">@color/blue_grey_500</item>
- <item name="colorPrimaryDark">@color/blue_grey_700</item>
- <item name="android:windowBackground">@color/blue_grey_50</item>
- <item name="android:textColorPrimary">@color/teal_900</item>
- <item name="android:textColor">@color/text_color</item>
+ <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+ <item name="colorPrimary">@color/primary_color</item>
+ <item name="colorOnPrimary">@color/color_control_normal</item>
+ <item name="colorPrimaryDark">@color/primary_color</item>
+ <item name="colorPrimaryVariant">@color/primary_light_color</item>
+ <item name="colorSecondary">@color/secondary_color</item>
+ <item name="colorOnSecondary">@android:color/white</item>
+ <item name="colorSurface">@color/color_surface</item>
+ <item name="colorOnSurface">@color/color_control_normal</item>
+ <item name="colorControlNormal">@color/color_control_normal</item>
+ <item name="passwordIconColor">@color/password_icon_color</item>
+ <item name="android:colorBackground">@color/window_background</item>
+ <item name="colorBackgroundFloating">@color/primary_dark_color</item>
+ <item name="android:windowBackground">@color/window_background</item>
+ <item name="android:textColorPrimary">@color/primary_text_color</item>
+ <item name="android:textColor">@color/primary_text_color</item>
+ <item name="android:statusBarColor">@color/primary_dark_color</item>
+ <item name="android:navigationBarColor">@android:color/black</item>
+ <item name="android:windowLightStatusBar" tools:targetApi="m">@bool/light_status_bar</item>
+ <item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">@bool/light_navigation_bar</item>
<item name="actionModeStyle">@style/ActionMode</item>
+ <item name="alertDialogTheme">@style/AppTheme.Dialog</item>
+ <item name="materialAlertDialogTheme">@style/AppTheme.Dialog</item>
+ <item name="actionBarPopupTheme">@style/ThemeOverlay.MaterialComponents.ActionBar</item>
+ </style>
+
+ <style name="AppTheme.Dialog" parent="Theme.MaterialComponents.DayNight.Dialog.Alert">
+ <item name="colorPrimary">@color/secondary_color</item>
+ <item name="colorSecondary">@color/secondary_color</item>
+ <item name="android:windowBackground">@color/window_background</item>
</style>
<style name="ActionMode" parent="@style/Widget.AppCompat.ActionMode">
- <item name="background">@color/blue_grey_700</item>
+ <item name="background">@color/primary_color</item>
</style>
- <style name="FilePickerAlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
- <item name="colorPrimary">@color/blue_grey_500</item>
- <item name="colorPrimaryDark">@color/blue_grey_700</item>
- <item name="colorAccent">@color/teal_A700</item>
+ <style name="TextInputLayoutBase" parent="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense">
+ <item name="boxBackgroundColor">?android:attr/windowBackground</item>
+ <item name="boxStrokeColor">?attr/colorSecondary</item>
+ <item name="hintTextColor">?attr/colorOnPrimary</item>
</style>
+
</resources>
diff --git a/app/src/main/res/xml/preference.xml b/app/src/main/res/xml/preference.xml
index 3e8f34a9..194053e6 100644
--- a/app/src/main/res/xml/preference.xml
+++ b/app/src/main/res/xml/preference.xml
@@ -1,28 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
- <PreferenceCategory android:title="@string/pref_git_title">
- <Preference
+<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ <androidx.preference.PreferenceCategory android:title="@string/pref_git_title">
+ <androidx.preference.Preference
android:key="git_server_info"
android:title="@string/pref_edit_server_info" />
- <Preference
+ <androidx.preference.Preference
android:key="git_config"
android:title="@string/pref_edit_git_config" />
- <Preference
+ <androidx.preference.Preference
android:key="ssh_key"
android:title="@string/pref_ssh_title" />
- <Preference
+ <androidx.preference.Preference
android:key="ssh_keygen"
android:title="@string/pref_ssh_keygen_title" />
- <Preference
+ <androidx.preference.Preference
android:key="ssh_key_clear_passphrase"
android:title="@string/ssh_key_clear_passphrase" />
- <Preference
+ <androidx.preference.Preference
android:key="hotp_remember_clear_choice"
android:title="@string/hotp_remember_clear_choice" />
- <Preference
+ <androidx.preference.Preference
android:key="ssh_see_key"
android:title="@string/pref_ssh_see_key_title" />
- <Preference
+ <androidx.preference.Preference
android:key="git_delete_repo"
android:summary="@string/pref_git_delete_repo_summary"
android:title="@string/pref_git_delete_repo" />
@@ -30,106 +30,102 @@
android:key="git_external"
android:summary="@string/pref_external_repository_summary"
android:title="@string/pref_external_repository" />
- <Preference
+ <androidx.preference.Preference
android:dependency="git_external"
android:key="pref_select_external"
android:title="@string/pref_select_external_repository" />
- </PreferenceCategory>
+ </androidx.preference.PreferenceCategory>
- <PreferenceCategory android:title="@string/pref_crypto_title">
+ <androidx.preference.PreferenceCategory android:title="@string/pref_crypto_title">
+ <!-- TODO(msf): Update the damn library and re-enable this
<org.openintents.openpgp.util.OpenPgpAppPreference
android:key="openpgp_provider_list"
android:title="@string/pref_provider_title" />
- <Preference
+ -->
+ <androidx.preference.Preference
android:key="openpgp_key_id_pref"
android:title="@string/pref_key_title" />
- </PreferenceCategory>
+ </androidx.preference.PreferenceCategory>
- <PreferenceCategory android:title="@string/pref_general_title">
- <EditTextPreference
+ <androidx.preference.PreferenceCategory android:title="@string/pref_general_title">
+ <androidx.preference.EditTextPreference
android:defaultValue="45"
android:dialogTitle="@string/pref_password_dialog_title"
android:inputType="number"
android:key="general_show_time"
android:summary="@string/pref_password_dialog_title"
android:title="@string/pref_password_title" />
- <CheckBoxPreference
+ <androidx.preference.CheckBoxPreference
android:defaultValue="true"
android:title="@string/show_password_pref_title"
android:summary="@string/show_password_pref_summary"
android:key="show_password" />
- <CheckBoxPreference
+ <androidx.preference.CheckBoxPreference
android:defaultValue="true"
android:title="@string/show_extra_content_pref_title"
android:summary="@string/show_extra_content_pref_summary"
android:key="show_extra_content" />
- <CheckBoxPreference
+ <androidx.preference.CheckBoxPreference
android:defaultValue="true"
android:dialogTitle="@string/pref_copy_dialog_title"
android:key="copy_on_decrypt"
android:summary="@string/pref_copy_dialog_title"
android:title="@string/pref_copy_title" />
- <CheckBoxPreference
+ <androidx.preference.CheckBoxPreference
android:defaultValue="true"
android:key="clear_after_copy"
android:summary="@string/prefs_clear_after_copy_summary"
android:title="@string/prefs_clear_after_copy_title" />
- <CheckBoxPreference
+ <androidx.preference.CheckBoxPreference
android:defaultValue="true"
android:key="filter_recursively"
android:summary="@string/pref_recursive_filter_hint"
android:title="@string/pref_recursive_filter" />
- <ListPreference
+ <androidx.preference.ListPreference
android:title="@string/pref_sort_order_title"
android:defaultValue="FOLDER_FIRST"
android:key="sort_order"
android:entries="@array/sort_order_entries"
android:entryValues="@array/sort_order_values"
- android:persistent="true"
- />
- </PreferenceCategory>
+ android:persistent="true" />
+ </androidx.preference.PreferenceCategory>
- <PreferenceCategory android:title="@string/pref_autofill_title">
- <CheckBoxPreference
+ <androidx.preference.PreferenceCategory android:title="@string/pref_autofill_title">
+ <androidx.preference.CheckBoxPreference
android:defaultValue="true"
android:key="autofill_enable"
android:title="@string/pref_autofill_enable_title"/>
- <Preference
- android:dependency="autofill_enable"
+ <androidx.preference.Preference
android:key="autofill_apps"
android:title="@string/pref_autofill_apps_title"/>
- <CheckBoxPreference
- android:dependency="autofill_enable"
+ <androidx.preference.CheckBoxPreference
android:defaultValue="true"
android:key="autofill_default"
android:summary="@string/pref_autofill_default_hint"
android:title="@string/pref_autofill_default_title"/>
- <CheckBoxPreference
- android:dependency="autofill_enable"
+ <androidx.preference.CheckBoxPreference
android:defaultValue="false"
android:key="autofill_always"
android:title="@string/pref_autofill_always_title"/>
- </PreferenceCategory>
+ </androidx.preference.PreferenceCategory>
- <PreferenceCategory android:title="@string/pref_misc_title">
- <Preference
+ <androidx.preference.PreferenceCategory android:title="@string/pref_misc_title">
+ <androidx.preference.Preference
android:key="export_passwords"
android:title="@string/prefs_export_passwords_title"
android:summary="@string/prefs_export_passwords_summary"/>
- <CheckBoxPreference
+ <androidx.preference.CheckBoxPreference
android:defaultValue="false"
android:key="clear_clipboard_20x"
android:summary="@string/pref_clear_clipboard_hint"
android:title="@string/pref_clear_clipboard_title" />
+ </androidx.preference.PreferenceCategory>
- <CheckBoxPreference
- android:defaultValue="false"
- android:key="use_android_file_picker"
- android:title="@string/prefs_use_default_file_picker" />
- </PreferenceCategory>
-
- <Preference
- android:key="app_version"
- android:title="@string/prefs_version" />
-</PreferenceScreen>
+ <androidx.preference.PreferenceCategory>
+ <androidx.preference.Preference
+ android:icon="@mipmap/ic_launcher_round"
+ android:key="app_version"
+ android:title="@string/prefs_version" />
+ </androidx.preference.PreferenceCategory>
+</androidx.preference.PreferenceScreen>