Redesign UI and introduce dark theme (#519)
Caveats: - The openpgp preference had to be removed because the open-intents developers are too lazy to update their libraries. Over the coming weeks I will be reimplementing a local solution for this instead. - The autofill dialog is broken but I since it is being worked on in #410 already I'm not going to bother fixing it.
This commit is contained in:
parent
c9dc4034f1
commit
073346c157
73 changed files with 2474 additions and 2433 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -34,4 +34,7 @@ project.properties
|
|||
.idea
|
||||
*.iml
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/
|
||||
|
||||
captures/
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 androidx.test.filters.LargeTest
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
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 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) -> {
|
||||
|
|
|
@ -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) -> {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
||||
findPreference("openpgp_key_id_pref").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
|
||||
// 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}"
|
||||
|
||||
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")
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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" +
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
660
app/src/main/java/com/zeapo/pwdstore/git/GitActivity.kt
Normal file
660
app/src/main/java/com/zeapo/pwdstore/git/GitActivity.kt
Normal file
|
@ -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 = "^[^@]+@[^@]+$"
|
||||
}
|
||||
}
|
|
@ -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)) { _, _ ->
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() }
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
81
app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt
Normal file
81
app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
282
app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt
Normal file
282
app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt
Normal file
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
|
@ -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
|
||||
|
|
12
app/src/main/res/drawable/password_row_background.xml
Normal file
12
app/src/main/res/drawable/password_row_background.xml
Normal file
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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">
|
||||
android:background="?android:attr/windowBackground">
|
||||
|
||||
<LinearLayout
|
||||
<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"/>
|
||||
<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" />
|
||||
|
||||
<RelativeLayout
|
||||
<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:orientation="horizontal"
|
||||
android:layout_gravity="center_vertical">
|
||||
android:inputType="textWebEmailAddress" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<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">
|
||||
|
||||
<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
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_vertical">
|
||||
android:id="@+id/server_url"
|
||||
android:inputType="textWebEmailAddress" />
|
||||
|
||||
<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" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<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
|
||||
<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">
|
||||
android:inputType="number" />
|
||||
|
||||
<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" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<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" />
|
||||
<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">
|
||||
|
||||
<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
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/server_path"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_vertical">
|
||||
android:inputType="textWebEmailAddress"/>
|
||||
|
||||
<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" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<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>
|
||||
<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">
|
||||
|
||||
|
||||
<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
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/clone_uri"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/repository_uri"
|
||||
android:inputType="textWebEmailAddress"
|
||||
tools:ignore="TextFields" />
|
||||
android:inputType="textWebEmailAddress"/>
|
||||
|
||||
<TextView
|
||||
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"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_vertical">
|
||||
<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" />
|
||||
|
||||
<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: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" />
|
||||
|
||||
<Spinner
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/connection_mode"
|
||||
android:layout_toEndOf="@+id/label_connection_mode" />
|
||||
</RelativeLayout>
|
||||
<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" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/clone_button"
|
||||
android:text="@string/clone_button"
|
||||
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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="saveConfiguration"/>
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
<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>
|
||||
|
|
|
@ -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"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="@string/git_user_name_hint"
|
||||
app:hintEnabled="true"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="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_name"
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<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_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="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"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/crypto_save"
|
||||
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"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:textStyle="bold"
|
||||
android:textSize="24sp"
|
||||
android:id="@+id/git_tools_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:text="@string/hackish_tools"
|
||||
app:layout_constraintTop_toBottomOf="@id/save_button"
|
||||
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: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" />
|
||||
|
||||
|
||||
<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" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/git_user_email"
|
||||
android:layout_width="0dp"
|
||||
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:id="@+id/save_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:onClick="applyGitConfigs"
|
||||
android:text="@string/crypto_save"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/git_user_email" />
|
||||
|
||||
<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"
|
||||
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: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" />
|
||||
|
||||
<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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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">
|
||||
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" />
|
||||
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
<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
|
||||
<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">
|
||||
android:layout_height="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:visibility="invisible"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/crypto_password_show_label"/>
|
||||
|
||||
<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"
|
||||
<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:visibility="invisible">
|
||||
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"/>
|
||||
|
||||
<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" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<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" />
|
||||
<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">
|
||||
|
||||
<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" />
|
||||
<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"/>
|
||||
|
||||
<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" />
|
||||
<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"/>
|
||||
|
||||
<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" />
|
||||
<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"/>
|
||||
|
||||
<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" />
|
||||
<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"/>
|
||||
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
<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" />
|
||||
|
||||
</LinearLayout>
|
||||
<ToggleButton
|
||||
android:id="@+id/crypto_extra_toggle_show"
|
||||
style="@style/Widget.MaterialComponents.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: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>
|
||||
|
|
|
@ -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" />
|
||||
<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>
|
||||
|
||||
<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" />
|
||||
<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"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/crypto_pass_label"
|
||||
android:textStyle="bold" />
|
||||
<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">
|
||||
|
||||
<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" />
|
||||
<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"/>
|
||||
|
||||
<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" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/crypto_extra_label"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<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>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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">
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
</com.zeapo.pwdstore.widget.MultiselectableLinearLayout>
|
18
app/src/main/res/values-night/colors.xml
Normal file
18
app/src/main/res/values-night/colors.xml
Normal file
|
@ -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>
|
7
app/src/main/res/values-night/styles.xml
Normal file
7
app/src/main/res/values-night/styles.xml
Normal file
|
@ -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>
|
9
app/src/main/res/values/attrs.xml
Normal file
9
app/src/main/res/values/attrs.xml
Normal file
|
@ -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>
|
5
app/src/main/res/values/bools.xml
Normal file
5
app/src/main/res/values/bools.xml
Normal file
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue