Merge pull request #11 from tom5079/development

Pupil v2.10
This commit is contained in:
tom5079
2019-06-23 16:17:49 +09:00
committed by GitHub
31 changed files with 736 additions and 131 deletions

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8 (2)" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" /> <output url="file://$PROJECT_DIR$/classes" />
</component> </component>
</project> </project>

View File

@@ -7,13 +7,13 @@ apply plugin: 'io.fabric'
apply plugin: 'com.google.firebase.firebase-perf' apply plugin: 'com.google.firebase.firebase-perf'
android { android {
compileSdkVersion 28 compileSdkVersion 29
defaultConfig { defaultConfig {
applicationId "xyz.quaver.pupil" applicationId "xyz.quaver.pupil"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 29
versionCode 16 versionCode 17
versionName "2.9" versionName "2.10-alpha"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
} }
@@ -42,17 +42,21 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.1.0-beta01' implementation 'androidx.preference:preference:1.1.0-beta01'
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.0.0'
implementation 'com.google.firebase:firebase-core:16.0.9' implementation 'com.google.firebase:firebase-core:17.0.0'
implementation 'com.google.firebase:firebase-perf:17.0.2' implementation 'com.google.firebase:firebase-perf:18.0.1'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.github.arimorty:floatingsearchview:2.1.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.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "ru.noties.markwon:core:${markwonVersion}" implementation "ru.noties.markwon:core:${markwonVersion}"
implementation 'com.github.clans:fab:1.6.4' 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' testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1' 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:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation project(path: ':libpupil') implementation project(path: ':libpupil')

View File

@@ -18,11 +18,4 @@
# If you keep the line number information, uncomment this to # If you keep the line number information, uncomment this to
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
-keep class com.finotes.android.finotescore.* { *; }
-keepclassmembers class * {
@com.finotes.android.finotescore.annotation.Observe *;
}
-keepattributes SourceFile,LineNumberTable

View File

@@ -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":{}}] [{"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":{}}]

View File

@@ -1,16 +1,18 @@
package xyz.quaver.pupil package xyz.quaver.pupil
import android.graphics.BitmapFactory import android.content.Intent
import android.util.Log import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import xyz.quaver.hiyobi.cookie import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.getReader import xyz.quaver.hiyobi.getReader
import xyz.quaver.hiyobi.user_agent import xyz.quaver.hiyobi.user_agent
import java.io.File import xyz.quaver.pupil.ui.LockActivity
import java.net.URL import java.net.URL
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
@@ -21,6 +23,7 @@ import javax.net.ssl.HttpsURLConnection
*/ */
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest { class ExampleInstrumentedTest {
@Test @Test
fun useAppContext() { fun useAppContext() {
// Context of the app under test. // Context of the app under test.
@@ -30,12 +33,12 @@ class ExampleInstrumentedTest {
@Test @Test
fun checkCacheDir() { fun checkCacheDir() {
val activityTestRule = ActivityTestRule<LockActivity>(LockActivity::class.java)
val appContext = InstrumentationRegistry.getInstrumentation().targetContext 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 @Test

View File

@@ -6,20 +6,19 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application <application
android:name=".ui.Pupil"
android:allowBackup="true" android:allowBackup="true"
android:fullBackupContent="true" android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme">
android:name=".Pupil"> <activity android:name=".ui.LockActivity"/>
<activity <activity
android:name=".ReaderActivity" android:name=".ui.ReaderActivity"
android:parentActivityName=".MainActivity" android:configChanges="keyboardHidden|orientation|screenSize"
android:configChanges="keyboardHidden|orientation|screenSize"> android:parentActivityName=".ui.MainActivity">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -27,9 +26,9 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="https"
android:host="hitomi.la" android:host="hitomi.la"
android:pathPrefix="/galleries" /> android:pathPrefix="/galleries"
android:scheme="https" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -38,9 +37,9 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="https"
android:host="히요비.asia" android:host="히요비.asia"
android:pathPrefix="/reader" /> android:pathPrefix="/reader"
android:scheme="https" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -49,9 +48,9 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="https"
android:host="xn--9w3b15m8vo.asia" android:host="xn--9w3b15m8vo.asia"
android:pathPrefix="/reader" /> android:pathPrefix="/reader"
android:scheme="https" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -60,9 +59,9 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="https"
android:host="e-hentai.org" android:host="e-hentai.org"
android:pathPrefix="/g" /> android:pathPrefix="/g"
android:scheme="https" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -71,9 +70,9 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="http"
android:host="hitomi.la" android:host="hitomi.la"
android:pathPrefix="/galleries" /> android:pathPrefix="/galleries"
android:scheme="http" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -82,9 +81,9 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="http"
android:host="히요비.asia" android:host="히요비.asia"
android:pathPrefix="/reader" /> android:pathPrefix="/reader"
android:scheme="http" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -93,9 +92,9 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="http"
android:host="xn--9w3b15m8vo.asia" android:host="xn--9w3b15m8vo.asia"
android:pathPrefix="/reader" /> android:pathPrefix="/reader"
android:scheme="http" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -104,16 +103,16 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data <data
android:scheme="http"
android:host="e-hentai.org" android:host="e-hentai.org"
android:pathPrefix="/g" /> android:pathPrefix="/g"
android:scheme="http" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".SettingsActivity" android:name=".ui.SettingsActivity"
android:label="@string/settings_title" /> android:label="@string/settings_title" />
<activity <activity
android:name=".MainActivity" android:name=".ui.MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/NoActionBarAppTheme"> android:theme="@style/NoActionBarAppTheme">
<intent-filter> <intent-filter>

View File

@@ -24,7 +24,7 @@ import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list import kotlinx.serialization.list
import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.ReaderItem 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.R
import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.Histories import xyz.quaver.pupil.util.Histories

View File

@@ -6,9 +6,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() { class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {

View File

@@ -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()
}
}

View File

@@ -1,12 +1,12 @@
package xyz.quaver.pupil package xyz.quaver.pupil.ui
import android.Manifest import android.Manifest
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.preference.PreferenceManager
import android.text.* import android.text.*
import android.text.style.AlignmentSpan import android.text.style.AlignmentSpan
import android.view.* import android.view.*
@@ -21,6 +21,7 @@ import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.preference.PreferenceManager
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.arlib.floatingsearchview.FloatingSearchView import com.arlib.floatingsearchview.FloatingSearchView
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
@@ -40,6 +41,8 @@ import kotlinx.serialization.list
import kotlinx.serialization.stringify import kotlinx.serialization.stringify
import ru.noties.markwon.Markwon import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.* import xyz.quaver.hitomi.*
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.types.TagSuggestion import xyz.quaver.pupil.types.TagSuggestion
@@ -51,6 +54,7 @@ import java.net.URL
import java.util.* import java.util.*
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@@ -65,9 +69,16 @@ class MainActivity : AppCompatActivity() {
private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>() private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
private var query = "" private var query = ""
set(value) {
field = value
findViewById<SearchInputView>(R.id.search_bar_text)
.setText(query, TextView.BufferType.EDITABLE)
}
private var mode = Mode.SEARCH private var mode = Mode.SEARCH
private val SETTINGS = 45162 private val REQUEST_SETTINGS = 45162
private val REQUEST_LOCK = 561
private var galleryIDs: Deferred<List<Int>>? = null private var galleryIDs: Deferred<List<Int>>? = null
private var totalItems = 0 private var totalItems = 0
@@ -81,6 +92,8 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
startActivityForResult(Intent(this, LockActivity::class.java), REQUEST_LOCK)
checkPermissions() checkPermissions()
val preference = PreferenceManager.getDefaultSharedPreferences(this) val preference = PreferenceManager.getDefaultSharedPreferences(this)
@@ -110,19 +123,11 @@ class MainActivity : AppCompatActivity() {
initView() initView()
} }
override fun onDestroy() {
super.onDestroy()
if (cacheDir.exists())
cacheDir.deleteRecursively()
}
override fun onBackPressed() { override fun onBackPressed() {
when { when {
main_drawer_layout.isDrawerOpen(GravityCompat.START) -> main_drawer_layout.closeDrawer(GravityCompat.START) main_drawer_layout.isDrawerOpen(GravityCompat.START) -> main_drawer_layout.closeDrawer(GravityCompat.START)
query.isNotEmpty() -> runOnUiThread { query.isNotEmpty() -> runOnUiThread {
query = "" query = ""
findViewById<SearchInputView>(R.id.search_bar_text).setText(query, TextView.BufferType.EDITABLE)
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
@@ -142,6 +147,7 @@ class MainActivity : AppCompatActivity() {
WindowManager.LayoutParams.FLAG_SECURE) WindowManager.LayoutParams.FLAG_SECURE)
else else
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
super.onResume() super.onResume()
} }
@@ -186,7 +192,7 @@ class MainActivity : AppCompatActivity() {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when(requestCode) { when(requestCode) {
SETTINGS -> { REQUEST_SETTINGS -> {
runOnUiThread { runOnUiThread {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
@@ -194,6 +200,10 @@ class MainActivity : AppCompatActivity() {
loadBlocks() loadBlocks()
} }
} }
REQUEST_LOCK -> {
if (resultCode != Activity.RESULT_OK)
finish()
}
} }
} }
@@ -352,8 +362,7 @@ class MainActivity : AppCompatActivity() {
onChipClickedHandler.add { onChipClickedHandler.add {
runOnUiThread { runOnUiThread {
query = it.toQuery() query = it.toQuery()
this@MainActivity.findViewById<SearchInputView>(R.id.search_bar_text) currentPage = 0
.setText(query, TextView.BufferType.EDITABLE)
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
@@ -679,7 +688,7 @@ class MainActivity : AppCompatActivity() {
setOnMenuItemClickListener { setOnMenuItemClickListener {
when(it.itemId) { 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 -> { R.id.main_menu_jump -> {
val preference = PreferenceManager.getDefaultSharedPreferences(context) val preference = PreferenceManager.getDefaultSharedPreferences(context)
val perPage = preference.getString("per_page", "25")!!.toInt() val perPage = preference.getString("per_page", "25")!!.toInt()
@@ -726,7 +735,8 @@ class MainActivity : AppCompatActivity() {
startActivity(intent) startActivity(intent)
} catch (e: Exception) { } 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) favorites.remove(tag)
} }
else { else {
setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star)) setImageDrawable(AnimatedVectorDrawableCompat.create(context,
R.drawable.avd_star
))
(drawable as Animatable).start() (drawable as Animatable).start()
favorites.add(tag) favorites.add(tag)
@@ -880,10 +892,8 @@ class MainActivity : AppCompatActivity() {
} }
private fun cancelFetch() { private fun cancelFetch() {
runBlocking { galleryIDs?.cancel()
galleryIDs?.cancelAndJoin() loadingJob?.cancel()
loadingJob?.cancelAndJoin()
}
} }
private fun clearGalleries() { private fun clearGalleries() {
@@ -992,7 +1002,7 @@ class MainActivity : AppCompatActivity() {
query.isEmpty() and defaultQuery.isEmpty() and (mode == Mode.SEARCH) -> query.isEmpty() and defaultQuery.isEmpty() and (mode == Mode.SEARCH) ->
galleryIDs galleryIDs
else -> 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 -> }.chunked(5).let { chunks ->
for (chunk in chunks) for (chunk in chunks)
chunk.map { galleryID -> chunk.map { galleryID ->
@@ -1009,8 +1019,8 @@ class MainActivity : AppCompatActivity() {
getGalleryBlock(galleryID).apply { getGalleryBlock(galleryID).apply {
this ?: return@apply this ?: return@apply
if (!cache.parentFile.exists()) if (cache.parentFile?.exists() == false)
cache.parentFile.mkdirs() cache.parentFile!!.mkdirs()
cache.writeText(json.stringify(serializer, this)) cache.writeText(json.stringify(serializer, this))
} }
@@ -1024,8 +1034,8 @@ class MainActivity : AppCompatActivity() {
if (!exists()) if (!exists())
try { try {
with(URL(galleryBlock.thumbnails[0]).openConnection() as HttpsURLConnection) { with(URL(galleryBlock.thumbnails[0]).openConnection() as HttpsURLConnection) {
if (!this@apply.parentFile.exists()) if (this@apply.parentFile?.exists() == false)
this@apply.parentFile.mkdirs() this@apply.parentFile!!.mkdirs()
inputStream.copyTo(FileOutputStream(this@apply)) inputStream.copyTo(FileOutputStream(this@apply))
} }

View File

@@ -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<PatternLockView.Dot>?) {
val password = PatternLockUtils.patternToMD5(lock_pattern_view, pattern)
onPatternDrawn?.invoke(password)
}
override fun onProgress(progressPattern: MutableList<PatternLockView.Dot>?) {
}
override fun onStarted() {
}
}

View File

@@ -1,14 +1,14 @@
package xyz.quaver.pupil package xyz.quaver.pupil.ui
import android.app.Application
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.preference.PreferenceManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.Histories import xyz.quaver.pupil.util.Histories
import java.io.File import java.io.File

View File

@@ -1,4 +1,4 @@
package xyz.quaver.pupil package xyz.quaver.pupil.ui
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
@@ -11,9 +11,9 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.vectordrawable.graphics.drawable.Animatable2Compat import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.crashlytics.android.Crashlytics
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.* import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.android.synthetic.main.activity_reader.view.* import kotlinx.android.synthetic.main.activity_reader.view.*
@@ -28,6 +28,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.getGalleryBlock import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.ReaderAdapter import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.util.GalleryDownloader import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.Histories import xyz.quaver.pupil.util.Histories
@@ -72,6 +73,8 @@ class ReaderActivity : AppCompatActivity() {
handleIntent(intent) handleIntent(intent)
Crashlytics.setInt("GalleryID", galleryBlock.id)
if (!::galleryBlock.isInitialized) { if (!::galleryBlock.isInitialized) {
onBackPressed() onBackPressed()
return return
@@ -117,7 +120,7 @@ class ReaderActivity : AppCompatActivity() {
} else { } else {
galleryBlock = Json(JsonConfiguration.Stable).parse( galleryBlock = Json(JsonConfiguration.Stable).parse(
GalleryBlock.serializer(), GalleryBlock.serializer(),
intent.getStringExtra("galleryblock") intent.getStringExtra("galleryblock")!!
) )
} }
} }
@@ -372,10 +375,7 @@ class ReaderActivity : AppCompatActivity() {
reader_recyclerview.layoutManager = LinearLayoutManager(this) reader_recyclerview.layoutManager = LinearLayoutManager(this)
} else { } else {
snapHelper.attachToRecyclerView(reader_recyclerview) snapHelper.attachToRecyclerView(reader_recyclerview)
reader_recyclerview.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false).apply { reader_recyclerview.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
isItemPrefetchEnabled = true
initialPrefetchItemCount = 4
}
} }
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0) (reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)

View File

@@ -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.Bundle
import android.os.Environment
import android.preference.PreferenceManager
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
@@ -15,12 +15,18 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.dialog_default_query.view.* import kotlinx.android.synthetic.main.dialog_default_query.view.*
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.types.Tags
import xyz.quaver.pupil.util.Lock
import xyz.quaver.pupil.util.LockManager
import java.io.File import java.io.File
class SettingsActivity : AppCompatActivity() { class SettingsActivity : AppCompatActivity() {
val REQUEST_LOCK = 38238
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -58,7 +64,25 @@ class SettingsActivity : AppCompatActivity() {
"TB" //really? "TB" //really?
) )
private fun getCacheSize(dir: File) : String { override fun onResume() {
super.onResume()
val lockManager = LockManager(context!!)
findPreference<Preference>("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 size = dir.walk().map { it.length() }.sum()
var suffixIndex = 0 var suffixIndex = 0
@@ -67,20 +91,53 @@ class SettingsActivity : AppCompatActivity() {
suffixIndex++ 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?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey) setPreferencesFromResource(R.xml.root_preferences, rootKey)
with(findPreference<Preference>("app_version")) {
this!!
val manager = context.packageManager
val info = manager.getPackageInfo(context.packageName, 0)
summary = info.versionName
}
with(findPreference<Preference>("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<Preference>("delete_downloads")) { with(findPreference<Preference>("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 { AlertDialog.Builder(context).apply {
setTitle(R.string.warning) setTitle(R.string.warning)
setMessage(R.string.settings_clear_downloads_alert_message) setMessage(R.string.settings_clear_downloads_alert_message)
@@ -92,7 +149,7 @@ class SettingsActivity : AppCompatActivity() {
downloads.clear() downloads.clear()
summary = getCacheSize(dir) summary = getDirSize(dir)
} }
setNegativeButton(android.R.string.no) { _, _ -> } setNegativeButton(android.R.string.no) { _, _ -> }
}.show() }.show()
@@ -101,13 +158,13 @@ class SettingsActivity : AppCompatActivity() {
} }
} }
with(findPreference<Preference>("clear_history")) { with(findPreference<Preference>("clear_history")) {
this ?: return@with this!!
val histories = (activity!!.application as Pupil).histories val histories = (activity!!.application as Pupil).histories
summary = getString(R.string.settings_clear_history_summary, histories.size) summary = getString(R.string.settings_clear_history_summary, histories.size)
setOnPreferenceClickListener { onPreferenceClickListener = Preference.OnPreferenceClickListener {
AlertDialog.Builder(context).apply { AlertDialog.Builder(context).apply {
setTitle(R.string.warning) setTitle(R.string.warning)
setMessage(R.string.settings_clear_history_alert_message) setMessage(R.string.settings_clear_history_alert_message)
@@ -123,7 +180,7 @@ class SettingsActivity : AppCompatActivity() {
} }
with(findPreference<Preference>("default_query")) { with(findPreference<Preference>("default_query")) {
this ?: return@with this!!
val preferences = PreferenceManager.getDefaultSharedPreferences(context) val preferences = PreferenceManager.getDefaultSharedPreferences(context)
@@ -139,7 +196,7 @@ class SettingsActivity : AppCompatActivity() {
val excludeBL = "-male:yaoi" val excludeBL = "-male:yaoi"
val excludeGuro = listOf("-female:guro", "-male:guro") val excludeGuro = listOf("-female:guro", "-male:guro")
setOnPreferenceClickListener { onPreferenceClickListener = Preference.OnPreferenceClickListener {
val dialogView = LayoutInflater.from(context).inflate( val dialogView = LayoutInflater.from(context).inflate(
R.layout.dialog_default_query, R.layout.dialog_default_query,
LinearLayout(context), LinearLayout(context),
@@ -154,7 +211,7 @@ class SettingsActivity : AppCompatActivity() {
with(dialogView.default_query_dialog_language_selector) { with(dialogView.default_query_dialog_language_selector) {
adapter = adapter =
ArrayAdapter<String>( ArrayAdapter(
context, context,
android.R.layout.simple_spinner_dropdown_item, android.R.layout.simple_spinner_dropdown_item,
arrayListOf( arrayListOf(
@@ -234,6 +291,82 @@ class SettingsActivity : AppCompatActivity() {
dialog.show() dialog.show()
true
}
}
with(findPreference<Preference>("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<Preference>("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<Preference>("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 true
} }
} }
@@ -247,4 +380,19 @@ class SettingsActivity : AppCompatActivity() {
return true 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)
}
}
} }

View File

@@ -4,7 +4,7 @@ import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import android.os.Environment import android.util.Log
import android.util.SparseArray import android.util.SparseArray
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
@@ -18,9 +18,9 @@ import kotlinx.serialization.list
import xyz.quaver.hitomi.* import xyz.quaver.hitomi.*
import xyz.quaver.hiyobi.cookie import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.user_agent 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.R
import xyz.quaver.pupil.ReaderActivity import xyz.quaver.pupil.ui.ReaderActivity
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.net.URL import java.net.URL
@@ -52,7 +52,7 @@ class GalleryDownloader(
cache.deleteRecursively() cache.deleteRecursively()
} }
if (!reader.isActive && downloadJob?.isActive != true) if (reader?.isActive == false && downloadJob?.isActive != true)
field = false field = false
downloads.add(galleryBlock.id) downloads.add(galleryBlock.id)
@@ -63,7 +63,7 @@ class GalleryDownloader(
onNotifyChangedHandler?.invoke(value) onNotifyChangedHandler?.invoke(value)
} }
private val reader: Deferred<Reader> private val reader: Deferred<Reader>?
private var downloadJob: Job? = null private var downloadJob: Job? = null
private lateinit var notificationBuilder: NotificationCompat.Builder private lateinit var notificationBuilder: NotificationCompat.Builder
@@ -126,8 +126,8 @@ class GalleryDownloader(
if (reader.isNotEmpty()) { if (reader.isNotEmpty()) {
//Save cache //Save cache
if (!cache.parentFile.exists()) if (cache.parentFile?.exists() == false)
cache.parentFile.mkdirs() cache.parentFile!!.mkdirs()
cache.writeText(json.stringify(serializer, reader)) cache.writeText(json.stringify(serializer, reader))
} }
@@ -140,7 +140,7 @@ class GalleryDownloader(
fun start() { fun start() {
downloadJob = CoroutineScope(Dispatchers.Default).launch { downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader.await() val reader = reader!!.await()
if (reader.isEmpty()) if (reader.isEmpty())
onErrorHandler?.invoke(IOException("Couldn't retrieve Reader")) onErrorHandler?.invoke(IOException("Couldn't retrieve Reader"))
@@ -183,8 +183,8 @@ class GalleryDownloader(
} else } else
setRequestProperty("Referer", getReferer(galleryBlock.id)) setRequestProperty("Referer", getReferer(galleryBlock.id))
if (!cache.parentFile.exists()) if (cache.parentFile?.exists() == false)
cache.parentFile.mkdirs() cache.parentFile!!.mkdirs()
inputStream.copyTo(FileOutputStream(cache)) inputStream.copyTo(FileOutputStream(cache))
} }
@@ -209,8 +209,6 @@ class GalleryDownloader(
} }
} }
onCompleteHandler?.invoke()
Timer(false).schedule(1000) { Timer(false).schedule(1000) {
notificationBuilder notificationBuilder
.setContentTitle(galleryBlock.title) .setContentTitle(galleryBlock.title)
@@ -220,7 +218,7 @@ class GalleryDownloader(
if (download) { if (download) {
File(cacheDir, "imageCache/${galleryBlock.id}").let { File(cacheDir, "imageCache/${galleryBlock.id}").let {
if (it.exists()) { if (it.exists()) {
val target = File(Environment.getExternalStorageDirectory(), "Pupil/${galleryBlock.id}") val target = File(getExternalFilesDir("Pupil"), galleryBlock.id.toString())
if (!target.exists()) if (!target.exists())
target.mkdirs() target.mkdirs()
@@ -231,9 +229,11 @@ class GalleryDownloader(
} }
notificationManager.notify(galleryBlock.id, notificationBuilder.build()) notificationManager.notify(galleryBlock.id, notificationBuilder.build())
}
download = false onCompleteHandler?.invoke()
download = false
}
} }
remove(galleryBlock.id) remove(galleryBlock.id)
@@ -254,7 +254,7 @@ class GalleryDownloader(
fun invokeOnReaderLoaded() { fun invokeOnReaderLoaded() {
CoroutineScope(Dispatchers.Default).launch { CoroutineScope(Dispatchers.Default).launch {
onReaderLoadedHandler?.invoke(reader.await()) onReaderLoadedHandler?.invoke(reader?.await() ?: return@launch)
} }
} }

View File

@@ -2,10 +2,11 @@ package xyz.quaver.pupil.util
import android.content.Context import android.content.Context
import android.os.Environment import android.os.Environment
import android.provider.MediaStore
import java.io.File import java.io.File
fun getCachedGallery(context: Context, galleryID: Int): File { fun getCachedGallery(context: Context, galleryID: Int): File {
return File(Environment.getExternalStorageDirectory(), "Pupil/$galleryID").let { return File(context.getExternalFilesDir("Pupil"), galleryID.toString()).let {
when { when {
it.exists() -> it it.exists() -> it
else -> File(context.cacheDir, "imageCache/$galleryID") else -> File(context.cacheDir, "imageCache/$galleryID")

View File

@@ -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<String, String> {
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<Lock>? = 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
}
}

View File

@@ -1,5 +1,6 @@
package xyz.quaver.pupil.util package xyz.quaver.pupil.util
import android.util.Log
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import java.net.URL import java.net.URL
@@ -19,8 +20,20 @@ fun checkUpdate(url: String, currentVersion: String) : JsonObject? {
if (releases.isEmpty()) if (releases.isEmpty())
return null return null
if (currentVersion != releases[0].jsonObject["tag_name"]?.content) val latestVersion = releases[0].jsonObject["tag_name"]?.content
return releases[0].jsonObject
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
}
}
}
} }

View File

@@ -0,0 +1,8 @@
<!-- drawable/fingerprint.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M17.81,4.47C17.73,4.47 17.65,4.45 17.58,4.41C15.66,3.42 14,3 12,3C10.03,3 8.15,3.47 6.44,4.41C6.2,4.54 5.9,4.45 5.76,4.21C5.63,3.97 5.72,3.66 5.96,3.53C7.82,2.5 9.86,2 12,2C14.14,2 16,2.47 18.04,3.5C18.29,3.65 18.38,3.95 18.25,4.19C18.16,4.37 18,4.47 17.81,4.47M3.5,9.72C3.4,9.72 3.3,9.69 3.21,9.63C3,9.47 2.93,9.16 3.09,8.93C4.08,7.53 5.34,6.43 6.84,5.66C10,4.04 14,4.03 17.15,5.65C18.65,6.42 19.91,7.5 20.9,8.9C21.06,9.12 21,9.44 20.78,9.6C20.55,9.76 20.24,9.71 20.08,9.5C19.18,8.22 18.04,7.23 16.69,6.54C13.82,5.07 10.15,5.07 7.29,6.55C5.93,7.25 4.79,8.25 3.89,9.5C3.81,9.65 3.66,9.72 3.5,9.72M9.75,21.79C9.62,21.79 9.5,21.74 9.4,21.64C8.53,20.77 8.06,20.21 7.39,19C6.7,17.77 6.34,16.27 6.34,14.66C6.34,11.69 8.88,9.27 12,9.27C15.12,9.27 17.66,11.69 17.66,14.66A0.5,0.5 0 0,1 17.16,15.16A0.5,0.5 0 0,1 16.66,14.66C16.66,12.24 14.57,10.27 12,10.27C9.43,10.27 7.34,12.24 7.34,14.66C7.34,16.1 7.66,17.43 8.27,18.5C8.91,19.66 9.35,20.15 10.12,20.93C10.31,21.13 10.31,21.44 10.12,21.64C10,21.74 9.88,21.79 9.75,21.79M16.92,19.94C15.73,19.94 14.68,19.64 13.82,19.05C12.33,18.04 11.44,16.4 11.44,14.66A0.5,0.5 0 0,1 11.94,14.16A0.5,0.5 0 0,1 12.44,14.66C12.44,16.07 13.16,17.4 14.38,18.22C15.09,18.7 15.92,18.93 16.92,18.93C17.16,18.93 17.56,18.9 17.96,18.83C18.23,18.78 18.5,18.96 18.54,19.24C18.59,19.5 18.41,19.77 18.13,19.82C17.56,19.93 17.06,19.94 16.92,19.94M14.91,22C14.87,22 14.82,22 14.78,22C13.19,21.54 12.15,20.95 11.06,19.88C9.66,18.5 8.89,16.64 8.89,14.66C8.89,13.04 10.27,11.72 11.97,11.72C13.67,11.72 15.05,13.04 15.05,14.66C15.05,15.73 16,16.6 17.13,16.6C18.28,16.6 19.21,15.73 19.21,14.66C19.21,10.89 15.96,7.83 11.96,7.83C9.12,7.83 6.5,9.41 5.35,11.86C4.96,12.67 4.76,13.62 4.76,14.66C4.76,15.44 4.83,16.67 5.43,18.27C5.53,18.53 5.4,18.82 5.14,18.91C4.88,19 4.59,18.87 4.5,18.62C4,17.31 3.77,16 3.77,14.66C3.77,13.46 4,12.37 4.45,11.42C5.78,8.63 8.73,6.82 11.96,6.82C16.5,6.82 20.21,10.33 20.21,14.65C20.21,16.27 18.83,17.59 17.13,17.59C15.43,17.59 14.05,16.27 14.05,14.65C14.05,13.58 13.12,12.71 11.97,12.71C10.82,12.71 9.89,13.58 9.89,14.65C9.89,16.36 10.55,17.96 11.76,19.16C12.71,20.1 13.62,20.62 15.03,21C15.3,21.08 15.45,21.36 15.38,21.62C15.33,21.85 15.12,22 14.91,22Z" />
</vector>

View File

@@ -0,0 +1,8 @@
<!-- drawable/lastpass.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M14,12A2,2 0 0,1 16,10A2,2 0 0,1 18,12A2,2 0 0,1 16,14A2,2 0 0,1 14,12M8,12A2,2 0 0,1 10,10A2,2 0 0,1 12,12A2,2 0 0,1 10,14A2,2 0 0,1 8,12M2,12A2,2 0 0,1 4,10A2,2 0 0,1 6,12A2,2 0 0,1 4,14A2,2 0 0,1 2,12M22,5H20V19H22V5Z" />
</vector>

View File

@@ -0,0 +1,8 @@
<!-- drawable/lock_pattern.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M7,3A4,4 0 0,1 11,7C11,8.86 9.73,10.43 8,10.87V13.13C8.37,13.22 8.72,13.37 9.04,13.56L13.56,9.04C13.2,8.44 13,7.75 13,7A4,4 0 0,1 17,3A4,4 0 0,1 21,7A4,4 0 0,1 17,11C16.26,11 15.57,10.8 15,10.45L10.45,15C10.8,15.57 11,16.26 11,17A4,4 0 0,1 7,21A4,4 0 0,1 3,17C3,15.14 4.27,13.57 6,13.13V10.87C4.27,10.43 3,8.86 3,7A4,4 0 0,1 7,3M17,13A4,4 0 0,1 21,17A4,4 0 0,1 17,21A4,4 0 0,1 13,17A4,4 0 0,1 17,13M17,15A2,2 0 0,0 15,17A2,2 0 0,0 17,19A2,2 0 0,0 19,17A2,2 0 0,0 17,15Z" />
</vector>

View File

@@ -0,0 +1,64 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.LockActivity">
<FrameLayout
android:id="@+id/lock_content"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/lock_button_layout"/>
<LinearLayout
android:id="@+id/lock_button_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="32dp"
app:layout_constraintTop_toBottomOf="@id/lock_content"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/lock_pattern"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/lock_pattern"
app:backgroundTint="@color/colorPrimary"
app:fabSize="mini"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/lock_pin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/numeric"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
app:backgroundTint="@color/dark_gray"
app:fabSize="mini"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/lock_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/fingerprint"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
app:backgroundTint="@color/dark_gray"
app:fabSize="mini"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/lock_password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/lastpass"
app:backgroundTint="@color/dark_gray"
app:fabSize="mini"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -6,7 +6,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity"> tools:context=".ui.MainActivity">
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -6,7 +6,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/dark_gray" android:background="@color/dark_gray"
tools:context=".ReaderActivity"> tools:context=".ui.ReaderActivity">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.PatternLockFragment">
<com.andrognito.patternlockview.PatternLockView
android:id="@+id/lock_pattern_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
app:aspectRatioEnabled="true"
app:normalStateColor="@color/colorPrimary"
app:correctStateColor="@color/colorPrimaryDark"
app:wrongStateColor="@color/colorAccent"/>
</FrameLayout>

View File

@@ -8,7 +8,8 @@
<string name="search_hint">ギャラリー検索</string> <string name="search_hint">ギャラリー検索</string>
<string name="search_hint_with_page">ギャラリー検索</string> <string name="search_hint_with_page">ギャラリー検索</string>
<string name="settings_clear_image_cache">イメージキャッシュクリア</string> <string name="settings_clear_image_cache">イメージキャッシュクリア</string>
<string name="settings_clear_downloads_summary">サイズ: %1$d%2$s</string> <string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string>
<string name="settings_clear_summary">サイズ: %1$d%2$s</string>
<string name="settings_default_query">デフォルトキーワード</string> <string name="settings_default_query">デフォルトキーワード</string>
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string> <string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
<string name="settings_search_title">検索設定</string> <string name="settings_search_title">検索設定</string>
@@ -66,4 +67,16 @@
<string name="main_open_gallery_by_id_error">エラーが発生しました</string> <string name="main_open_gallery_by_id_error">エラーが発生しました</string>
<string name="settings_storage">ストレージ</string> <string name="settings_storage">ストレージ</string>
<string name="main_drawer_grouop_contact_kakaotalk">カカオトーク</string> <string name="main_drawer_grouop_contact_kakaotalk">カカオトーク</string>
<string name="settings_app_lock">アプリロック</string>
<string name="settings_app_lock_type">アップロックの種類</string>
<string name="settings_app_version_title">バージョン</string>
<string name="settings_lock_biomatrics">生体認識</string>
<string name="settings_lock_confirm">ロック確認のためもう一回入力してください。</string>
<string name="settings_lock_enabled">有効</string>
<string name="settings_lock_fingerprint">指紋</string>
<string name="settings_lock_password">パスワード</string>
<string name="settings_lock_pattern">パターン</string>
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string>
<string name="settings_lock_none">なし</string>
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
</resources> </resources>

View File

@@ -7,7 +7,8 @@
<string name="search_hint_with_page">갤러리 검색</string> <string name="search_hint_with_page">갤러리 검색</string>
<string name="settings_default_query">기본 검색어</string> <string name="settings_default_query">기본 검색어</string>
<string name="settings_clear_image_cache">이미지 캐시 정리하기</string> <string name="settings_clear_image_cache">이미지 캐시 정리하기</string>
<string name="settings_clear_downloads_summary">사용량: %1$d%2$s</string> <string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?</string>
<string name="settings_clear_summary">사용량: %1$d%2$s</string>
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string> <string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
<string name="settings_search_title">검색 설정</string> <string name="settings_search_title">검색 설정</string>
<string name="settings_title">설정</string> <string name="settings_title">설정</string>
@@ -66,4 +67,16 @@
<string name="main_open_gallery_by_id_error">갤러리를 찾지 못했습니다</string> <string name="main_open_gallery_by_id_error">갤러리를 찾지 못했습니다</string>
<string name="settings_storage">저장 공간</string> <string name="settings_storage">저장 공간</string>
<string name="main_drawer_grouop_contact_kakaotalk">카카오톡 오픈채팅방</string> <string name="main_drawer_grouop_contact_kakaotalk">카카오톡 오픈채팅방</string>
<string name="settings_app_lock">앱 잠금</string>
<string name="settings_app_lock_type">앱 잠금 종류</string>
<string name="settings_app_version_title">앱 버전</string>
<string name="settings_lock_biomatrics">생체 인식</string>
<string name="settings_lock_confirm">잠금 확인을 위해 한번 더 입력해주세요</string>
<string name="settings_lock_enabled">사용 중</string>
<string name="settings_lock_fingerprint">지문</string>
<string name="settings_lock_password">비밀번호</string>
<string name="settings_lock_pattern">패턴</string>
<string name="settings_lock_wrong_confirm">잠금이 일치하지 않습니다. 다시 시도하세요.</string>
<string name="settings_lock_none">없음</string>
<string name="settings_lock_remove_message">잠금을 해제할까요?</string>
</resources> </resources>

View File

@@ -80,23 +80,38 @@
<string name="reader_notification_error">Download error</string> <string name="reader_notification_error">Download error</string>
<string name="settings_title">Settings</string> <string name="settings_title">Settings</string>
<string name="settings_app_version_title">App version</string>
<string name="settings_search_title">Search Settings</string> <string name="settings_search_title">Search Settings</string>
<string name="settings_galleries_per_page">Galleries per page</string> <string name="settings_galleries_per_page">Galleries per page</string>
<string name="settings_default_query">Default query</string> <string name="settings_default_query">Default query</string>
<string name="settings_storage">Storage</string> <string name="settings_storage">Storage</string>
<string name="settings_clear_image_cache">Clear image cache</string> <string name="settings_clear_image_cache">Clear image cache</string>
<string name="settings_clear_downloads_summary">Currently using %1$d%2$s</string> <string name="settings_clear_cache_alert_message">Deleting cache can affect image loading speed. Do you want to continue?</string>
<string name="settings_clear_summary">Currently using %1$d%2$s</string>
<string name="settings_clear_downloads">Clear downloads</string> <string name="settings_clear_downloads">Clear downloads</string>
<string name="settings_clear_downloads_alert_message">Delete all downloaded galleries.\nDo you want to continue?</string> <string name="settings_clear_downloads_alert_message">Delete all downloaded galleries.\nDo you want to continue?</string>
<string name="settings_clear_history">Clear history</string> <string name="settings_clear_history">Clear history</string>
<string name="settings_clear_history_alert_message">Do you want to clear histories?</string> <string name="settings_clear_history_alert_message">Do you want to clear histories?</string>
<string name="settings_clear_history_summary">%1$d histories saved</string> <string name="settings_clear_history_summary">%1$d histories saved</string>
<string name="settings_app_lock">App lock</string>
<string name="settings_app_lock_type">App lock type</string>
<string name="settings_miscellaneous_title">Miscellaneous</string> <string name="settings_miscellaneous_title">Miscellaneous</string>
<string name="settings_use_hiyobi_title">Use hiyobi.me</string> <string name="settings_use_hiyobi_title">Use hiyobi.me</string>
<string name="settings_use_hiyobi_summary">Load images from hiyobi.me to improve loading speed (if available)</string> <string name="settings_use_hiyobi_summary">Load images from hiyobi.me to improve loading speed (if available)</string>
<string name="settings_security_mode_title">Enable security mode</string> <string name="settings_security_mode_title">Enable security mode</string>
<string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string> <string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string>
<string name="settings_lock_none">None</string>
<string name="settings_lock_pattern">Pattern</string>
<string name="settings_lock_pin" translatable="false">PIN</string>
<string name="settings_lock_password">Password</string>
<string name="settings_lock_biomatrics">Biomatrics</string>
<string name="settings_lock_fingerprint">Fingerprint</string>
<string name="settings_lock_enabled">Enabled</string>
<string name="settings_lock_confirm">Input same lock once more to confirm Lock</string>
<string name="settings_lock_remove_message">Do you want to remove lock?</string>
<string name="settings_lock_wrong_confirm">Lock is different from last one. Please try again.</string>
<string name="default_query_dialog_title">Set default query</string> <string name="default_query_dialog_title">Set default query</string>
<string name="default_query_dialog_language">Language: </string> <string name="default_query_dialog_language">Language: </string>
<string name="default_query_dialog_filter_BL">Filter BL</string> <string name="default_query_dialog_filter_BL">Filter BL</string>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
app:title="@string/settings_lock_pattern"
app:key="lock_pattern"/>
<Preference
app:title="@string/settings_lock_pin"
app:key="lock_pin"/>
<Preference
app:title="@string/settings_lock_password"
app:key="lock_password"/>
<PreferenceCategory
app:title="@string/settings_lock_biomatrics">
<Preference
app:title="@string/settings_lock_fingerprint"
app:key="lock_fingerprint"/>
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@@ -2,6 +2,10 @@
<androidx.preference.PreferenceScreen <androidx.preference.PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
app:title="@string/settings_app_version_title"
app:key="app_version"/>
<PreferenceCategory <PreferenceCategory
app:title="@string/settings_search_title"> app:title="@string/settings_search_title">
@@ -24,6 +28,10 @@
<PreferenceCategory <PreferenceCategory
app:title="@string/settings_storage"> app:title="@string/settings_storage">
<Preference
app:title="Clear cache"
app:key="delete_cache"/>
<Preference <Preference
app:title="@string/settings_clear_downloads" app:title="@string/settings_clear_downloads"
app:key="delete_downloads"/> app:key="delete_downloads"/>
@@ -34,6 +42,15 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory
app:title="@string/settings_app_lock">
<Preference
app:title="@string/settings_app_lock_type"
app:key="app_lock"/>
</PreferenceCategory>
<PreferenceCategory <PreferenceCategory
app:title="@string/settings_miscellaneous_title"> app:title="@string/settings_miscellaneous_title">

View File

@@ -1,15 +1,24 @@
package xyz.quaver.hitomi package xyz.quaver.hitomi
import org.junit.Test import org.junit.Test
import java.io.File import java.net.InetAddress
import java.net.URL import java.net.UnknownHostException
class UnitTest { class UnitTest {
@Test @Test
fun 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 @Test