diff --git a/.idea/misc.xml b/.idea/misc.xml index fb8c126a..7631aec3 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 46f2ce9b..97cb1eb5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,13 +7,13 @@ apply plugin: 'io.fabric' apply plugin: 'com.google.firebase.firebase-perf' android { - compileSdkVersion 28 + compileSdkVersion 29 defaultConfig { applicationId "xyz.quaver.pupil" minSdkVersion 16 - targetSdkVersion 28 - versionCode 16 - versionName "2.9" + targetSdkVersion 29 + versionCode 17 + versionName "2.10-alpha" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true } @@ -42,17 +42,21 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.preference:preference:1.1.0-beta01' implementation 'com.google.android.material:material:1.0.0' - implementation 'com.google.firebase:firebase-core:16.0.9' - implementation 'com.google.firebase:firebase-perf:17.0.2' + implementation 'com.google.firebase:firebase-core:17.0.0' + implementation 'com.google.firebase:firebase-perf:18.0.1' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.github.arimorty:floatingsearchview:2.1.1' - implementation 'com.github.deano2390:MaterialShowcaseView:1.3.4' + implementation 'com.andrognito.patternlockview:patternlockview:1.0.0' implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation "ru.noties.markwon:core:${markwonVersion}" implementation 'com.github.clans:fab:1.6.4' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation project(path: ':libpupil') diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index efb4fd24..481bb434 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,11 +18,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile --keep class com.finotes.android.finotescore.* { *; } - --keepclassmembers class * { - @com.finotes.android.finotescore.annotation.Observe *; -} - --keepattributes SourceFile,LineNumberTable \ No newline at end of file +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/release/output.json b/app/release/output.json index 0cc69cca..f084575a 100644 --- a/app/release/output.json +++ b/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":16,"versionName":"2.9","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":17,"versionName":"2.9.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt index 79db10d2..facb9b50 100644 --- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt @@ -1,16 +1,18 @@ package xyz.quaver.pupil -import android.graphics.BitmapFactory +import android.content.Intent import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.rule.ActivityTestRule import org.junit.Assert.assertEquals +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import xyz.quaver.hiyobi.cookie import xyz.quaver.hiyobi.getReader import xyz.quaver.hiyobi.user_agent -import java.io.File +import xyz.quaver.pupil.ui.LockActivity import java.net.URL import javax.net.ssl.HttpsURLConnection @@ -21,6 +23,7 @@ import javax.net.ssl.HttpsURLConnection */ @RunWith(AndroidJUnit4::class) class ExampleInstrumentedTest { + @Test fun useAppContext() { // Context of the app under test. @@ -30,12 +33,12 @@ class ExampleInstrumentedTest { @Test fun checkCacheDir() { + val activityTestRule = ActivityTestRule(LockActivity::class.java) val appContext = InstrumentationRegistry.getInstrumentation().targetContext - val file = File(appContext.cacheDir, "imageCache/1412251/01.jpg.webp") - val bitmap = BitmapFactory.decodeFile(file.absolutePath) + activityTestRule.launchActivity(Intent()) - Log.d("Pupil", bitmap.byteCount.toString()) + while(true); } @Test diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 294d57a0..c3e6686f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,20 +6,19 @@ - + android:theme="@style/AppTheme"> + - + android:name=".ui.ReaderActivity" + android:configChanges="keyboardHidden|orientation|screenSize" + android:parentActivityName=".ui.MainActivity"> @@ -27,9 +26,9 @@ + android:pathPrefix="/galleries" + android:scheme="https" /> @@ -38,9 +37,9 @@ + android:pathPrefix="/reader" + android:scheme="https" /> @@ -49,9 +48,9 @@ + android:pathPrefix="/reader" + android:scheme="https" /> @@ -60,9 +59,9 @@ + android:pathPrefix="/g" + android:scheme="https" /> @@ -71,9 +70,9 @@ + android:pathPrefix="/galleries" + android:scheme="http" /> @@ -82,9 +81,9 @@ + android:pathPrefix="/reader" + android:scheme="http" /> @@ -93,9 +92,9 @@ + android:pathPrefix="/reader" + android:scheme="http" /> @@ -104,16 +103,16 @@ + android:pathPrefix="/g" + android:scheme="http" /> diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt index 12080356..9cf73343 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt @@ -24,7 +24,7 @@ import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.list import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.ReaderItem -import xyz.quaver.pupil.Pupil +import xyz.quaver.pupil.ui.Pupil import xyz.quaver.pupil.R import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.util.Histories diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt index f4f33a09..e75617c4 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt @@ -6,9 +6,6 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.recyclerview.widget.RecyclerView -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import xyz.quaver.pupil.R class ReaderAdapter(private val images: List) : RecyclerView.Adapter() { diff --git a/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt new file mode 100644 index 00000000..d8eee511 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt @@ -0,0 +1,85 @@ +package xyz.quaver.pupil.ui + +import android.app.Activity +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.widget.Toast +import com.andrognito.patternlockview.PatternLockView +import com.google.android.material.snackbar.Snackbar +import kotlinx.android.synthetic.main.activity_lock.* +import kotlinx.android.synthetic.main.fragment_pattern_lock.* +import kotlinx.android.synthetic.main.settings_activity.* +import xyz.quaver.pupil.R +import xyz.quaver.pupil.util.Lock +import xyz.quaver.pupil.util.LockManager + +class LockActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_lock) + + val lockManager = LockManager(this) + + val mode = intent.getStringExtra("mode") + + lock_pattern.isEnabled = false + lock_pin.isEnabled = false + lock_fingerprint.isEnabled = false + lock_password.isEnabled = false + + when(mode) { + null -> { + if (lockManager.empty()) { + setResult(RESULT_OK) + finish() + } + } + "add_lock" -> { + when(intent.getStringExtra("type")!!) { + "pattern" -> { + + } + } + } + } + + supportFragmentManager.beginTransaction().add( + R.id.lock_content, + PatternLockFragment().apply { + var lastPass = "" + onPatternDrawn = { + when(mode) { + null -> { + val result = lockManager.check(it) + + if (result == true) { + setResult(Activity.RESULT_OK) + finish() + } else + lock_pattern_view.setViewMode(PatternLockView.PatternViewMode.WRONG) + } + "add_lock" -> { + if (lastPass.isEmpty()) { + lastPass = it + + Snackbar.make(view!!, R.string.settings_lock_confirm, Snackbar.LENGTH_LONG).show() + } else { + if (lastPass == it) { + LockManager(context!!).add(Lock.generate(Lock.Type.PATTERN, it)) + finish() + } else { + lock_pattern_view.setViewMode(PatternLockView.PatternViewMode.WRONG) + lastPass = "" + + Snackbar.make(view!!, R.string.settings_lock_wrong_confirm, Snackbar.LENGTH_LONG).show() + } + } + } + } + } + } + ).commit() + } + +} diff --git a/app/src/main/java/xyz/quaver/pupil/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt similarity index 96% rename from app/src/main/java/xyz/quaver/pupil/MainActivity.kt rename to app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index 1933c54e..8b59da1c 100644 --- a/app/src/main/java/xyz/quaver/pupil/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -1,12 +1,12 @@ -package xyz.quaver.pupil +package xyz.quaver.pupil.ui import android.Manifest +import android.app.Activity import android.content.Intent import android.content.pm.PackageManager import android.graphics.drawable.Animatable import android.net.Uri import android.os.Bundle -import android.preference.PreferenceManager import android.text.* import android.text.style.AlignmentSpan import android.view.* @@ -21,6 +21,7 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.core.view.GravityCompat +import androidx.preference.PreferenceManager import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.arlib.floatingsearchview.FloatingSearchView import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion @@ -40,6 +41,8 @@ import kotlinx.serialization.list import kotlinx.serialization.stringify import ru.noties.markwon.Markwon import xyz.quaver.hitomi.* +import xyz.quaver.pupil.BuildConfig +import xyz.quaver.pupil.R import xyz.quaver.pupil.adapters.GalleryBlockAdapter import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.TagSuggestion @@ -51,6 +54,7 @@ import java.net.URL import java.util.* import javax.net.ssl.HttpsURLConnection import kotlin.collections.ArrayList +import kotlin.math.min import kotlin.math.roundToInt class MainActivity : AppCompatActivity() { @@ -65,9 +69,16 @@ class MainActivity : AppCompatActivity() { private val galleries = ArrayList>>() private var query = "" + set(value) { + field = value + findViewById(R.id.search_bar_text) + .setText(query, TextView.BufferType.EDITABLE) + } + private var mode = Mode.SEARCH - private val SETTINGS = 45162 + private val REQUEST_SETTINGS = 45162 + private val REQUEST_LOCK = 561 private var galleryIDs: Deferred>? = null private var totalItems = 0 @@ -81,6 +92,8 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + startActivityForResult(Intent(this, LockActivity::class.java), REQUEST_LOCK) + checkPermissions() val preference = PreferenceManager.getDefaultSharedPreferences(this) @@ -110,19 +123,11 @@ class MainActivity : AppCompatActivity() { initView() } - override fun onDestroy() { - super.onDestroy() - - if (cacheDir.exists()) - cacheDir.deleteRecursively() - } - override fun onBackPressed() { when { main_drawer_layout.isDrawerOpen(GravityCompat.START) -> main_drawer_layout.closeDrawer(GravityCompat.START) query.isNotEmpty() -> runOnUiThread { query = "" - findViewById(R.id.search_bar_text).setText(query, TextView.BufferType.EDITABLE) cancelFetch() clearGalleries() @@ -142,6 +147,7 @@ class MainActivity : AppCompatActivity() { WindowManager.LayoutParams.FLAG_SECURE) else window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) + super.onResume() } @@ -186,7 +192,7 @@ class MainActivity : AppCompatActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when(requestCode) { - SETTINGS -> { + REQUEST_SETTINGS -> { runOnUiThread { cancelFetch() clearGalleries() @@ -194,6 +200,10 @@ class MainActivity : AppCompatActivity() { loadBlocks() } } + REQUEST_LOCK -> { + if (resultCode != Activity.RESULT_OK) + finish() + } } } @@ -352,8 +362,7 @@ class MainActivity : AppCompatActivity() { onChipClickedHandler.add { runOnUiThread { query = it.toQuery() - this@MainActivity.findViewById(R.id.search_bar_text) - .setText(query, TextView.BufferType.EDITABLE) + currentPage = 0 cancelFetch() clearGalleries() @@ -679,7 +688,7 @@ class MainActivity : AppCompatActivity() { setOnMenuItemClickListener { when(it.itemId) { - R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), SETTINGS) + R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), REQUEST_SETTINGS) R.id.main_menu_jump -> { val preference = PreferenceManager.getDefaultSharedPreferences(context) val perPage = preference.getString("per_page", "25")!!.toInt() @@ -726,7 +735,8 @@ class MainActivity : AppCompatActivity() { startActivity(intent) } catch (e: Exception) { - Snackbar.make(main_layout, R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show() + Snackbar.make(main_layout, + R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show() } } } @@ -804,7 +814,9 @@ class MainActivity : AppCompatActivity() { favorites.remove(tag) } else { - setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star)) + setImageDrawable(AnimatedVectorDrawableCompat.create(context, + R.drawable.avd_star + )) (drawable as Animatable).start() favorites.add(tag) @@ -880,10 +892,8 @@ class MainActivity : AppCompatActivity() { } private fun cancelFetch() { - runBlocking { - galleryIDs?.cancelAndJoin() - loadingJob?.cancelAndJoin() - } + galleryIDs?.cancel() + loadingJob?.cancel() } private fun clearGalleries() { @@ -992,7 +1002,7 @@ class MainActivity : AppCompatActivity() { query.isEmpty() and defaultQuery.isEmpty() and (mode == Mode.SEARCH) -> galleryIDs else -> - galleryIDs.slice(currentPage*perPage until Math.min(currentPage*perPage+perPage, galleryIDs.size)) + galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size)) }.chunked(5).let { chunks -> for (chunk in chunks) chunk.map { galleryID -> @@ -1009,8 +1019,8 @@ class MainActivity : AppCompatActivity() { getGalleryBlock(galleryID).apply { this ?: return@apply - if (!cache.parentFile.exists()) - cache.parentFile.mkdirs() + if (cache.parentFile?.exists() == false) + cache.parentFile!!.mkdirs() cache.writeText(json.stringify(serializer, this)) } @@ -1024,8 +1034,8 @@ class MainActivity : AppCompatActivity() { if (!exists()) try { with(URL(galleryBlock.thumbnails[0]).openConnection() as HttpsURLConnection) { - if (!this@apply.parentFile.exists()) - this@apply.parentFile.mkdirs() + if (this@apply.parentFile?.exists() == false) + this@apply.parentFile!!.mkdirs() inputStream.copyTo(FileOutputStream(this@apply)) } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt new file mode 100644 index 00000000..11d8a53b --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt @@ -0,0 +1,47 @@ +package xyz.quaver.pupil.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.andrognito.patternlockview.PatternLockView +import com.andrognito.patternlockview.listener.PatternLockViewListener +import com.andrognito.patternlockview.utils.PatternLockUtils +import kotlinx.android.synthetic.main.fragment_pattern_lock.* +import kotlinx.android.synthetic.main.fragment_pattern_lock.view.* +import xyz.quaver.pupil.R +import xyz.quaver.pupil.util.hash +import xyz.quaver.pupil.util.hashWithSalt + +class PatternLockFragment : Fragment(), PatternLockViewListener { + + var onPatternDrawn: ((String) -> Unit)? = null + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_pattern_lock, container, false).apply { + lock_pattern_view.addPatternLockListener(this@PatternLockFragment) + } + } + + override fun onCleared() { + + } + + override fun onComplete(pattern: MutableList?) { + val password = PatternLockUtils.patternToMD5(lock_pattern_view, pattern) + onPatternDrawn?.invoke(password) + } + + override fun onProgress(progressPattern: MutableList?) { + + } + + override fun onStarted() { + + } + +} diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/ui/Pupil.kt similarity index 94% rename from app/src/main/java/xyz/quaver/pupil/Pupil.kt rename to app/src/main/java/xyz/quaver/pupil/ui/Pupil.kt index add8d659..c1859fe3 100644 --- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/Pupil.kt @@ -1,14 +1,14 @@ -package xyz.quaver.pupil +package xyz.quaver.pupil.ui -import android.app.Application import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.os.Build -import android.preference.PreferenceManager import androidx.core.content.ContextCompat import androidx.multidex.MultiDexApplication +import androidx.preference.PreferenceManager +import xyz.quaver.pupil.R import xyz.quaver.pupil.util.Histories import java.io.File diff --git a/app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt similarity index 97% rename from app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt rename to app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt index 49fa1f33..990f7b50 100644 --- a/app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -1,4 +1,4 @@ -package xyz.quaver.pupil +package xyz.quaver.pupil.ui import android.content.Intent import android.graphics.drawable.Animatable @@ -11,9 +11,9 @@ import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.SimpleItemAnimator import androidx.vectordrawable.graphics.drawable.Animatable2Compat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat +import com.crashlytics.android.Crashlytics import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.activity_reader.* import kotlinx.android.synthetic.main.activity_reader.view.* @@ -28,6 +28,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonConfiguration import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.getGalleryBlock +import xyz.quaver.pupil.R import xyz.quaver.pupil.adapters.ReaderAdapter import xyz.quaver.pupil.util.GalleryDownloader import xyz.quaver.pupil.util.Histories @@ -72,6 +73,8 @@ class ReaderActivity : AppCompatActivity() { handleIntent(intent) + Crashlytics.setInt("GalleryID", galleryBlock.id) + if (!::galleryBlock.isInitialized) { onBackPressed() return @@ -117,7 +120,7 @@ class ReaderActivity : AppCompatActivity() { } else { galleryBlock = Json(JsonConfiguration.Stable).parse( GalleryBlock.serializer(), - intent.getStringExtra("galleryblock") + intent.getStringExtra("galleryblock")!! ) } } @@ -372,10 +375,7 @@ class ReaderActivity : AppCompatActivity() { reader_recyclerview.layoutManager = LinearLayoutManager(this) } else { snapHelper.attachToRecyclerView(reader_recyclerview) - reader_recyclerview.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false).apply { - isItemPrefetchEnabled = true - initialPrefetchItemCount = 4 - } + reader_recyclerview.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) } (reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0) diff --git a/app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt similarity index 58% rename from app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt rename to app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt index e93b0166..1f995222 100644 --- a/app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt @@ -1,8 +1,8 @@ -package xyz.quaver.pupil +package xyz.quaver.pupil.ui +import android.app.Activity +import android.content.Intent import android.os.Bundle -import android.os.Environment -import android.preference.PreferenceManager import android.text.Editable import android.text.TextWatcher import android.view.LayoutInflater @@ -15,12 +15,18 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceManager import kotlinx.android.synthetic.main.dialog_default_query.view.* +import xyz.quaver.pupil.R import xyz.quaver.pupil.types.Tags +import xyz.quaver.pupil.util.Lock +import xyz.quaver.pupil.util.LockManager import java.io.File class SettingsActivity : AppCompatActivity() { + val REQUEST_LOCK = 38238 + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -58,7 +64,25 @@ class SettingsActivity : AppCompatActivity() { "TB" //really? ) - private fun getCacheSize(dir: File) : String { + override fun onResume() { + super.onResume() + + val lockManager = LockManager(context!!) + + findPreference("app_lock")?.summary = if (lockManager.locks.isNullOrEmpty()) { + getString(R.string.settings_lock_none) + } else { + lockManager.locks?.joinToString(", ") { + when(it.type) { + Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern) + Lock.Type.PIN -> getString(R.string.settings_lock_pin) + Lock.Type.PASSWORD -> getString(R.string.settings_lock_password) + } + } + } + } + + private fun getDirSize(dir: File) : String { var size = dir.walk().map { it.length() }.sum() var suffixIndex = 0 @@ -67,20 +91,53 @@ class SettingsActivity : AppCompatActivity() { suffixIndex++ } - return getString(R.string.settings_clear_downloads_summary, size, suffix[suffixIndex]) + return getString(R.string.settings_clear_summary, size, suffix[suffixIndex]) } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.root_preferences, rootKey) + with(findPreference("app_version")) { + this!! + + val manager = context.packageManager + val info = manager.getPackageInfo(context.packageName, 0) + + summary = info.versionName + } + + with(findPreference("delete_cache")) { + this!! + + val dir = File(context.cacheDir, "imageCache") + + summary = getDirSize(dir) + + onPreferenceClickListener = Preference.OnPreferenceClickListener { + AlertDialog.Builder(context).apply { + setTitle(R.string.warning) + setMessage(R.string.settings_clear_cache_alert_message) + setPositiveButton(android.R.string.yes) { _, _ -> + if (dir.exists()) + dir.deleteRecursively() + + summary = getDirSize(dir) + } + setNegativeButton(android.R.string.no) { _, _ -> } + }.show() + + true + } + } + with(findPreference("delete_downloads")) { - this ?: return@with + this!! - val dir = File(Environment.getExternalStorageDirectory(), "Pupil") + val dir = context.getExternalFilesDir("Pupil") ?: return@with - summary = getCacheSize(dir) + summary = getDirSize(dir) - setOnPreferenceClickListener { + onPreferenceClickListener = Preference.OnPreferenceClickListener { AlertDialog.Builder(context).apply { setTitle(R.string.warning) setMessage(R.string.settings_clear_downloads_alert_message) @@ -92,7 +149,7 @@ class SettingsActivity : AppCompatActivity() { downloads.clear() - summary = getCacheSize(dir) + summary = getDirSize(dir) } setNegativeButton(android.R.string.no) { _, _ -> } }.show() @@ -101,13 +158,13 @@ class SettingsActivity : AppCompatActivity() { } } with(findPreference("clear_history")) { - this ?: return@with + this!! val histories = (activity!!.application as Pupil).histories summary = getString(R.string.settings_clear_history_summary, histories.size) - setOnPreferenceClickListener { + onPreferenceClickListener = Preference.OnPreferenceClickListener { AlertDialog.Builder(context).apply { setTitle(R.string.warning) setMessage(R.string.settings_clear_history_alert_message) @@ -123,7 +180,7 @@ class SettingsActivity : AppCompatActivity() { } with(findPreference("default_query")) { - this ?: return@with + this!! val preferences = PreferenceManager.getDefaultSharedPreferences(context) @@ -139,7 +196,7 @@ class SettingsActivity : AppCompatActivity() { val excludeBL = "-male:yaoi" val excludeGuro = listOf("-female:guro", "-male:guro") - setOnPreferenceClickListener { + onPreferenceClickListener = Preference.OnPreferenceClickListener { val dialogView = LayoutInflater.from(context).inflate( R.layout.dialog_default_query, LinearLayout(context), @@ -154,7 +211,7 @@ class SettingsActivity : AppCompatActivity() { with(dialogView.default_query_dialog_language_selector) { adapter = - ArrayAdapter( + ArrayAdapter( context, android.R.layout.simple_spinner_dropdown_item, arrayListOf( @@ -234,6 +291,82 @@ class SettingsActivity : AppCompatActivity() { dialog.show() + true + } + } + with(findPreference("app_lock")) { + this!! + + val lockManager = LockManager(context) + + summary = if (lockManager.locks.isNullOrEmpty()) { + getString(R.string.settings_lock_none) + } else { + lockManager.locks?.joinToString(", ") { + when(it.type) { + Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern) + Lock.Type.PIN -> getString(R.string.settings_lock_pin) + Lock.Type.PASSWORD -> getString(R.string.settings_lock_password) + } + } + } + + onPreferenceClickListener = Preference.OnPreferenceClickListener { + val intent = Intent(context, LockActivity::class.java) + activity?.startActivityForResult(intent, (activity as SettingsActivity).REQUEST_LOCK) + + true + } + } + } + } + + class LockFragment : PreferenceFragmentCompat() { + + override fun onResume() { + super.onResume() + + val lockManager = LockManager(context!!) + + findPreference("lock_pattern")?.summary = + if (lockManager.contains(Lock.Type.PATTERN)) + getString(R.string.settings_lock_enabled) + else + "" + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.lock_preferences, rootKey) + + with(findPreference("lock_pattern")) { + this!! + + if (LockManager(context!!).contains(Lock.Type.PATTERN)) + summary = getString(R.string.settings_lock_enabled) + + onPreferenceClickListener = Preference.OnPreferenceClickListener { + val lockManager = LockManager(context!!) + + if (lockManager.contains(Lock.Type.PATTERN)) { + AlertDialog.Builder(context).apply { + setTitle(R.string.warning) + setMessage(R.string.settings_lock_remove_message) + + setPositiveButton(android.R.string.yes) { _, _ -> + lockManager.remove(Lock.Type.PATTERN) + onResume() + } + setNegativeButton(android.R.string.no) { _, _ -> } + }.show() + } else { + val intent = Intent(context, LockActivity::class.java).apply { + putExtra("mode", "add_lock") + putExtra("type", "pattern") + } + + startActivity(intent) + } + true } } @@ -247,4 +380,19 @@ class SettingsActivity : AppCompatActivity() { return true } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + when(requestCode) { + REQUEST_LOCK -> { + if (resultCode == Activity.RESULT_OK) { + supportFragmentManager + .beginTransaction() + .replace(R.id.settings, LockFragment()) + .addToBackStack("Lock") + .commitAllowingStateLoss() + } + } + else -> super.onActivityResult(requestCode, resultCode, data) + } + } } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt index f4e08369..fda31db8 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt @@ -4,7 +4,7 @@ import android.app.PendingIntent import android.content.Context import android.content.ContextWrapper import android.content.Intent -import android.os.Environment +import android.util.Log import android.util.SparseArray import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -18,9 +18,9 @@ import kotlinx.serialization.list import xyz.quaver.hitomi.* import xyz.quaver.hiyobi.cookie import xyz.quaver.hiyobi.user_agent -import xyz.quaver.pupil.Pupil +import xyz.quaver.pupil.ui.Pupil import xyz.quaver.pupil.R -import xyz.quaver.pupil.ReaderActivity +import xyz.quaver.pupil.ui.ReaderActivity import java.io.File import java.io.FileOutputStream import java.net.URL @@ -52,7 +52,7 @@ class GalleryDownloader( cache.deleteRecursively() } - if (!reader.isActive && downloadJob?.isActive != true) + if (reader?.isActive == false && downloadJob?.isActive != true) field = false downloads.add(galleryBlock.id) @@ -63,7 +63,7 @@ class GalleryDownloader( onNotifyChangedHandler?.invoke(value) } - private val reader: Deferred + private val reader: Deferred? private var downloadJob: Job? = null private lateinit var notificationBuilder: NotificationCompat.Builder @@ -126,8 +126,8 @@ class GalleryDownloader( if (reader.isNotEmpty()) { //Save cache - if (!cache.parentFile.exists()) - cache.parentFile.mkdirs() + if (cache.parentFile?.exists() == false) + cache.parentFile!!.mkdirs() cache.writeText(json.stringify(serializer, reader)) } @@ -140,7 +140,7 @@ class GalleryDownloader( fun start() { downloadJob = CoroutineScope(Dispatchers.Default).launch { - val reader = reader.await() + val reader = reader!!.await() if (reader.isEmpty()) onErrorHandler?.invoke(IOException("Couldn't retrieve Reader")) @@ -183,8 +183,8 @@ class GalleryDownloader( } else setRequestProperty("Referer", getReferer(galleryBlock.id)) - if (!cache.parentFile.exists()) - cache.parentFile.mkdirs() + if (cache.parentFile?.exists() == false) + cache.parentFile!!.mkdirs() inputStream.copyTo(FileOutputStream(cache)) } @@ -209,8 +209,6 @@ class GalleryDownloader( } } - onCompleteHandler?.invoke() - Timer(false).schedule(1000) { notificationBuilder .setContentTitle(galleryBlock.title) @@ -220,7 +218,7 @@ class GalleryDownloader( if (download) { File(cacheDir, "imageCache/${galleryBlock.id}").let { if (it.exists()) { - val target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id}") + val target = File(getExternalFilesDir("Pupil"), galleryBlock.id.toString()) if (!target.exists()) target.mkdirs() @@ -231,9 +229,11 @@ class GalleryDownloader( } notificationManager.notify(galleryBlock.id, notificationBuilder.build()) - } - download = false + onCompleteHandler?.invoke() + + download = false + } } remove(galleryBlock.id) @@ -254,7 +254,7 @@ class GalleryDownloader( fun invokeOnReaderLoaded() { CoroutineScope(Dispatchers.Default).launch { - onReaderLoadedHandler?.invoke(reader.await()) + onReaderLoadedHandler?.invoke(reader?.await() ?: return@launch) } } diff --git a/app/src/main/java/xyz/quaver/pupil/util/file.kt b/app/src/main/java/xyz/quaver/pupil/util/file.kt index 7e085650..f351dcd2 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/file.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/file.kt @@ -2,10 +2,11 @@ package xyz.quaver.pupil.util import android.content.Context import android.os.Environment +import android.provider.MediaStore import java.io.File fun getCachedGallery(context: Context, galleryID: Int): File { - return File(Environment.getExternalStorageDirectory(), "Pupil/$galleryID").let { + return File(context.getExternalFilesDir("Pupil"), galleryID.toString()).let { when { it.exists() -> it else -> File(context.cacheDir, "imageCache/$galleryID") diff --git a/app/src/main/java/xyz/quaver/pupil/util/lock.kt b/app/src/main/java/xyz/quaver/pupil/util/lock.kt new file mode 100644 index 00000000..13f87003 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/util/lock.kt @@ -0,0 +1,105 @@ +package xyz.quaver.pupil.util + +import android.content.Context +import android.content.ContextWrapper +import androidx.core.content.ContextCompat +import kotlinx.serialization.* +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonConfiguration +import java.io.File +import java.security.MessageDigest + +fun hash(password: String): String { + val bytes = password.toByteArray() + val md = MessageDigest.getInstance("SHA-256") + + return md.digest(bytes).fold("") { str, it -> str + "%02x".format(it) } +} + +// Ret1: SHA-256 Hash +// Ret2: Hash salt +fun hashWithSalt(password: String): Pair { + val salt = (0 until 12).map { source.random() }.joinToString() + + return Pair(hash(password+salt), salt) +} + +val source = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +@Serializable +data class Lock(val type: Type, val hash: String, val salt: String) { + + enum class Type { + PATTERN, + PIN, + PASSWORD + } + + companion object { + fun generate(type: Type, password: String): Lock { + val (hash, salt) = hashWithSalt(password) + return Lock(type, hash, salt) + } + } + + fun match(password: String): Boolean { + return hash(password+salt) == hash + } +} + +class LockManager(base: Context): ContextWrapper(base) { + + var locks: ArrayList? = null + + init { + load() + } + + @UseExperimental(ImplicitReflectionSerializer::class) + private fun load() { + val lock = File(ContextCompat.getDataDir(this), "lock.json") + + if (!lock.exists()) { + lock.createNewFile() + lock.writeText("[]") + } + + locks = ArrayList(Json(JsonConfiguration.Stable).parseList(lock.readText())) + } + + @UseExperimental(ImplicitReflectionSerializer::class) + private fun save() { + val lock = File(ContextCompat.getDataDir(this), "lock.json") + + if (!lock.exists()) + lock.createNewFile() + + lock.writeText(Json(JsonConfiguration.Stable).stringify(locks?.toList() ?: listOf())) + } + + fun add(lock: Lock) { + remove(lock.type) + locks?.add(lock) + save() + } + + fun remove(type: Lock.Type) { + locks?.removeAll { it.type == type } + save() + } + + fun check(password: String): Boolean? { + return locks?.any { + it.match(password) + } + } + + fun empty(): Boolean { + return locks.isNullOrEmpty() + } + + fun contains(type: Lock.Type): Boolean { + return locks?.any { it.type == type } ?: false + } + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/util/update.kt b/app/src/main/java/xyz/quaver/pupil/util/update.kt index 2c951cbe..1461f75c 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/update.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt @@ -1,5 +1,6 @@ package xyz.quaver.pupil.util +import android.util.Log import kotlinx.serialization.json.* import java.net.URL @@ -19,8 +20,20 @@ fun checkUpdate(url: String, currentVersion: String) : JsonObject? { if (releases.isEmpty()) return null - if (currentVersion != releases[0].jsonObject["tag_name"]?.content) - return releases[0].jsonObject + val latestVersion = releases[0].jsonObject["tag_name"]?.content - return null + return when { + currentVersion.split('-').size == 1 -> { + when { + currentVersion != latestVersion -> releases[0].jsonObject + else -> null + } + } + else -> { + when { + (currentVersion.split('-')[0] == latestVersion) -> releases[0].jsonObject + else -> null + } + } + } } \ No newline at end of file diff --git a/app/src/main/res/drawable/fingerprint.xml b/app/src/main/res/drawable/fingerprint.xml new file mode 100644 index 00000000..ffa8a34b --- /dev/null +++ b/app/src/main/res/drawable/fingerprint.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/lastpass.xml b/app/src/main/res/drawable/lastpass.xml new file mode 100644 index 00000000..9dbf638b --- /dev/null +++ b/app/src/main/res/drawable/lastpass.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/lock_pattern.xml b/app/src/main/res/drawable/lock_pattern.xml new file mode 100644 index 00000000..f1be51c2 --- /dev/null +++ b/app/src/main/res/drawable/lock_pattern.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_lock.xml b/app/src/main/res/layout/activity_lock.xml new file mode 100644 index 00000000..e4632044 --- /dev/null +++ b/app/src/main/res/layout/activity_lock.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main_content.xml b/app/src/main/res/layout/activity_main_content.xml index a5b3b266..bde8ddf4 100644 --- a/app/src/main/res/layout/activity_main_content.xml +++ b/app/src/main/res/layout/activity_main_content.xml @@ -6,7 +6,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".MainActivity"> + tools:context=".ui.MainActivity"> + tools:context=".ui.ReaderActivity"> + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 8e7dd7d0..bdab1daf 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -8,7 +8,8 @@ ギャラリー検索 ギャラリー検索 イメージキャッシュクリア - サイズ: %1$d%2$s + キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか? + サイズ: %1$d%2$s デフォルトキーワード 一回にロードするギャラリー数 検索設定 @@ -66,4 +67,16 @@ エラーが発生しました ストレージ カカオトーク + アプリロック + アップロックの種類 + バージョン + 生体認識 + ロック確認のためもう一回入力してください。 + 有効 + 指紋 + パスワード + パターン + ロックが一致しません。やり直してください。 + なし + ロックを無効にしますか? \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 0b99f608..d7c7956d 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -7,7 +7,8 @@ 갤러리 검색 기본 검색어 이미지 캐시 정리하기 - 사용량: %1$d%2$s + 캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까? + 사용량: %1$d%2$s 한 번에 로드할 갤러리 수 검색 설정 설정 @@ -66,4 +67,16 @@ 갤러리를 찾지 못했습니다 저장 공간 카카오톡 오픈채팅방 + 앱 잠금 + 앱 잠금 종류 + 앱 버전 + 생체 인식 + 잠금 확인을 위해 한번 더 입력해주세요 + 사용 중 + 지문 + 비밀번호 + 패턴 + 잠금이 일치하지 않습니다. 다시 시도하세요. + 없음 + 잠금을 해제할까요? \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2cc8d514..c8ef231d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -80,23 +80,38 @@ Download error Settings + App version Search Settings Galleries per page Default query Storage Clear image cache - Currently using %1$d%2$s + Deleting cache can affect image loading speed. Do you want to continue? + Currently using %1$d%2$s Clear downloads Delete all downloaded galleries.\nDo you want to continue? Clear history Do you want to clear histories? %1$d histories saved + App lock + App lock type Miscellaneous Use hiyobi.me Load images from hiyobi.me to improve loading speed (if available) Enable security mode Enable security mode to make the screen invisible on recent app window + None + Pattern + PIN + Password + Biomatrics + Fingerprint + Enabled + Input same lock once more to confirm Lock + Do you want to remove lock? + Lock is different from last one. Please try again. + Set default query Language: Filter BL diff --git a/app/src/main/res/xml/lock_preferences.xml b/app/src/main/res/xml/lock_preferences.xml new file mode 100644 index 00000000..9fed9fba --- /dev/null +++ b/app/src/main/res/xml/lock_preferences.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 0ccfbbb2..5a802f27 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -2,6 +2,10 @@ + + @@ -24,6 +28,10 @@ + + @@ -34,6 +42,15 @@ + + + + + + diff --git a/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt b/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt index bc7c0d78..1b864d91 100644 --- a/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt +++ b/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt @@ -1,15 +1,24 @@ package xyz.quaver.hitomi import org.junit.Test -import java.io.File -import java.net.URL +import java.net.InetAddress +import java.net.UnknownHostException + class UnitTest { @Test fun test() { - val galleries = getGalleryIDsForQuery("series:touhou_project") - println(galleries.size) + } + + private fun getByIp(host: String): InetAddress { + try { + return InetAddress.getByName(host) + } catch (e: UnknownHostException) { + // unlikely + throw RuntimeException(e) + } + } @Test