Compare commits

..

94 Commits

Author SHA1 Message Date
Pupil
3159c343c1 Merge pull request #34 from tom5079/development
Version 4.2-beta1
2019-12-13 20:10:55 +09:00
tom5079
ceaa930623 bug fix 2019-12-13 20:03:11 +09:00
tom5079
6a8539106b bug fix 2019-12-13 20:01:45 +09:00
tom5079
7a24c3c08e bug fix 2019-12-13 19:50:14 +09:00
tom5079
251abeb090 Merge remote-tracking branch 'origin/development' into development 2019-12-13 19:43:42 +09:00
Pupil
a61fe9f98c Merge pull request #33 from tom5079/Pupil-29
Pupil-29
2019-12-13 19:42:44 +09:00
tom5079
d29c7bf91a Apply update on startup 2019-12-13 19:30:19 +09:00
tom5079
ed4911c441 Updated serialization library 2019-12-13 18:39:12 +09:00
tom5079
d40b4f3748 Added update logic for outdated readers 2019-12-12 20:14:55 +09:00
tom5079
f3c4fe1914 Merge remote-tracking branch 'origin/development' into development 2019-12-11 20:23:45 +09:00
tom5079
55ee841bd0 resolves #31 2019-12-11 20:23:18 +09:00
tom5079
657fb488ee fixed #31 on libpupil
#TODO: fix app side to completely resolve the issue
2019-12-11 20:23:18 +09:00
tom5079
4eef0b93fb bug fix 2019-12-11 20:23:17 +09:00
Pupil
f2be56435c Merge pull request #32 from tom5079/Pupil-31 2019-12-11 20:08:45 +09:00
tom5079
fa6b3ad7ba resolves #31 2019-12-11 20:03:55 +09:00
tom5079
52c05e6888 fixed #31 on libpupil
#TODO: fix app side to completely resolve the issue
2019-12-11 19:52:25 +09:00
tom5079
865bf0ba83 Added code for differentiating readers 2019-12-09 10:33:26 +09:00
tom5079
3f827d1bad bug fix 2019-12-09 10:15:53 +09:00
tom5079
0561d5f55c Added code for saved reader 2019-12-09 09:36:36 +09:00
Pupil
1bf2e1dacc Merge pull request #30 from tom5079/Pupil-24
fixed #24
2019-12-08 18:45:21 +09:00
tom5079
db5a221b56 kotlin plugin update 2019-12-08 18:10:35 +09:00
tom5079
295285f132 Turned off development only option 2019-12-02 19:04:09 +09:00
tom5079
5052b6c074 Removed folder opening feature due to its unstability 2019-12-02 18:55:38 +09:00
tom5079
f98f45dc54 Pupil-24 Absence of backing up favorites feature 2019-12-01 16:58:29 +09:00
tom5079
8d16950f46 Issue #27 fix 2019-11-30 16:10:47 +09:00
tom5079
74033b9f4a Issue #27 fix 2019-11-30 16:10:09 +09:00
tom5079
e497d47374 Fix for bug caused by changed hiyobi domain 2019-11-30 15:10:25 +09:00
tom5079
a97af59260 Potential fix for memory issues 2019-11-30 15:09:53 +09:00
tom5079
2197de98ea Potential fix for too large bitmap crash 2019-11-25 19:39:17 +09:00
tom5079
c004c7f71a Fixed bug fetching old galleries from hiyobi 2019-11-15 19:47:09 +09:00
tom5079
69fc3ad4e8 typo 2019-11-02 21:50:03 +09:00
tom5079
678a8f0914 Merge pull request #21 from tom5079/development
Fixed bug with missing hash
2019-11-02 21:46:11 +09:00
tom5079
08c4c0bf1f Fixed bug with missing hash
Version 4.1
2019-11-02 21:45:27 +09:00
tom5079
f2a2656837 Merge pull request #20 from tom5079/development
Development
2019-11-02 20:30:04 +09:00
tom5079
2011572270 Merge remote-tracking branch 'origin/development' into development 2019-11-02 20:29:24 +09:00
tom5079
3b682667e1 Fixed bug caused by updated hitomi server structure
Version 4.0
2019-11-02 20:25:03 +09:00
tom5079
6da8de6463 Merge pull request #19 from tom5079/master
merge readme
2019-11-02 20:08:28 +09:00
tom5079
039d415871 Update README.md
r/engrish
2019-08-31 23:59:41 +09:00
tom5079
776f53bde0 Update README.md 2019-08-30 22:28:07 +09:00
tom5079
58e535595e Update README.md 2019-08-30 22:27:32 +09:00
tom5079
96ad5f6a6c Update README.md 2019-08-30 22:27:08 +09:00
tom5079
043f7bedd8 Added quick download/delete 2019-08-30 15:24:51 +09:00
tom5079
69bcd8f7c0 Merge pull request #17 from tom5079/development
Version 3.2
2019-08-29 12:11:22 +09:00
tom5079
8a58564812 Version 3.2 2019-08-29 12:10:51 +09:00
tom5079
d346cf431f Merge conflicts 2019-08-29 11:47:01 +09:00
tom5079
c0bce4f3b1 Merge conflicts 2019-08-29 11:43:20 +09:00
tom5079
94d258ddbb Merge conflicts 2019-08-29 11:42:31 +09:00
tom5079
6bdba49284 added missing file 2019-08-29 11:41:08 +09:00
tom5079
9b99baf4bc Added missing files 2019-08-29 11:39:01 +09:00
tom5079
5ad2a538bc Merge pull request #16 from tom5079/master
merge to development branch
2019-08-29 11:29:27 +09:00
tom5079
28703e9bf2 added missing file 2019-08-29 11:25:08 +09:00
tom5079
e664efefe9 Fixed image broken after download finishes 2019-08-28 09:21:36 +09:00
tom5079
27a8694938 Fixed not moving cached gallery to download folder 2019-08-28 09:01:39 +09:00
tom5079
e0a6102d4d Fixed viewer flickering
Added moving with volume button
Added nomedia
Added help link to error snackbar
2019-08-28 08:56:29 +09:00
tom5079
2afdc5591a Added gallery details
Added dark mode
2019-07-21 20:51:50 +09:00
tom5079
8eed4b67c3 Added gallery details
Added dark mode
2019-07-21 20:51:50 +09:00
tom5079
edacef0f2b Update ignore feature added
Bug fixed
2019-07-14 10:53:42 +09:00
tom5079
d28894f8cd Update ignore feature added
Bug fixed
2019-07-14 10:53:42 +09:00
tom5079
ee8e921e1a Image loading optimized
Adds gallery to history when opened directly by gallery ID
Fixed blurred image
2019-07-11 21:24:25 +09:00
tom5079
480d4b1e9a Image loading optimized
Adds gallery to history when opened directly by gallery ID
Fixed blurred image
2019-07-11 21:24:25 +09:00
tom5079
a79c023220 Fixed invisible favorite tag bug 2019-07-07 17:30:03 +09:00
tom5079
efc50df243 Fixed invisible favorite tag bug 2019-07-07 17:30:03 +09:00
tom5079
905ea766b1 App crash fix 2019-07-07 16:36:42 +09:00
tom5079
bce26f4557 App crash fix 2019-07-07 16:36:42 +09:00
tom5079
a74b2c9b49 Version fix 2019-07-07 15:26:22 +09:00
tom5079
22bdf61bb3 Version fix 2019-07-07 15:26:22 +09:00
tom5079
1d812487a6 Merge remote-tracking branch 'origin/development' into development 2019-07-07 15:25:54 +09:00
tom5079
dfb78bed69 Version fix 2019-07-07 15:25:36 +09:00
tom5079
c64b6f112b Merge branch 'master' into development 2019-07-07 15:24:53 +09:00
tom5079
bd88a8a8d3 Merge remote-tracking branch 'origin/development' into development 2019-07-07 15:23:42 +09:00
tom5079
5ccc96aeb9 UI update
Added sort by popularity functionality
Added auto update
2019-07-07 15:21:56 +09:00
tom5079
ef72d10344 Create README.md 2019-07-03 20:49:34 +09:00
tom5079
573f0b40d1 Create LICENSE 2019-07-03 20:49:13 +09:00
tom5079
48f49edb19 Update LICENSE 2019-07-03 20:46:43 +09:00
tom5079
aa22d9fdd8 Create LICENSE 2019-07-03 20:45:56 +09:00
tom5079
ec98e4e9a4 Update LICENSE 2019-07-03 20:44:57 +09:00
tom5079
5b10a781a6 Added license 2019-07-03 20:44:10 +09:00
tom5079
29637b234c Merge pull request #13 from tom5079/master
updated license
2019-07-03 20:07:21 +09:00
tom5079
34dc238ef1 Create LICENSE 2019-07-03 19:49:40 +09:00
tom5079
3c2675e650 Create README.md 2019-07-03 19:47:13 +09:00
tom5079
3992a07340 Search algorithm improved
Language settings in default tag fixed
2019-07-03 19:40:19 +09:00
tom5079
2046d87031 Utilizing Glide
Fixed Reader FAB icon
Changed to use gallery id instead of galleryblock to open Reader
2019-06-30 22:04:35 +09:00
tom5079
0618d8c6f8 Merge pull request #12 from tom5079/development
Version 2.11.1
2019-06-23 23:33:23 +09:00
tom5079
5bfc27835b Fixed app icon
Version 2.11.1
2019-06-23 23:32:38 +09:00
tom5079
cdc545ea32 Fixed bug for older devices
Hoping that the viewer crashing bug is fixed
Version 2.11
2019-06-23 23:09:01 +09:00
tom5079
449db97a2b Merge pull request #11 from tom5079/development
Pupil v2.10
2019-06-23 16:17:49 +09:00
tom5079
e01380090d Added lock 2019-06-23 10:27:07 +09:00
tom5079
6d1505241e Migrate to Android 29
Re-Added Cache clear to prevent deleting downloading images
2019-06-23 00:23:17 +09:00
tom5079
f303e49e97 Memory optimization 2019-06-14 20:01:32 +09:00
tom5079
0e6b50e302 Merge pull request #10 from tom5079/development
Version 2.9
2019-06-13 22:08:57 +09:00
tom5079
868af1e6a2 Changed to non-scrolling horizontal image view 2019-06-13 21:45:31 +09:00
tom5079
34f7b111ee Bug fix 2019-06-13 21:29:57 +09:00
tom5079
df27907c57 Bug fix 2019-06-13 21:18:26 +09:00
tom5079
75583b9e65 Added URL support
Added Firebase
Added progressbar to the full screen horizontal reader view
2019-06-13 21:05:52 +09:00
81 changed files with 2131 additions and 713 deletions

5
.gitignore vendored
View File

@@ -13,4 +13,7 @@
.externalNativeBuild
#Github pages
/gh-pages
/gh-pages
#Private files
/app/google-services.json

View File

@@ -1,18 +1,12 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<AndroidXmlCodeStyleSettings>
<option name="USE_CUSTOM_SETTINGS" value="true" />
<option name="ARRANGEMENT_SETTINGS_MIGRATED_TO_191" value="true" />
</AndroidXmlCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<XML>
<option name="XML_KEEP_LINE_BREAKS" value="false" />
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
</XML>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
@@ -23,6 +17,7 @@
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -33,6 +28,7 @@
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -44,6 +40,7 @@
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@@ -54,6 +51,7 @@
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@@ -64,6 +62,7 @@
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -74,6 +73,7 @@
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -84,6 +84,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
@@ -95,6 +96,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
@@ -106,6 +108,7 @@
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>

2
.idea/misc.xml generated
View File

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

View File

@@ -1,2 +1,27 @@
# Pupil
Hitomi.la viewer for Android
![Banner](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/pupil-banner.png?raw=true)
*Pupil, Hitomi.la viewer for Android*
# Screenshot
![Main Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/main-screenshot.png?raw=true)
*Main Screen*
![Reader Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/reader-screenshot.png?raw=true)
*Reader Screen*
Images are censored to be SFW
# Installation
Go [Releases page](https://github.com/tom5079/Pupil/releases) and get latest version or
Visit [github page](https://tom5079.github.io/Pupil/) (only available in Korean)
or Build app yourself
# Manual
[Manual](https://tom5079.github.io/Pupil/2019/06/06/manual-kr.html) is only available in Korean. Consider using translator.
# Contribution
Any kind of contribution is appriciated. Feel free to leave PR!

View File

@@ -13,8 +13,8 @@ android {
applicationId "xyz.quaver.pupil"
minSdkVersion 16
targetSdkVersion 29
versionCode 21
versionName "3.0"
versionCode 31
versionName "4.2-beta1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
@@ -26,11 +26,16 @@ android {
}
buildTypes.each {
it.buildConfigField('boolean', 'PRERELEASE', 'false')
it.buildConfigField('boolean', 'CENSOR', 'false')
}
}
kotlinOptions {
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
@@ -41,18 +46,21 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.1.0-beta01'
implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "androidx.biometric:biometric:1.0.0"
implementation 'com.android.support:multidex:1.0.3'
implementation 'com.google.android.material:material:1.0.0'
implementation 'com.google.firebase:firebase-core:17.0.0'
implementation 'com.google.firebase:firebase-perf:18.0.1'
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation 'com.google.android.material:material:1.2.0-alpha02'
implementation 'com.google.firebase:firebase-core:17.2.1'
implementation 'com.google.firebase:firebase-perf:19.0.3'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'com.github.clans:fab:1.6.4'

View File

@@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":21,"versionName":"2.12","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":31,"versionName":"4.2-beta1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

View File

@@ -25,15 +25,21 @@ import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.JsonObject
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import xyz.quaver.hitomi.fetchNozomi
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.getReader
import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.ui.LockActivity
import xyz.quaver.pupil.util.getDownloadDirectory
import xyz.quaver.pupil.util.updateOldReaderGalleries
import java.io.File
import java.net.URL
import javax.net.ssl.HttpsURLConnection
@@ -49,9 +55,8 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
Log.i("PUPILD", getDownloadDirectory(appContext).absolutePath ?: "")
assertEquals("xyz.quaver.pupil", appContext.packageName)
Log.d("Pupil", fetchNozomi().first.size.toString())
}
@Test
@@ -60,17 +65,15 @@ class ExampleInstrumentedTest {
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
activityTestRule.launchActivity(Intent())
while(true);
}
@Test
fun test_doSearch() {
val reader = getReader(1426382)
val reader = getReader( 1426382)
val data: ByteArray
with(URL(reader.readerItems[0].url).openConnection() as HttpsURLConnection) {
with(URL(createImgList(1426382, reader)[0].path).openConnection() as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
@@ -79,4 +82,38 @@ class ExampleInstrumentedTest {
Log.d("Pupil", data.size.toString())
}
}
@UseExperimental(ImplicitReflectionSerializer::class)
@Test
fun test_deleteCodeFromReader() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val json = Json(JsonConfiguration.Stable)
listOf(
getDownloadDirectory(context),
File(context.cacheDir, "imageCache")
).forEach { root ->
root.listFiles()?.forEach gallery@{ gallery ->
val reader = json.parseJson(File(gallery, "reader.json").apply {
if (!exists())
return@gallery
}.readText())
.jsonObject.toMutableMap()
Log.d("PUPILD", gallery.name)
reader.remove("code")
File(gallery, "reader.json").writeText(JsonObject(reader).toString())
}
}
}
@Test
fun test_updateOldReader() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
updateOldReaderGalleries(context)
}
}

View File

@@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="xyz.quaver.pupil">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<application
android:name=".Pupil"
@@ -15,7 +17,20 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
tools:replace="android:theme">
<provider
android:authorities="${applicationId}.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
<activity android:name=".ui.LockActivity"/>
<activity

View File

@@ -18,13 +18,10 @@
package xyz.quaver.pupil
import android.app.DownloadManager
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
@@ -33,7 +30,11 @@ import androidx.preference.PreferenceManager
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.updateOldReaderGalleries
import java.io.File
class Pupil : MultiDexApplication() {
@@ -76,6 +77,15 @@ class Pupil : MultiDexApplication() {
preference.edit().putBoolean("channel_created", true).apply()
}
AppCompatDelegate.setDefaultNightMode(when (preference.getBoolean("dark_mode", false)) {
true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO
})
CoroutineScope(Dispatchers.IO).launch {
updateOldReaderGalleries(this@Pupil)
}
super.onCreate()
}

View File

@@ -18,22 +18,22 @@
package xyz.quaver.pupil.adapters
import android.app.AlertDialog
import android.graphics.BitmapFactory
import android.graphics.drawable.Drawable
import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.LinearLayout
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.daimajia.swipe.SwipeLayout
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.item_galleryblock.view.*
import kotlinx.coroutines.CoroutineScope
@@ -44,18 +44,21 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.getCachedGallery
import xyz.quaver.pupil.util.wordCapitalize
import java.io.File
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.concurrent.schedule
class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerSwipeAdapter<RecyclerView.ViewHolder>(), SwipeAdapterInterface {
enum class ViewType {
NEXT,
@@ -65,8 +68,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
private lateinit var favorites: Histories
inner class GalleryViewHolder(val view: CardView) : RecyclerView.ViewHolder(view) {
fun bind(holder: GalleryViewHolder, item: Pair<GalleryBlock, Deferred<String>>) {
inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: Pair<GalleryBlock, Deferred<String>>) {
with(view) {
val resources = context.resources
val languages = resources.getStringArray(R.array.languages).map {
@@ -83,11 +86,16 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
CoroutineScope(Dispatchers.Main).launch {
val cache = thumbnail.await()
Glide.with(holder.view)
glide
.load(cache)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.error(R.drawable.image_broken_variant)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}
.fitCenter()
.into(galleryblock_thumbnail)
}
@@ -95,12 +103,19 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
val readerCache = { File(getCachedGallery(context, galleryBlock.id), "reader.json") }
val imageCache = { File(getCachedGallery(context, galleryBlock.id), "images") }
try {
Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText())
} catch(e: Exception) {
readerCache.invoke().delete()
}
if (readerCache.invoke().exists()) {
val reader = Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText())
with(galleryblock_progressbar) {
max = reader.readerItems.size
max = reader.galleryInfo.size
progress = imageCache.invoke().list()?.size ?: 0
visibility = View.VISIBLE
@@ -125,7 +140,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
if (visibility == View.GONE) {
val reader = Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText())
max = reader.readerItems.size
max = reader.galleryInfo.size
visibility = View.VISIBLE
}
@@ -146,8 +161,6 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
}
} else
view.galleryblock_progress_complete.visibility = View.INVISIBLE
null
}
}
}
@@ -163,19 +176,6 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
artists.isNotEmpty() -> View.VISIBLE
else -> View.GONE
}
setOnClickListener {
if (artists.size > 1) {
AlertDialog.Builder(context).apply {
setAdapter(ArrayAdapter(context, android.R.layout.select_dialog_item, artists)) { _, index ->
for (callback in onChipClickedHandler)
callback.invoke(Tag("artist", artists[index]))
}
}.show()
} else {
for(callback in onChipClickedHandler)
callback.invoke(Tag("artist", artists.first()))
}
}
}
with(galleryblock_series) {
text =
@@ -186,31 +186,8 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
series.isNotEmpty() -> View.VISIBLE
else -> View.GONE
}
setOnClickListener {
setOnClickListener {
if (series.size > 1) {
AlertDialog.Builder(context).apply {
setAdapter(ArrayAdapter(context, android.R.layout.select_dialog_item, series)) { _, index ->
for (callback in onChipClickedHandler)
callback.invoke(Tag("series", series[index]))
}
}.show()
} else {
for(callback in onChipClickedHandler)
callback.invoke(Tag("series", series.first()))
}
}
}
}
with(galleryblock_type) {
text = resources.getString(R.string.galleryblock_type, galleryBlock.type).wordCapitalize()
setOnClickListener {
setOnClickListener {
for(callback in onChipClickedHandler)
callback.invoke(Tag("type", galleryBlock.type))
}
}
}
galleryblock_type.text = resources.getString(R.string.galleryblock_type, galleryBlock.type).wordCapitalize()
with(galleryblock_language) {
text =
resources.getString(R.string.galleryblock_language, languages[galleryBlock.language])
@@ -218,48 +195,37 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
galleryBlock.language.isNotEmpty() -> View.VISIBLE
else -> View.GONE
}
setOnClickListener {
setOnClickListener {
for(callback in onChipClickedHandler)
callback.invoke(Tag("language", galleryBlock.language))
}
}
}
galleryblock_tag_group.removeAllViews()
galleryBlock.relatedTags.forEach {
val tag = Tag.parse(it).let { tag ->
when {
tag.area != null -> tag
else -> Tag("tag", it)
galleryblock_tag_group.addView(Chip(context).apply {
val tag = Tag.parse(it).let { tag ->
when {
tag.area != null -> tag
else -> Tag("tag", it)
}
}
}
val chip = LayoutInflater.from(context)
.inflate(R.layout.tag_chip, this, false) as Chip
val icon = when(tag.area) {
"male" -> {
chip.setChipBackgroundColorResource(R.color.material_blue_700)
chip.setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white)
chipIcon = when(tag.area) {
"male" -> {
setChipBackgroundColorResource(R.color.material_blue_700)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white)
}
"female" -> {
setChipBackgroundColorResource(R.color.material_pink_600)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white)
}
else -> null
}
"female" -> {
chip.setChipBackgroundColorResource(R.color.material_pink_600)
chip.setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white)
text = tag.tag.wordCapitalize()
setOnClickListener {
for (callback in onChipClickedHandler)
callback.invoke(tag)
}
else -> null
}
chip.chipIcon = icon
chip.text = tag.tag.wordCapitalize()
chip.setOnClickListener {
for (callback in onChipClickedHandler)
callback.invoke(tag)
}
galleryblock_tag_group.addView(chip)
})
}
galleryblock_id.text = galleryBlock.id.toString()
@@ -311,19 +277,12 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
}
}
private fun String.wordCapitalize() : String {
val result = ArrayList<String>()
for (word in this.split(" "))
result.add(word.capitalize())
return result.joinToString(" ")
}
private val refreshTasks = HashMap<GalleryViewHolder, TimerTask>()
val completeFlag = SparseBooleanArray()
val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
var onDownloadClickedHandler: ((Int) -> Unit)? = null
var onDeleteClickedHandler: ((Int) -> Unit)? = null
var showNext = false
var showPrev = false
@@ -349,8 +308,47 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GalleryViewHolder)
holder.bind(holder, galleries[position-(if (showPrev) 1 else 0)])
if (holder is GalleryViewHolder) {
val gallery = galleries[position-(if (showPrev) 1 else 0)]
holder.bind(gallery)
with(holder.view.galleryblock_primary) {
setOnClickListener {
holder.view.performClick()
}
setOnLongClickListener {
holder.view.performLongClick()
}
}
holder.view.galleryblock_download.setOnClickListener {
onDownloadClickedHandler?.invoke(position)
}
holder.view.galleryblock_delete.setOnClickListener {
onDeleteClickedHandler?.invoke(position)
}
mItemManger.bindView(holder.view, position)
holder.view.galleryblock_swipe_layout.addSwipeListener(object: SwipeLayout.SwipeListener {
override fun onStartOpen(layout: SwipeLayout?) {
mItemManger.closeAllExcept(layout)
holder.view.galleryblock_download.text = when(GalleryDownloader.get(gallery.first.id)) {
null -> holder.view.context.getString(R.string.main_download)
else -> holder.view.context.getString(android.R.string.cancel)
}
}
override fun onClose(layout: SwipeLayout?) {}
override fun onHandRelease(layout: SwipeLayout?, xvel: Float, yvel: Float) {}
override fun onOpen(layout: SwipeLayout?) {}
override fun onStartClose(layout: SwipeLayout?) {}
override fun onUpdate(layout: SwipeLayout?, leftOffset: Int, topOffset: Int) {}
})
}
}
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
@@ -376,4 +374,6 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
else -> ViewType.GALLERY
}.ordinal
}
override fun getSwipeLayoutResourceId(position: Int) = R.id.galleryblock_swipe_layout
}

View File

@@ -23,39 +23,44 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.engine.DiskCacheStrategy
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.getCachedGallery
import java.io.File
class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
class ReaderAdapter(private val glide: RequestManager,
private val galleryID: Int,
private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
var isFullScreen = false
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
LayoutInflater.from(parent.context).inflate(
return LayoutInflater.from(parent.context).inflate(
R.layout.item_reader, parent, false
).let {
return ViewHolder(it)
ViewHolder(it)
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val progressDrawable = CircularProgressDrawable(holder.view.context).apply {
strokeWidth = 10f
centerRadius = 100f
start()
}
holder.view as ImageView
Glide.with(holder.view)
.load(images[position])
glide
.load(File(getCachedGallery(holder.view.context, galleryID), images[position]))
.dontTransform()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.placeholder(progressDrawable)
.error(R.drawable.image_broken_variant)
.into(holder.view as ImageView)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}
.fitCenter()
.into(holder.view)
}
override fun getItemCount() = images.size

View File

@@ -0,0 +1,48 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.adapters
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import xyz.quaver.pupil.BuildConfig
class ThumbnailAdapter(private val glide: RequestManager, private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ImageView(parent.context))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
glide
.load(thumbnails[position])
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}
.fitCenter()
.into(holder.view)
}
override fun getItemCount() = thumbnails.size
}

View File

@@ -0,0 +1,275 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout.LayoutParams
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_galleryblock.*
import kotlinx.android.synthetic.main.gallery_details.view.*
import kotlinx.android.synthetic.main.item_gallery_details.view.*
import kotlinx.coroutines.*
import xyz.quaver.hitomi.Gallery
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.getGallery
import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.adapters.ThumbnailAdapter
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.util.wordCapitalize
class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(context) {
private val languages = context.resources.getStringArray(R.array.languages).map {
it.split("|").let { split ->
Pair(split[0], split[1])
}
}.toMap()
private val glide = Glide.with(context)
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.dialog_galleryblock)
window?.attributes.apply {
this ?: return@apply
width = LayoutParams.MATCH_PARENT
height = LayoutParams.MATCH_PARENT
}
with(gallery_fab) {
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.arrow_right))
setOnClickListener {
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
putExtra("galleryID", galleryID)
})
(context.applicationContext as Pupil).histories.add(galleryID)
}
}
CoroutineScope(Dispatchers.IO).launch {
try {
val gallery = getGallery(galleryID)
launch(Dispatchers.Main) {
gallery_progressbar.visibility = View.GONE
gallery_title.text = gallery.title
gallery_artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() }
with(gallery_type) {
text = gallery.type.wordCapitalize()
setOnClickListener {
gallery.type.let {
when (it) {
"artist CG" -> "artistcg"
"game CG" -> "gamecg"
else -> it
}
}.let {
onChipClickedHandler.forEach { handler ->
handler.invoke(Tag("type", it))
}
}
}
}
Glide.with(context)
.load(gallery.cover)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}.into(gallery_cover)
addDetails(gallery)
addThumbnails(gallery)
addRelated(gallery)
}
} catch (e: Exception) {
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).show()
}
}
}
private fun addDetails(gallery: Gallery) {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
gallery_details.setText(R.string.gallery_details)
listOf(
R.string.gallery_artists,
R.string.gallery_groups,
R.string.gallery_language,
R.string.gallery_series,
R.string.gallery_characters,
R.string.gallery_tags
).zip(
listOf(
gallery.artists.map { Tag("artist", it) },
gallery.groups.map { Tag("group", it) },
listOf(gallery.language).map { Tag("language", it) },
gallery.series.map { Tag("series", it) },
gallery.characters.map { Tag("character", it) },
gallery.tags.map {
Tag.parse(it).let { tag ->
when {
tag.area != null -> tag
else -> Tag("tag", it)
}
}
}
)
).filter {
(_, content) -> content.isNotEmpty()
}.forEach { (title, content) ->
inflater.inflate(R.layout.item_gallery_details, gallery_details_contents, false).apply {
gallery_details_type.setText(title)
content.forEach { tag ->
gallery_details_tags.addView(
Chip(context).apply {
chipIcon = when(tag.area) {
"male" -> {
setChipBackgroundColorResource(R.color.material_blue_700)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white)
}
"female" -> {
setChipBackgroundColorResource(R.color.material_pink_600)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white)
}
else -> null
}
text = when (tag.area) {
"language" -> languages[tag.tag]
else -> tag.tag.wordCapitalize()
}
setOnClickListener {
onChipClickedHandler.forEach { handler ->
handler.invoke(tag)
}
}
}
)
}
}.let {
gallery_details_contents.addView(it)
}
}
}.let {
gallery_contents.addView(it)
}
}
private fun addThumbnails(gallery: Gallery) {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
gallery_details.setText(R.string.gallery_thumbnails)
RecyclerView(context).apply {
layoutManager = GridLayoutManager(context, 3)
adapter = ThumbnailAdapter(glide, gallery.thumbnails)
}.let {
gallery_details_contents.addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
}
}.let {
gallery_contents.addView(it)
}
}
private fun addRelated(gallery: Gallery) {
val inflater = LayoutInflater.from(context)
val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
val adapter = GalleryBlockAdapter(glide, galleries).apply {
onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
handler.invoke(tag)
}
}
}
CoroutineScope(Dispatchers.Main).launch {
gallery.related.forEachIndexed { i, galleryID ->
async(Dispatchers.IO) {
getGalleryBlock(galleryID)
}.let {
val galleryBlock = it.await() ?: return@let
galleries.add(Pair(galleryBlock, GlobalScope.async { galleryBlock.thumbnails.first() }))
adapter.notifyItemInserted(i)
}
}
}
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
gallery_details.setText(R.string.gallery_related)
RecyclerView(context).apply {
layoutManager = LinearLayoutManager(context)
this.adapter = adapter
ItemClickSupport.addTo(this)
.setOnItemClickListener { _, position, _ ->
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
putExtra("galleryID", galleries[position].first.id)
})
(context.applicationContext as Pupil).histories.add(galleries[position].first.id)
}
.setOnItemLongClickListener { _, position, _ ->
GalleryDialog(context, galleries[position].first.id).apply {
onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
}
}.show()
true
}
}.let {
gallery_details_contents.addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
}
}.let {
gallery_contents.addView(it)
}
}
}

View File

@@ -19,6 +19,7 @@
package xyz.quaver.pupil.ui
import android.app.Activity
import android.app.AlertDialog
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.andrognito.patternlockview.PatternLockView
@@ -35,7 +36,18 @@ class LockActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lock)
val lockManager = LockManager(this)
val lockManager = try {
LockManager(this)
} catch (e: Exception) {
AlertDialog.Builder(this).apply {
setTitle(R.string.warning)
setMessage(R.string.lock_corrupted)
setPositiveButton(android.R.string.ok) { _, _ ->
finish()
}
}.show()
return
}
val mode = intent.getStringExtra("mode")
@@ -46,9 +58,10 @@ class LockActivity : AppCompatActivity() {
when(mode) {
null -> {
if (lockManager.empty()) {
if (lockManager.isEmpty()) {
setResult(RESULT_OK)
finish()
return
}
}
"add_lock" -> {

View File

@@ -25,14 +25,16 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.drawable.Animatable
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.text.*
import android.text.style.AlignmentSpan
import android.view.*
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.widget.EditText
import android.widget.ImageView
import android.widget.LinearLayout
@@ -42,7 +44,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat
import androidx.preference.PreferenceManager
@@ -50,11 +51,11 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.arlib.floatingsearchview.FloatingSearchView
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import com.arlib.floatingsearchview.util.view.SearchInputView
import com.bumptech.glide.Glide
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main_content.*
import kotlinx.android.synthetic.main.dialog_galleryblock.view.*
import kotlinx.coroutines.*
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
@@ -126,7 +127,21 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivityForResult(Intent(this, LockActivity::class.java), REQUEST_LOCK)
val lockManager = try {
LockManager(this)
} catch (e: Exception) {
android.app.AlertDialog.Builder(this).apply {
setTitle(R.string.warning)
setMessage(R.string.lock_corrupted)
setPositiveButton(android.R.string.ok) { _, _ ->
finish()
}
}.show()
return
}
if (lockManager.isNotEmpty())
startActivityForResult(Intent(this, LockActivity::class.java), REQUEST_LOCK)
checkPermissions()
@@ -191,10 +206,10 @@ class MainActivity : AppCompatActivity() {
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
return when(keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (currentPage < maxPage) {
KeyEvent.KEYCODE_VOLUME_UP -> {
if (currentPage > 0) {
runOnUiThread {
currentPage++
currentPage--
cancelFetch()
clearGalleries()
@@ -205,10 +220,10 @@ class MainActivity : AppCompatActivity() {
true
}
KeyEvent.KEYCODE_VOLUME_UP -> {
if (currentPage > 0) {
KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (currentPage < maxPage) {
runOnUiThread {
currentPage--
currentPage++
cancelFetch()
clearGalleries()
@@ -243,6 +258,12 @@ class MainActivity : AppCompatActivity() {
private fun checkUpdate() {
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
val ignoreUpdateUntil = preferences.getLong("ignore_update_until", 0)
if (ignoreUpdateUntil > System.currentTimeMillis())
return
fun extractReleaseNote(update: JsonObject, locale: String) : String {
val markdown = update["body"]!!.content
@@ -297,6 +318,16 @@ class MainActivity : AppCompatActivity() {
val msg = extractReleaseNote(update, Locale.getDefault().language)
setMessage(Markwon.create(context).toMarkdown(msg))
setPositiveButton(android.R.string.yes) { _, _ ->
if (!this@MainActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
AlertDialog.Builder(this@MainActivity).apply {
setTitle(R.string.warning)
setMessage(R.string.update_no_permission)
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
return@setPositiveButton
}
val request = DownloadManager.Request(Uri.parse(url)).apply {
setDescription(getString(R.string.update_notification_description))
setTitle(getString(R.string.app_name))
@@ -308,17 +339,29 @@ class MainActivity : AppCompatActivity() {
registerReceiver(object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val install = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
setDataAndType(manager.getUriForDownloadedFile(id), manager.getMimeTypeForDownloadedFile(id))
}
try {
val install = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
setDataAndType(manager.getUriForDownloadedFile(id), manager.getMimeTypeForDownloadedFile(id))
}
startActivity(install)
unregisterReceiver(this)
startActivity(install)
unregisterReceiver(this)
} catch (e: Exception) {
AlertDialog.Builder(this@MainActivity).apply {
setTitle(R.string.update_failed)
setMessage(R.string.update_failed_message)
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
}
}
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
setNegativeButton(android.R.string.no) { _, _ ->}
setNegativeButton(R.string.ignore_update) { _, _ ->
preferences.edit()
.putLong("ignore_update_until", System.currentTimeMillis() + 604800000)
.apply()
}
}
launch(Dispatchers.Main) {
@@ -328,7 +371,7 @@ class MainActivity : AppCompatActivity() {
}
private fun checkPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
if (!hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE))
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 13489)
}
@@ -405,7 +448,7 @@ class MainActivity : AppCompatActivity() {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
}
R.id.main_drawer_kakaotalk -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.kakaotalk))))
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord))))
}
}
}
@@ -435,7 +478,6 @@ class MainActivity : AppCompatActivity() {
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
@@ -461,6 +503,8 @@ class MainActivity : AppCompatActivity() {
intent.putExtra("galleryID", gallery.id)
startActivity(intent)
histories.add(gallery.id)
} catch (e: Exception) {
Snackbar.make(main_layout,
R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show()
@@ -479,7 +523,7 @@ class MainActivity : AppCompatActivity() {
private fun setupRecyclerView() {
with(main_recyclerview) {
adapter = GalleryBlockAdapter(galleries).apply {
adapter = GalleryBlockAdapter(Glide.with(this@MainActivity), galleries).apply {
onChipClickedHandler.add {
runOnUiThread {
query = it.toQuery()
@@ -491,10 +535,66 @@ class MainActivity : AppCompatActivity() {
loadBlocks()
}
}
onDownloadClickedHandler = { position ->
val galleryID = galleries[position].first.id
if (!completeFlag.get(galleryID, false)) {
val downloader = GalleryDownloader.get(galleryID)
if (downloader == null)
GalleryDownloader(context, galleryID, true).start()
else {
downloader.cancel()
downloader.clearNotification()
}
}
closeAllItems()
}
onDeleteClickedHandler = { position ->
val galleryID = galleries[position].first.id
CoroutineScope(Dispatchers.Default).launch {
with(GalleryDownloader[galleryID]) {
this?.cancelAndJoin()
this?.clearNotification()
}
val cache = File(cacheDir, "imageCache/${galleryID}")
val data = getCachedGallery(context, galleryID)
cache.deleteRecursively()
data.deleteRecursively()
downloads.remove(galleryID)
if (this@MainActivity.mode == Mode.DOWNLOAD) {
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
histories.remove(galleryID)
if (this@MainActivity.mode == Mode.HISTORY) {
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
completeFlag.put(galleryID, false)
}
closeAllItems()
}
}
ItemClickSupport.addTo(this)
.setOnItemClickListener { _, position, v ->
if (v !is CardView)
return@setOnItemClickListener
@@ -506,66 +606,27 @@ class MainActivity : AppCompatActivity() {
startActivity(intent)
histories.add(gallery.id)
}.setOnItemLongClickListener { recyclerView, position, v ->
}.setOnItemLongClickListener { _, position, v ->
if (v !is CardView)
return@setOnItemLongClickListener true
val gallery = galleries[position].first
val view = LayoutInflater.from(this@MainActivity)
.inflate(R.layout.dialog_galleryblock, recyclerView, false)
val galleryID = galleries[position].first.id
val dialog = AlertDialog.Builder(this@MainActivity).apply {
setView(view)
}.create()
GalleryDialog(this@MainActivity, galleryID).apply {
onChipClickedHandler.add {
runOnUiThread {
query = it.toQuery()
currentPage = 0
with(view.main_dialog_download) {
text = when(GalleryDownloader.get(gallery.id)) {
null -> getString(R.string.reader_fab_download)
else -> getString(R.string.reader_fab_download_cancel)
}
isEnabled = !(adapter as GalleryBlockAdapter).completeFlag.get(gallery.id, false)
setOnClickListener {
val downloader = GalleryDownloader.get(gallery.id)
if (downloader == null)
GalleryDownloader(context, gallery.id, true).start()
else {
downloader.cancel()
downloader.clearNotification()
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
dialog.dismiss()
dismiss()
}
}
view.main_dialog_delete.setOnClickListener {
CoroutineScope(Dispatchers.Default).launch {
with(GalleryDownloader[gallery.id]) {
this?.cancelAndJoin()
this?.clearNotification()
}
val cache = File(cacheDir, "imageCache/${gallery.id}")
val data = getCachedGallery(context, gallery.id)
cache.deleteRecursively()
data.deleteRecursively()
downloads.remove(gallery.id)
if (mode == Mode.DOWNLOAD) {
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
}
(adapter as GalleryBlockAdapter).completeFlag.put(gallery.id, false)
}
dialog.dismiss()
}
dialog.show()
}.show()
true
}
@@ -721,6 +782,7 @@ class MainActivity : AppCompatActivity() {
val text = next.findViewById<TextView>(R.id.text_next).apply {
text = getString(R.string.main_move, currentPage+2)
}
if (absDist < 360) {
next.layoutParams.height = (absDist/2).roundToInt()
icon.layoutParams.height = (absDist/2).roundToInt()
@@ -728,8 +790,7 @@ class MainActivity : AppCompatActivity() {
text.layoutParams.width = absDist.roundToInt()
target = -1
}
else {
} else {
next.layoutParams.height = 180
icon.layoutParams.height = 180
icon.rotation = 0f
@@ -792,7 +853,7 @@ class MainActivity : AppCompatActivity() {
s ?: return
if (s.any { it.isUpperCase() })
s.replace(0, s.length, s.toString().toLowerCase())
s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault()))
}
})
@@ -900,11 +961,10 @@ class MainActivity : AppCompatActivity() {
else
setImageResource(R.drawable.ic_star_empty)
visibility = View.VISIBLE
rotation = 0f
isEnabled = true
setColorFilter(ContextCompat.getColor(context, R.color.material_orange_500))
isClickable = true
setOnClickListener {
val favorites = Tags(json.parse(serializer, favoritesFile.readText()))

View File

@@ -29,8 +29,6 @@ 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 {

View File

@@ -18,9 +18,11 @@
package xyz.quaver.pupil.ui
import android.Manifest
import android.content.Intent
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.view.*
import androidx.appcompat.app.AlertDialog
@@ -31,6 +33,7 @@ import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide
import com.crashlytics.android.Crashlytics
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.*
@@ -46,6 +49,7 @@ import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.util.hasPermission
class ReaderActivity : AppCompatActivity() {
@@ -217,6 +221,23 @@ class ReaderActivity : AppCompatActivity() {
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
//currentPage is 1-based
return when(keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(currentPage-2, 0)
true
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(currentPage, 0)
true
}
else -> super.onKeyDown(keyCode, event)
}
}
private fun initDownloader() {
var d: GalleryDownloader? = GalleryDownloader.get(galleryID)
@@ -228,16 +249,16 @@ class ReaderActivity : AppCompatActivity() {
CoroutineScope(Dispatchers.Main).launch {
title = it.title
with(reader_download_progressbar) {
max = it.readerItems.size
max = it.galleryInfo.size
progress = 0
}
with(reader_progressbar) {
max = it.readerItems.size
max = it.galleryInfo.size
progress = 0
}
gallerySize = it.readerItems.size
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.readerItems.size}"
gallerySize = it.galleryInfo.size
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.galleryInfo.size}"
}
}
onProgressHandler = {
@@ -259,7 +280,12 @@ class ReaderActivity : AppCompatActivity() {
}
}
onErrorHandler = {
Snackbar.make(reader_layout, it.message ?: it.javaClass.name, Snackbar.LENGTH_INDEFINITE).show()
Snackbar
.make(reader_layout, it.message ?: it.javaClass.name, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.reader_help) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.error_help))))
}
.show()
downloader.download = false
}
onCompleteHandler = {
@@ -307,7 +333,7 @@ class ReaderActivity : AppCompatActivity() {
private fun initView() {
with(reader_recyclerview) {
adapter = ReaderAdapter(images)
adapter = ReaderAdapter(Glide.with(this@ReaderActivity), galleryID, images)
addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@@ -337,7 +363,7 @@ class ReaderActivity : AppCompatActivity() {
scrollMode(false)
fullscreen(true)
} else {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPosition(currentPage)
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPosition(currentPage) //Moves to next page because currentPage is 1-based indexing
}
}
}
@@ -345,6 +371,17 @@ class ReaderActivity : AppCompatActivity() {
with(reader_fab_download) {
setImageResource(R.drawable.ic_download)
setOnClickListener {
if (!this@ReaderActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
AlertDialog.Builder(this@ReaderActivity).apply {
setTitle(R.string.warning)
setMessage(R.string.update_no_permission)
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
return@setOnClickListener
}
downloader.download = !downloader.download
if (!downloader.download)

View File

@@ -31,10 +31,16 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_default_query.view.*
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.parseList
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tags
@@ -42,10 +48,13 @@ import xyz.quaver.pupil.util.Lock
import xyz.quaver.pupil.util.LockManager
import xyz.quaver.pupil.util.getDownloadDirectory
import java.io.File
import java.nio.charset.Charset
import java.util.*
class SettingsActivity : AppCompatActivity() {
val REQUEST_LOCK = 38238
val REQUEST_RESTORE = 16546
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -153,7 +162,7 @@ class SettingsActivity : AppCompatActivity() {
with(findPreference<Preference>("delete_downloads")) {
this!!
val dir = getDownloadDirectory(context)!!
val dir = getDownloadDirectory(context)
summary = getDirSize(dir)
@@ -277,7 +286,7 @@ class SettingsActivity : AppCompatActivity() {
s ?: return
if (s.any { it.isUpperCase() })
s.replace(0, s.length, s.toString().toLowerCase())
s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault()))
}
})
}
@@ -338,6 +347,50 @@ class SettingsActivity : AppCompatActivity() {
true
}
}
with(findPreference<Preference>("dark_mode")) {
this!!
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
AppCompatDelegate.setDefaultNightMode(when (newValue as Boolean) {
true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO
})
true
}
}
with(findPreference<Preference>("backup")) {
this!!
onPreferenceClickListener = Preference.OnPreferenceClickListener {
File(ContextCompat.getDataDir(context), "favorites.json").copyTo(
File(getDownloadDirectory(context), "favorites.json"),
true
)
Snackbar.make(this@SettingsFragment.listView, R.string.settings_backup_snackbar, Snackbar.LENGTH_LONG)
.show()
true
}
}
with(findPreference<Preference>("restore")) {
this!!
onPreferenceClickListener = Preference.OnPreferenceClickListener {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/*"
}
activity?.startActivityForResult(intent, (activity as SettingsActivity).REQUEST_RESTORE)
true
}
}
}
}
@@ -401,6 +454,7 @@ class SettingsActivity : AppCompatActivity() {
return true
}
@UseExperimental(ImplicitReflectionSerializer::class)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when(requestCode) {
REQUEST_LOCK -> {
@@ -412,6 +466,33 @@ class SettingsActivity : AppCompatActivity() {
.commitAllowingStateLoss()
}
}
REQUEST_RESTORE -> {
if (resultCode == Activity.RESULT_OK) {
val uri = data?.data ?: return
try {
val json = contentResolver.openInputStream(uri).use { inputStream ->
inputStream!!
inputStream.readBytes().toString(Charset.defaultCharset())
}
(application as Pupil).favorites.addAll(Json.parseList<Int>(json).also {
Snackbar.make(
window.decorView,
getString(R.string.settings_restore_successful, it.size),
Snackbar.LENGTH_LONG
).show()
})
} catch (e: Exception) {
Snackbar.make(
window.decorView,
R.string.settings_restore_failed,
Snackbar.LENGTH_LONG
).show()
}
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}

View File

@@ -27,14 +27,16 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.TaskStackBuilder
import androidx.preference.PreferenceManager
import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.*
import kotlinx.io.IOException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.getReader
import xyz.quaver.hitomi.getReferer
import xyz.quaver.hitomi.urlFromUrlFromHash
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.createImgList
import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
@@ -62,16 +64,17 @@ class GalleryDownloader(
field = true
notificationManager.notify(galleryID, notificationBuilder.build())
val data = getCachedGallery(this, galleryID)
val cache = File(cacheDir, "imageCache/$galleryID")
if (reader?.isActive == false && downloadJob?.isActive != true) {
val data = File(getDownloadDirectory(this), galleryID.toString())
val cache = File(cacheDir, "imageCache/$galleryID")
if (File(cache, "images").exists() && !data.exists()) {
cache.copyRecursively(data, true)
cache.deleteRecursively()
}
if (File(cache, "images").exists() && !data.exists()) {
cache.copyRecursively(data, true)
cache.deleteRecursively()
}
if (reader?.isActive == false && downloadJob?.isActive != true)
field = false
}
downloads.add(galleryID)
} else {
@@ -81,7 +84,7 @@ class GalleryDownloader(
onNotifyChangedHandler?.invoke(value)
}
private val reader: Deferred<Reader>?
private val reader: Deferred<Reader?>?
private var downloadJob: Job? = null
private lateinit var notificationBuilder: NotificationCompat.Builder
@@ -110,14 +113,17 @@ class GalleryDownloader(
//Check cache
val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "reader.json")
try {
json.parse(serializer, cache.readText())
} catch(e: Exception) {
cache.delete()
}
if (cache.exists()) {
val cached = json.parse(serializer, cache.readText())
if (cached.readerItems.isNotEmpty()) {
useHiyobi = when {
cached.readerItems[0].url.contains("hitomi.la") -> false
else -> true
}
if (cached.galleryInfo.isNotEmpty()) {
useHiyobi = cached.code == Reader.Code.HIYOBI
onReaderLoadedHandler?.invoke(cached)
@@ -128,14 +134,11 @@ class GalleryDownloader(
//Cache doesn't exist. Load from internet
val reader = when {
useHiyobi -> {
xyz.quaver.hiyobi.getReader(galleryID).let {
when {
it.readerItems.isEmpty() -> {
useHiyobi = false
getReader(galleryID)
}
else -> it
}
try {
xyz.quaver.hiyobi.getReader(galleryID)
} catch(e: Exception) {
useHiyobi = false
getReader(galleryID)
}
}
else -> {
@@ -143,7 +146,7 @@ class GalleryDownloader(
}
}
if (reader.readerItems.isNotEmpty()) {
if (reader.galleryInfo.isNotEmpty()) {
//Save cache
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
@@ -153,7 +156,9 @@ class GalleryDownloader(
reader
} catch (e: Exception) {
Reader("", listOf())
Crashlytics.logException(e)
onErrorHandler?.invoke(e)
null
}
}
}
@@ -162,27 +167,32 @@ class GalleryDownloader(
fun start() {
downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader!!.await()
val reader = reader!!.await() ?: return@launch
if (reader.readerItems.isEmpty()) {
onErrorHandler?.invoke(IOException(getString(R.string.unable_to_connect)))
return@launch
}
notificationBuilder.setContentTitle(reader.title)
val list = ArrayList<String>()
onReaderLoadedHandler?.invoke(reader)
notificationBuilder
.setProgress(reader.readerItems.size, 0, false)
.setContentText("0/${reader.readerItems.size}")
.setProgress(reader.galleryInfo.size, 0, false)
.setContentText("0/${reader.galleryInfo.size}")
reader.readerItems.chunked(4).forEachIndexed { chunkIndex, chunked ->
chunked.mapIndexed { i, it ->
reader.galleryInfo.chunked(4).forEachIndexed { chunkIndex, chunked ->
chunked.mapIndexed { i, galleryInfo ->
val index = chunkIndex*4+i
async(Dispatchers.IO) {
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
val url = when(useHiyobi) {
true -> createImgList(galleryID, reader)[index].path
false -> when {
(!galleryInfo.hash.isNullOrBlank()) and (galleryInfo.haswebp == 1) ->
urlFromUrlFromHash(galleryID, galleryInfo, "webp")
else ->
urlFromUrlFromHash(galleryID, galleryInfo)
}
}
val name = "$index".padStart(4, '0')
val ext = url.split('.').last()
@@ -216,7 +226,7 @@ class GalleryDownloader(
notificationManager.notify(galleryID, notificationBuilder.build())
}
cache.absolutePath
"images/$name.$ext"
}
}.forEach {
list.add(it.await())
@@ -226,8 +236,8 @@ class GalleryDownloader(
onProgressHandler?.invoke(index)
notificationBuilder
.setProgress(reader.readerItems.size, index, false)
.setContentText("$index/${reader.readerItems.size}")
.setProgress(reader.galleryInfo.size, index, false)
.setContentText("$index/${reader.galleryInfo.size}")
if (download)
notificationManager.notify(galleryID, notificationBuilder.build())
@@ -307,16 +317,11 @@ class GalleryDownloader(
notificationBuilder = NotificationCompat.Builder(this, "download").apply {
setContentTitle(getString(R.string.reader_loading))
setContentText(getString(R.string.reader_notification_text))
setSmallIcon(R.drawable.ic_download)
setSmallIcon(android.R.drawable.stat_sys_download)
setContentIntent(pendingIntent)
setProgress(0, 0, true)
priority = NotificationCompat.PRIORITY_LOW
}
CoroutineScope(Dispatchers.Default).launch {
while (reader == null) ;
notificationBuilder.setContentTitle(reader.await().title)
}
}
}

View File

@@ -21,8 +21,6 @@ package xyz.quaver.pupil.util
import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.core.content.ContextCompat
import java.io.File
fun getCachedGallery(context: Context, galleryID: Int): File {
@@ -35,9 +33,9 @@ fun getCachedGallery(context: Context, galleryID: Int): File {
}
@Suppress("DEPRECATION")
fun getDownloadDirectory(context: Context): File? {
fun getDownloadDirectory(context: Context): File {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
context.getExternalFilesDir("Pupil")
context.getExternalFilesDir("Pupil")!!
else
File(Environment.getExternalStorageDirectory(), "Pupil")
}

View File

@@ -112,10 +112,12 @@ class LockManager(base: Context): ContextWrapper(base) {
}
}
fun empty(): Boolean {
fun isEmpty(): Boolean {
return locks.isNullOrEmpty()
}
fun isNotEmpty(): Boolean = !isEmpty()
fun contains(type: Lock.Type): Boolean {
return locks?.any { it.type == type } ?: false
}

View File

@@ -0,0 +1,35 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.util
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
fun Context.hasPermission(permission: String) =
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
fun String.wordCapitalize() : String {
val result = ArrayList<String>()
for (word in this.split(" "))
result.add(word.capitalize())
return result.joinToString(" ")
}

View File

@@ -18,8 +18,14 @@
package xyz.quaver.pupil.util
import android.content.Context
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.internal.EnumSerializer
import kotlinx.serialization.json.*
import xyz.quaver.availableInHiyobi
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.BuildConfig
import java.io.File
import java.net.URL
fun getReleases(url: String) : JsonArray {
@@ -40,19 +46,74 @@ fun checkUpdate(url: String) : JsonObject? {
return releases.firstOrNull {
if (BuildConfig.PRERELEASE) {
BuildConfig.VERSION_NAME != it.jsonObject["tag_name"]?.content
true
} else {
it.jsonObject["prerelease"]?.boolean == false &&
BuildConfig.VERSION_NAME != (it.jsonObject["tag_name"]?.content ?: "")
it.jsonObject["prerelease"]?.boolean == false
}
}?.jsonObject
}?.let {
if (it.jsonObject["tag_name"]?.content == BuildConfig.VERSION_NAME)
null
else
it.jsonObject
}
}
fun getApkUrl(releases: JsonObject) : Pair<String?, String?>? {
releases["assets"]?.jsonArray?.forEach {
if (Regex("Pupil-v(\\d+\\.)+\\d+\\.apk").matches(it.jsonObject["name"]?.content ?: ""))
return Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content)
return releases["assets"]?.jsonArray?.firstOrNull {
Regex("Pupil-v.+\\.apk").matches(it.jsonObject["name"]?.content ?: "")
}.let {
if (it == null)
null
else
Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content)
}
}
fun getOldReaderGalleries(context: Context) : List<File> {
val oldGallery = mutableListOf<File>()
listOf(
getDownloadDirectory(context),
File(context.cacheDir, "imageCache")
).forEach { root ->
root.listFiles()?.forEach { gallery ->
File(gallery, "reader.json").let { readerFile ->
if (!readerFile.exists())
return@let
Json(JsonConfiguration.Stable).parseJson(readerFile.readText()).jsonObject.let { reader ->
if (!reader.contains("code"))
oldGallery.add(gallery)
}
}
}
}
return null
return oldGallery
}
@UseExperimental(InternalSerializationApi::class)
fun updateOldReaderGalleries(context: Context) {
val json = Json(JsonConfiguration.Stable)
getOldReaderGalleries(context).forEach { gallery ->
val reader = json.parseJson(File(gallery, "reader.json").apply {
if (!exists())
return@forEach
}.readText())
.jsonObject.toMutableMap()
val codeSerializer = EnumSerializer(Reader.Code::class)
reader["code"] = when {
(File(gallery, "images").list()?.
all { !it.endsWith("webp") } ?: return@forEach) &&
availableInHiyobi(gallery.name.toInt()) -> json.toJson(codeSerializer, Reader.Code.HIYOBI)
else -> json.toJson(codeSerializer, Reader.Code.HITOMI)
}
File(gallery, "reader.json").writeText(JsonObject(reader).toString())
}
}

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333">
<path
android:fillColor="#FF000000"
android:pathData="M20,4L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM20,8l-8,5 -8,-5L4,6l8,5 8,-5v2z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333">
<path
android:fillColor="#FF000000"
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="#FF000000"
android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,19h-2v-2h2v2zM15.07,11.25l-0.9,0.92C13.45,12.9 13,13.5 13,15h-2v-0.5c0,-1.1 0.45,-2.1 1.17,-2.83l1.24,-1.26c0.37,-0.36 0.59,-0.86 0.59,-1.41 0,-1.1 -0.9,-2 -2,-2s-2,0.9 -2,2L8,9c0,-2.21 1.79,-4 4,-4s4,1.79 4,4c0,0.88 -0.36,1.68 -0.93,2.25z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333">
<path
android:fillColor="#FF000000"
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333">
<path
android:fillColor="#FF000000"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333">
<path
android:fillColor="#FF000000"
android:pathData="M21,5v6.59l-3,-3.01 -4,4.01 -4,-4 -4,4 -3,-3.01L3,5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01L21,19c0,1.1 -0.9,2 -2,2L5,21c-1.1,0 -2,-0.9 -2,-2v-6.58l3,2.99 4,-4 4,4 4,-3.99z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="#FF000000"
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="#FF000000"
android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/>
</vector>

View File

@@ -0,0 +1,8 @@
<!-- drawable/arrow_right.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="#fff" android:pathData="M4,11V13H16L10.5,18.5L11.92,19.92L19.84,12L11.92,4.08L10.5,5.5L16,11H4Z" />
</vector>

View File

@@ -13,14 +13,14 @@
<path
android:name="path"
android:pathData="M 12 15.39 L 8.24 17.66 L 9.23 13.38 L 5.91 10.5 L 10.29 10.13 L 12 6.09 L 13.71 10.13 L 18.09 10.5 L 14.77 13.38 L 15.76 17.66 M 22 9.24 L 14.81 8.63 L 12 2 L 9.19 8.63 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 Z"
android:fillColor="#000"/>
android:fillColor="@color/material_orange_500"/>
<clip-path
android:name="clip"
android:pathData="M 2 21 L 2 21 L 22 21 L 22 21 Z"/>
<path
android:name="path_1"
android:pathData="M 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 L 14.81 8.62 L 12 2 L 9.19 8.62 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 Z"
android:fillColor="#000"/>
android:fillColor="@color/material_orange_500"/>
</vector>
</aapt:attr>
<target android:name="clip">

View File

@@ -1,3 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
@@ -52,4 +71,4 @@
</set>
</aapt:attr>
</target>
</animated-vector>
</animated-vector>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:width="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#000" android:pathData="M 12 15.39 L 8.24 17.66 L 9.23 13.38 L 5.91 10.5 L 10.29 10.13 L 12 6.09 L 13.71 10.13 L 18.09 10.5 L 14.77 13.38 L 15.76 17.66 M 22 9.24 L 14.81 8.63 L 12 2 L 9.19 8.63 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 Z"/>
<path android:fillColor="@color/material_orange_500" android:pathData="M 12 15.39 L 8.24 17.66 L 9.23 13.38 L 5.91 10.5 L 10.29 10.13 L 12 6.09 L 13.71 10.13 L 18.09 10.5 L 14.77 13.38 L 15.76 17.66 M 22 9.24 L 14.81 8.63 L 12 2 L 9.19 8.63 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 Z"/>
</vector>

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:width="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#000" android:pathData="M 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 L 14.81 8.62 L 12 2 L 9.19 8.62 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 Z"/>
<path android:fillColor="@color/material_orange_500" android:pathData="M 12 17.27 L 18.18 21 L 16.54 13.97 L 22 9.24 L 14.81 8.62 L 12 2 L 9.19 8.62 L 2 9.24 L 7.45 13.97 L 5.82 21 L 12 17.27 Z"/>
</vector>

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<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"
@@ -11,7 +29,29 @@
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/lock_button_layout"/>
app:layout_constraintBottom_toTopOf="@id/lock_fingerprint_layout"/>
<LinearLayout
android:id="@+id/lock_fingerprint_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="32dp"
android:gravity="center"
app:layout_constraintTop_toBottomOf="@id/lock_content"
app:layout_constraintBottom_toTopOf="@id/lock_button_layout">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/lock_fingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/fingerprint"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
app:backgroundTint="@color/dark_gray"
app:fabSize="mini"/>
</LinearLayout>
<LinearLayout
android:id="@+id/lock_button_layout"
@@ -19,7 +59,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="32dp"
app:layout_constraintTop_toBottomOf="@id/lock_content"
app:layout_constraintTop_toBottomOf="@id/lock_fingerprint_layout"
app:layout_constraintBottom_toBottomOf="parent"
android:gravity="center">
@@ -41,16 +81,6 @@
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"
app:srcCompat="@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"

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout
android:id="@+id/main_layout"
xmlns:android="http://schemas.android.com/apk/res/android"
@@ -86,6 +104,7 @@
android:id="@+id/main_searchview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:floatingSearch_backgroundColor="?attr/colorSurface"
app:floatingSearch_searchBarMarginLeft="8dp"
app:floatingSearch_searchBarMarginRight="8dp"
app:floatingSearch_searchBarMarginTop="8dp"

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/reader_layout"
xmlns:app="http://schemas.android.com/apk/res-auto"

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@@ -1,23 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gallery_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="16dp">
android:orientation="vertical">
<Button
android:id="@+id/main_dialog_download"
style="?borderlessButtonStyle"
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/gallery_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"/>
android:layout_height="wrap_content">
<Button
android:id="@+id/main_dialog_delete"
style="?borderlessButtonStyle"
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<ImageView
android:id="@+id/gallery_cover"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/gallery_title"/>
<TextView
android:id="@+id/gallery_title"
style="@style/TextAppearance.AppCompat.Headline"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@id/gallery_cover"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"/>
<TextView
style="@style/TextAppearance.AppCompat.Medium"
android:id="@+id/gallery_artist"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/gallery_title"
app:layout_constraintLeft_toRightOf="@id/gallery_cover"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"/>
<View
android:id="@+id/gallery_padding"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/gallery_artist"
app:layout_constraintBottom_toTopOf="@id/gallery_type"/>
<com.google.android.material.chip.Chip
android:id="@+id/gallery_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/gallery_cover"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/main_dialog_delete"
app:layout_constraintTop_toBottomOf="@id/main_dialog_download"/>
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/gallery_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"/>
</androidx.core.widget.NestedScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/gallery_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/gallery_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:layout_anchor="@id/gallery_toolbar"
app:layout_anchorGravity="bottom|end"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/gallery_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/colorAccent"/>
<LinearLayout
android:id="@+id/gallery_details_contents"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
style="@style/TextAppearance.MaterialComponents.Body2"
android:id="@+id/gallery_details_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="8dp"/>
<com.google.android.material.chip.ChipGroup
android:id="@+id/gallery_details_tags"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipSpacingVertical="8dp"/>
</LinearLayout>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="3"/>

View File

@@ -1,173 +1,225 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:paddingStart="0dp"
android:paddingLeft="0dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
app:cardCornerRadius="8dp"
android:foreground="?attr/selectableItemBackground"
android:focusable="true"
android:clickable="true">
android:clipChildren="true">
<LinearLayout
<com.daimajia.swipe.SwipeLayout
android:id="@+id/galleryblock_swipe_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ProgressBar
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:id="@+id/galleryblock_progressbar"
android:layout_width="match_parent"
android:layout_height="4dp"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/galleryblock_progress_complete"
android:layout_width="match_parent"
android:layout_height="4dp"
android:visibility="invisible"
android:scaleType="fitXY"
android:contentDescription="@string/reader_imageview_description"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/galleryblock_thumbnail"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:contentDescription="@string/galleryblock_thumbnail_description"
android:adjustViewBounds="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
style="@style/TextAppearance.AppCompat.Headline"
android:id="@+id/galleryblock_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
style="@style/TextAppearance.AppCompat.Medium"
android:id="@+id/galleryblock_artist"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_title"/>
<TextView
android:id="@+id/galleryblock_series"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/galleryblock_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
<TextView
android:id="@+id/galleryblock_language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
app:layout_constraintBottom_toTopOf="@id/galleryblock_padding"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
<View
android:id="@+id/galleryblock_padding"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
app:layout_constraintBottom_toTopOf="@id/galleryblock_tag_group"/>
<com.google.android.material.chip.ChipGroup
android:id="@+id/galleryblock_tag_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_padding"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="8dp"
android:background="@android:color/darker_gray"/>
app:drag_edge="right"
app:show_mode="pull_out">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom="8dp"
android:orientation="horizontal">
android:id="@+id/galleryblock_secondary"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="@+id/galleryblock_id"
android:id="@+id/galleryblock_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:layout_height="match_parent"
android:minWidth="70dp"
android:padding="8dp"
android:gravity="center"
android:background="@android:color/holo_blue_dark"
android:textColor="@android:color/white"
android:text="@string/main_download"
android:foreground="?attr/selectableItemBackground"
android:focusable="true"
android:clickable="true"/>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<ImageView
android:id="@+id/galleryblock_favorite"
android:contentDescription="@string/app_name"
android:layout_width="32dp"
android:layout_height="32dp"
app:srcCompat="@drawable/ic_star_empty"
app:backgroundTint="@color/material_orange_500"/>
<TextView
android:id="@+id/galleryblock_delete"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="70dp"
android:padding="8dp"
android:gravity="center"
android:background="@android:color/holo_red_dark"
android:textColor="@android:color/white"
android:text="@string/main_delete"
android:foreground="?attr/selectableItemBackground"
android:focusable="true"
android:clickable="true"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/galleryblock_primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:foreground="?attr/selectableItemBackground"
android:focusable="true"
android:clickable="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ProgressBar
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:id="@+id/galleryblock_progressbar"
android:layout_width="match_parent"
android:layout_height="4dp"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/galleryblock_progress_complete"
android:layout_width="match_parent"
android:layout_height="4dp"
android:visibility="invisible"
android:scaleType="fitXY"
android:contentDescription="@string/reader_imageview_description"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/galleryblock_thumbnail"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:contentDescription="@string/galleryblock_thumbnail_description"
android:adjustViewBounds="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_progressbar"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
style="@style/TextAppearance.AppCompat.Headline"
android:id="@+id/galleryblock_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
style="@style/TextAppearance.AppCompat.Medium"
android:id="@+id/galleryblock_artist"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_title"/>
<TextView
android:id="@+id/galleryblock_series"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/galleryblock_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
<TextView
android:id="@+id/galleryblock_language"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
app:layout_constraintBottom_toTopOf="@id/galleryblock_padding"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
<View
android:id="@+id/galleryblock_padding"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_language"
app:layout_constraintBottom_toTopOf="@id/galleryblock_tag_group"/>
<com.google.android.material.chip.ChipGroup
android:id="@+id/galleryblock_tag_group"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:chipSpacing="2dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_padding"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="8dp"
android:background="@android:color/darker_gray"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom="8dp"
android:orientation="horizontal">
<TextView
android:id="@+id/galleryblock_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"/>
<ImageView
android:id="@+id/galleryblock_favorite"
android:contentDescription="@string/app_name"
android:layout_width="32dp"
android:layout_height="32dp"
app:srcCompat="@drawable/ic_star_empty"/>
</LinearLayout>
</LinearLayout>
</com.daimajia.swipe.SwipeLayout>
</androidx.cardview.widget.CardView>

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical"
android:layout_width="match_parent" android:layout_height="wrap_content">

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:contentDescription="@string/reader_imageview_description"
android:layout_width="match_parent"

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"

View File

@@ -1,3 +1,21 @@
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.chip.Chip
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="24dp"
app:chipIconSize="16dp"
app:chipStartPadding="8dp"
app:chipCornerRadius="100dp"/>

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
@@ -35,7 +53,7 @@
android:title="@string/main_drawer_group_contact_email"
android:icon="@drawable/ic_email"/>
<item android:id="@+id/main_drawer_kakaotalk"
android:title="@string/main_drawer_grouop_contact_kakaotalk"
android:title="@string/main_drawer_grouop_contact_discord"
android:icon="@drawable/ic_message"/>
</menu>
</item>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/gallery_favorite"
android:icon="@drawable/ic_star_empty"
android:title=""
app:showAsAction="ifRoom"/>
<item
android:id="@+id/gallery_download"
android:icon="@drawable/ic_download"
android:title=""
app:showAsAction="always"/>
</menu>

View File

@@ -1,4 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

View File

@@ -1,10 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/reader_menu_favorite"
android:title=""
android:iconTint="@color/material_orange_500"
android:icon="@drawable/avd_star"
app:showAsAction="always"/>

View File

@@ -66,11 +66,11 @@
<string name="main_open_gallery_by_id">ギャラリー番号で見る</string>
<string name="main_open_gallery_by_id_error">エラーが発生しました</string>
<string name="settings_storage">ストレージ</string>
<string name="main_drawer_grouop_contact_kakaotalk">カカオトーク</string>
<string name="main_drawer_grouop_contact_discord">ディスコード</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_biometrics">生体認識</string>
<string name="settings_lock_confirm">ロック確認のためもう一回入力してください。</string>
<string name="settings_lock_enabled">有効</string>
<string name="settings_lock_fingerprint">指紋</string>
@@ -83,4 +83,31 @@
<string name="main_menu_sort">ソート</string>
<string name="main_menu_sort_newest">投稿日時順</string>
<string name="main_menu_sort_popular">人気順</string>
<string name="update_failed">アップデートに失敗しました</string>
<string name="update_failed_message">マニュアルインストールが必要です。APKファイルは</string>
<string name="ignore_update">無視</string>
<string name="lock_corrupted">ロックファイルが破損されています。Pupilを再再インストールしてください。</string>
<string name="update_no_permission">権限がないため自動アップデートを行えません。ホームページで直接ダウンロードしてください。</string>
<string name="settings_dark_mode_title">ダークモード</string>
<string name="settings_dark_mode_summary">夜にシコりたい方々へ</string>
<string name="gallery_details">ギャラリー情報</string>
<string name="gallery_artists">アーティスト</string>
<string name="gallery_characters">キャラクター</string>
<string name="gallery_groups">グループ</string>
<string name="gallery_language">言語</string>
<string name="gallery_series">シリーズ</string>
<string name="gallery_tags">タグ</string>
<string name="gallery_thumbnails">サムネイル</string>
<string name="gallery_related">おすすめ</string>
<string name="settings_nomedia_summary">イメージをギャラリーから見えなくする</string>
<string name="settings_nomedia_title">イメージを隠す</string>
<string name="reader_help">ヘルプ</string>
<string name="main_delete">削除</string>
<string name="main_download">ダウンロード</string>
<string name="settings_backup_title">お気に入りバックアップ</string>
<string name="settings_restore_title">お気に入り復元</string>
<string name="settings_backup_snackbar">バックアップファイルを作成しました</string>
<string name="settings_backup_checkout">確認</string>
<string name="settings_restore_failed">復元に失敗しました</string>
<string name="settings_restore_successful">%1$d項目を復元しました</string>
</resources>

View File

@@ -66,11 +66,11 @@
<string name="main_open_gallery_by_id">갤러리 번호로 열기</string>
<string name="main_open_gallery_by_id_error">갤러리를 찾지 못했습니다</string>
<string name="settings_storage">저장 공간</string>
<string name="main_drawer_grouop_contact_kakaotalk">카카오톡 오픈채팅방</string>
<string name="main_drawer_grouop_contact_discord">디스코드</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_biometrics">생체 인식</string>
<string name="settings_lock_confirm">잠금 확인을 위해 한번 더 입력해주세요</string>
<string name="settings_lock_enabled">사용 중</string>
<string name="settings_lock_fingerprint">지문</string>
@@ -83,4 +83,31 @@
<string name="main_menu_sort">정렬</string>
<string name="main_menu_sort_popular">인기순</string>
<string name="main_menu_sort_newest">시간순</string>
<string name="update_failed">"업데이트 "</string>
<string name="update_failed_message">수동 업데이트가 필요합니다. APK 파일은 다운로드 폴더에 있습니다.</string>
<string name="ignore_update">무시</string>
<string name="lock_corrupted">잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.</string>
<string name="update_no_permission">권한이 부여되어 있지 않아 자동 업데이트를 진행할 수 없습니다. 홈페이지에서 직접 다운로드 받으시기 바랍니다.</string>
<string name="settings_dark_mode_title">다크 모드</string>
<string name="settings_dark_mode_summary">딥 다크한 모오드</string>
<string name="gallery_details">갤러리 정보</string>
<string name="gallery_artists">작가</string>
<string name="gallery_characters">캐릭터</string>
<string name="gallery_groups">그룹</string>
<string name="gallery_language">언어</string>
<string name="gallery_series">시리즈</string>
<string name="gallery_tags">태그</string>
<string name="gallery_related">관련 갤러리</string>
<string name="gallery_thumbnails">미리보기</string>
<string name="settings_nomedia_summary">갤러리에서 이미지 검색이 되지 않도록 합니다</string>
<string name="settings_nomedia_title">이미지 숨기기</string>
<string name="reader_help">도움말</string>
<string name="main_delete">삭제</string>
<string name="main_download">다운로드</string>
<string name="settings_backup_title">즐겨찾기 백업</string>
<string name="settings_restore_title">즐겨찾기 복원</string>
<string name="settings_backup_snackbar">백업 파일을 생성하였습니다</string>
<string name="settings_backup_checkout">확인</string>
<string name="settings_restore_failed">복원에 실패했습니다</string>
<string name="settings_restore_successful">%1$d개 항목을 복원했습니다</string>
</resources>

View File

@@ -8,4 +8,6 @@
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen>
<dimen name="thumbnail_margin">8dp</dimen>
</resources>

View File

@@ -7,9 +7,10 @@
<string name="home_page" translatable="false">http://bit.ly/2EZDClw</string>
<string name="update" translatable="false">http://bit.ly/2ZlOjXJ</string>
<string name="help" translatable="false">http://bit.ly/2Z7lNZE</string>
<string name="github" translatable="false">https://github.com/tom5079/Pupil-issue/issues/new/choose</string>
<string name="github" translatable="false">https://github.com/tom5079/Pupil/</string>
<string name="email" translatable="false">mailto:pupil.hentai@gmail.com</string>
<string name="kakaotalk" translatable="false">https://open.kakao.com/o/gvNrncsb</string>
<string name="discord" translatable="false">https://discord.gg/Stj4b5v</string>
<string name="error_help" translatable="false">http://bit.ly/2KYYhto</string>
<string name="main_settings" translatable="false">Settings</string>
<string name="galleryblock_thumbnail_description" translatable="false">Thumbnail</string>
@@ -26,10 +27,17 @@
<string name="https_block_alert_title">(Korean only)</string>
<string name="https_block_alert">(Korean only)</string>
<string name="update_failed">Update failed</string>
<string name="update_failed_message">Please install manually. APK file is in the Downloads folder.</string>
<string name="update_no_permission">Cannot auto update because permission is denied. Please download manually from the webpage.</string>
<string name="ignore_update">Ignore</string>
<string name="channel_download">Download</string>
<string name="channel_download_description">Shows download status</string>
<string name="unable_to_connect">Unable to connect to hitomi.la</string>
<string name="lock_corrupted">Lock file corrupted! Please re-install Pupil</string>
<string name="main_search">Search</string>
<string name="main_no_result">No result</string>
@@ -43,7 +51,7 @@
<string name="main_drawer_group_contact_homepage">Visit homepage</string>
<string name="main_drawer_group_contact_github">Visit github</string>
<string name="main_drawer_group_contact_email">Email me!</string>
<string name="main_drawer_grouop_contact_kakaotalk">Kakaotalk</string>
<string name="main_drawer_grouop_contact_discord">Discord</string>
<string name="main_menu_sort">Sort</string>
<string name="main_menu_sort_newest">Newest</string>
@@ -63,6 +71,9 @@
<string name="main_export_open_folder">Open Folder</string>
<string name="main_export_error">Error occurred during export</string>
<string name="main_download">DOWNLOAD</string>
<string name="main_delete">DELETE</string>
<string name="update_title">Update available</string>
<string name="update_download_started">Download started</string>
<string name="update_notification_description">Downloading apk&#8230;</string>
@@ -71,6 +82,16 @@
<string name="search_hint">Search galleries</string>
<string name="search_hint_with_page">Search galleries</string>
<string name="gallery_details">Details</string>
<string name="gallery_thumbnails">Thumbnails</string>
<string name="gallery_related">Related Galleries</string>
<string name="gallery_artists">Artists</string>
<string name="gallery_groups">Groups</string>
<string name="gallery_language">Language</string>
<string name="gallery_series">Series</string>
<string name="gallery_characters">Characters</string>
<string name="gallery_tags">Tags</string>
<string name="galleryblock_series">Series: %1$s</string>
<string name="galleryblock_type">Type: %1$s</string>
<string name="galleryblock_language">Language: %1$s</string>
@@ -84,6 +105,8 @@
<string name="reader_notification_complete">Download complete</string>
<string name="reader_notification_error">Download error</string>
<string name="reader_help">Help</string>
<string name="settings_title">Settings</string>
<string name="settings_app_version_title">App version</string>
<string name="settings_search_title">Search Settings</string>
@@ -105,12 +128,22 @@
<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_summary">Enable security mode to make the screen invisible on recent app window</string>
<string name="settings_dark_mode_title">Dark mode</string>
<string name="settings_dark_mode_summary">Protect yourself against light attacks!</string>
<string name="settings_nomedia_title">Hide image from gallery</string>
<string name="settings_nomedia_summary">Hides image from gallery</string>
<string name="settings_backup_title">Backup favorites</string>
<string name="settings_backup_snackbar">Backup file created</string>
<string name="settings_backup_checkout">Check out</string>
<string name="settings_restore_title">Restore favorites</string>
<string name="settings_restore_failed">Restore failed</string>
<string name="settings_restore_successful">%1$d entries restored</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_biometrics">Biometrics</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>

View File

@@ -1,14 +1,14 @@
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="NoActionBarAppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<style name="NoActionBarAppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external" path="/"/>
</paths>

View File

@@ -15,7 +15,7 @@
app:key="lock_password"/>
<PreferenceCategory
app:title="@string/settings_lock_biomatrics">
app:title="@string/settings_lock_biometrics">
<Preference
app:title="@string/settings_lock_fingerprint"

View File

@@ -20,7 +20,6 @@
<Preference
app:key="default_query"
app:title="@string/settings_default_query"
app:defaultValue=""
app:useSimpleSummaryProvider="true"/>
</PreferenceCategory>
@@ -65,6 +64,24 @@
app:summary="@string/settings_security_mode_summary"
app:defaultValue="true"/>
<SwitchPreference
app:key="dark_mode"
app:title="@string/settings_dark_mode_title"
app:summary="@string/settings_dark_mode_summary"/>
<SwitchPreference
app:key="nomedia"
app:title="@string/settings_nomedia_title"
app:summary="@string/settings_nomedia_title"/>
<Preference
app:key="backup"
app:title="@string/settings_backup_title"/>
<Preference
app:key="restore"
app:title="@string/settings_restore_title"/>
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@@ -20,22 +20,19 @@
package xyz.quaver.pupil
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
import org.junit.Test
class ExampleUnitTest {
@Test
fun test() {
val current = "0.1"
val latest = "0.2"
print(current < latest)
}
}

View File

@@ -1,24 +1,22 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.3.41'
ext.kotlin_version = '1.3.61'
repositories {
google()
jcenter()
maven {
url 'https://maven.fabric.io/public'
}
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.0'
classpath 'com.google.gms:google-services:4.3.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'io.fabric.tools:gradle:1.29.0'
classpath 'com.google.firebase:perf-plugin:1.2.1'
classpath 'com.google.firebase:perf-plugin:1.3.1'
}
}

View File

@@ -1,6 +1,6 @@
#Thu Apr 25 10:57:40 KST 2019
#Fri Aug 23 08:21:15 KST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip

View File

@@ -6,7 +6,7 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0"
implementation 'org.jsoup:jsoup:1.11.3'
testImplementation 'junit:junit:4.12'
}
@@ -14,7 +14,6 @@ dependencies {
sourceCompatibility = "7"
targetCompatibility = "7"
buildscript {
ext.kotlin_version = '1.3.31'
repositories {
mavenCentral()
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2019 tom5079
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.quaver
fun availableInHiyobi(galleryID: Int) : Boolean {
return try {
xyz.quaver.hiyobi.getReader(galleryID)
true
} catch (e: Exception) {
false
}
}

View File

@@ -16,11 +16,25 @@
package xyz.quaver.hitomi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import java.net.URL
const val protocol = "https:"
fun getGalleryInfo(galleryID: Int): List<GalleryInfo> {
return Json(JsonConfiguration.Stable).parse(
GalleryInfo.serializer().list,
Regex("""\[.+]""").find(
URL("$protocol//$domain/galleries/$galleryID.js").readText()
)?.value ?: "[]"
)
}
//common.js
var adapose = false
const val numberOfFrontends = 2
const val numberOfFrontends = 3
const val domain = "ltn.hitomi.la"
const val galleryblockdir = "galleryblock"
const val nozomiextension = ".nozomi"
@@ -37,20 +51,22 @@ fun subdomainFromGalleryID(g: Int) : String {
fun subdomainFromURL(url: String, base: String? = null) : String {
var retval = "a"
if (base != null)
if (!base.isNullOrBlank())
retval = base
val r = Regex("""/\d*(\d)/""")
val m = r.find(url)
val r = Regex("""/galleries/\d*(\d)/""")
var m = r.find(url)
var b = 10
m ?: return retval
if (m == null) {
b = 16
val r2 = Regex("""/[0-9a-f]/([0-9a-f]{2})/""")
m = r2.find(url)
if (m == null)
return retval
}
var g = m.groups[1]!!.value.toIntOrNull()
g ?: return retval
if (g == 1)
g = 0
val g = m.groupValues[1].toIntOrNull(b) ?: return retval
retval = subdomainFromGalleryID(g) + retval
@@ -58,5 +74,25 @@ fun subdomainFromURL(url: String, base: String? = null) : String {
}
fun urlFromURL(url: String, base: String? = null) : String {
return url.replace(Regex("//..?\\.hitomi\\.la/"), "//${subdomainFromURL(url, base)}.hitomi.la/")
}
return url.replace(Regex("""//..?\.hitomi\.la/"""), "//${subdomainFromURL(url, base)}.hitomi.la/")
}
fun fullPathFromHash(hash: String?) : String? {
return when {
(hash?.length ?: 0) < 3 -> hash
else -> hash!!.replace(Regex("^.*(..)(.)$"), "$2/$1/$hash")
}
}
fun urlFromHash(galleryID: Int, image: GalleryInfo, webp: String? = null) : String {
val ext = webp ?: image.name.split('.').last()
return when {
image.hash.isNullOrBlank() ->
"$protocol//a.hitomi.la/galleries/$galleryID/${image.name}"
else ->
"$protocol//a.hitomi.la/${webp?:"images"}/${fullPathFromHash(image.hash)}.$ext"
}
}
fun urlFromUrlFromHash(galleryID: Int, image: GalleryInfo, webp: String? = null) =
urlFromURL(urlFromHash(galleryID, image, webp))

View File

@@ -17,12 +17,12 @@
package xyz.quaver.hitomi
import org.jsoup.Jsoup
import java.net.URL
import java.net.URLDecoder
data class Gallery(
val related: List<Int>,
val langList: List<Pair<String, String>>,
val cover: URL,
val cover: String,
val title: String,
val artists: List<String>,
val groups: List<String>,
@@ -31,10 +31,11 @@ data class Gallery(
val series: List<String>,
val characters: List<String>,
val tags: List<String>,
val thumbnails: List<URL>
val thumbnails: List<String>
)
fun getGallery(galleryID: Int) : Gallery {
val url = "https://hitomi.la/galleries/$galleryID.html"
val url = Jsoup.connect("https://hitomi.la/galleries/$galleryID.html").get()
.select("a").attr("href")
val doc = Jsoup.connect(url).get()
@@ -45,10 +46,10 @@ fun getGallery(galleryID: Int) : Gallery {
}.toList()
val langList = doc.select("#lang-list a").map {
Pair(it.text(), it.attr("href").replace(".html", ""))
Pair(it.text(), "$protocol//hitomi.la${it.attr("href")}")
}
val cover = URL(protocol + doc.selectFirst(".cover img").attr("src"))
val cover = protocol + doc.selectFirst(".cover img").attr("src")
val title = doc.selectFirst(".gallery h1 a").text()
val artists = doc.select(".gallery h2 a").map { it.text() }
val groups = doc.select(".gallery-info a[href~=^/group/]").map { it.text() }
@@ -63,15 +64,13 @@ fun getGallery(galleryID: Int) : Gallery {
val characters = doc.select(".gallery-info a[href~=^/character/]").map { it.text() }
val tags = doc.select(".gallery-info a[href~=^/tag/]").map {
val href = it.attr("href")
val href = URLDecoder.decode(it.attr("href"), "UTF-8")
href.slice(5 until href.indexOf('-'))
}
val thumbnails = Regex("'(//tn.hitomi.la/smalltn/\\d+/\\d+.+)',")
.findAll(doc.select("script").last().html())
.map {
URL(protocol + it.groups[1]!!.value)
}.toList()
val thumbnails = getGalleryInfo(galleryID).map {
"$protocol//tn.hitomi.la/smalltn/$galleryID/${it.name}.jpg"
}
return Gallery(related, langList, cover, title, artists, groups, type, language, series, characters, tags, thumbnails)
}

View File

@@ -18,7 +18,6 @@ package xyz.quaver.hitomi
import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
import sun.rmi.runtime.Log
import java.net.URL
import java.net.URLDecoder
import java.nio.ByteBuffer
@@ -69,6 +68,7 @@ fun fetchNozomi(area: String? = null, tag: String = "index", language: String =
@Serializable
data class GalleryBlock(
val id: Int,
val galleryUrl: String,
val thumbnails: List<String>,
val title: String,
val artists: List<String>,
@@ -83,6 +83,8 @@ fun getGalleryBlock(galleryID: Int) : GalleryBlock? {
try {
val doc = Jsoup.connect(url).get()
val galleryUrl = doc.selectFirst(".lillie").attr("href")
val thumbnails = doc.select("img").map { protocol + it.attr("data-src") }
val title = doc.selectFirst("h1.lillie > a").text()
@@ -100,7 +102,7 @@ fun getGalleryBlock(galleryID: Int) : GalleryBlock? {
href.slice(5 until href.indexOf("-all"))
}
return GalleryBlock(galleryID, thumbnails, title, artists, series, type, language, relatedTags)
return GalleryBlock(galleryID, galleryUrl, thumbnails, title, artists, series, type, language, relatedTags)
} catch (e: Exception) {
return null
}

View File

@@ -17,72 +17,34 @@
package xyz.quaver.hitomi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import org.jsoup.Jsoup
import xyz.quaver.hiyobi.HiyobiReader
import java.net.URL
fun getReferer(galleryID: Int) = "https://hitomi.la/reader/$galleryID.html"
fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
fun webpReaderFromReader(reader: Reader) : Reader {
if (reader is HiyobiReader)
return reader
return Reader(reader.title, reader.readerItems.map {
ReaderItem(
if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url,
it.galleryInfo
)
})
}
@Serializable
data class GalleryInfo(
val width: Int,
val haswebp: Int,
val hash: String? = null,
val haswebp: Int = 0,
val name: String,
val height: Int
)
@Serializable
data class ReaderItem(
val url: String,
val galleryInfo: GalleryInfo?
)
@Serializable
open class Reader(val title: String, val readerItems: List<ReaderItem>)
data class Reader(val code: Code, val title: String, val galleryInfo: List<GalleryInfo>) {
enum class Code {
HITOMI,
HIYOBI,
SORALA
}
}
//Set header `Referer` to reader url to avoid 403 error
fun getReader(galleryID: Int) : Reader {
val readerUrl = "https://hitomi.la/reader/$galleryID.html"
val galleryInfoUrl = "https://ltn.hitomi.la/galleries/$galleryID.js"
val doc = Jsoup.connect(readerUrl).get()
val title = doc.title()
val images = doc.select(".img-url").map {
protocol + urlFromURL(it.text())
}
val galleryInfo = ArrayList<GalleryInfo?>()
galleryInfo.addAll(
Json(JsonConfiguration.Stable).parse(
GalleryInfo.serializer().list,
Regex("""\[.+]""").find(
URL(galleryInfoUrl).readText()
)?.value ?: "[]"
)
)
if (images.size > galleryInfo.size)
galleryInfo.addAll(arrayOfNulls(images.size - galleryInfo.size))
return Reader(title, (images zip galleryInfo).map {
ReaderItem(it.first, it.second)
})
return Reader(Reader.Code.HITOMI, doc.title(), getGalleryInfo(galleryID))
}

View File

@@ -57,30 +57,33 @@ fun doSearch(query: String, sortByPopularity: Boolean = false) : List<Int> {
var results = when {
sortByPopularity -> getGalleryIDsFromNozomi(null, "popular", "all")
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(null, "index", "all")
else -> getGalleryIDsForQuery(positiveTerms.poll())
else -> listOf()
}
runBlocking {
@Synchronized fun filterPositive(newResults: List<Int>) {
results = results.filter { newResults.binarySearch(it) >= 0 }
results = when {
results.isEmpty() -> newResults
else -> newResults.sorted().let { sorted ->
results.filter { sorted.binarySearch(it) >= 0 }
}
}
}
@Synchronized fun filterNegative(newResults: List<Int>) {
results = results.filter { newResults.binarySearch(it) < 0 }
results = newResults.sorted().let { sorted ->
results.filter { sorted.binarySearch(it) < 0 }
}
}
//positive results
positiveResults.forEach {
val result = it.await()
filterPositive(result.sorted())
filterPositive(it.await())
}
//negative results
negativeResults.forEach {
val result = it.await()
filterNegative(result.sorted())
filterNegative(it.await())
}
}

View File

@@ -16,7 +16,6 @@
package xyz.quaver.hitomi
import java.io.ByteArrayOutputStream
import java.net.URL
import java.nio.ByteBuffer
import java.nio.ByteOrder
@@ -175,22 +174,18 @@ fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : List
}
try {
with (URL(nozomiAddress).openConnection() as HttpsURLConnection) {
requestMethod = "GET"
val bytes = URL(nozomiAddress).readBytes()
val nozomi = ArrayList<Int>()
val nozomi = ArrayList<Int>()
val bytes = inputStream.readBytes()
val arrayBuffer = ByteBuffer
.wrap(bytes)
.order(ByteOrder.BIG_ENDIAN)
val arrayBuffer = ByteBuffer
.wrap(bytes)
.order(ByteOrder.BIG_ENDIAN)
while (arrayBuffer.hasRemaining())
nozomi.add(arrayBuffer.int)
while (arrayBuffer.hasRemaining())
nozomi.add(arrayBuffer.int)
return nozomi
}
return nozomi
} catch (e: Exception) {
return emptyList()
}

View File

@@ -18,18 +18,17 @@ package xyz.quaver.hiyobi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.json.content
import kotlinx.serialization.list
import org.jsoup.Jsoup
import xyz.quaver.hitomi.GalleryInfo
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.ReaderItem
import xyz.quaver.hitomi.protocol
import java.net.URL
import javax.net.ssl.HttpsURLConnection
const val hiyobi = "xn--9w3b15m8vo.asia"
const val hiyobi = "hiyobi.me"
const val user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36"
class HiyobiReader(title: String, readerItems: List<ReaderItem>) : Reader(title, readerItems)
var cookie: String = ""
get() {
if (field.isEmpty())
@@ -38,6 +37,12 @@ var cookie: String = ""
return field
}
data class Images(
val path: String,
val no: Int,
val name: String
)
fun renewCookie() : String {
val url = "https://$hiyobi/"
@@ -59,7 +64,8 @@ fun getReader(galleryID: Int) : Reader {
val title = Jsoup.connect(reader).get().title()
val json = Json(JsonConfiguration.Stable).parseJson(
val galleryInfo = Json(JsonConfiguration.Stable).parse(
GalleryInfo.serializer().list,
with(URL(url).openConnection() as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
@@ -70,8 +76,8 @@ fun getReader(galleryID: Int) : Reader {
}
)
return Reader(title, json.jsonArray.map {
val name = it.jsonObject["name"]!!.content
ReaderItem("https://$hiyobi/data/$galleryID/$name", null)
})
}
return Reader(Reader.Code.HIYOBI, title, galleryInfo)
}
fun createImgList(galleryID: Int, reader: Reader) =
reader.galleryInfo.map { Images("$protocol//$hiyobi/data/$galleryID/${it.name}", galleryID, it.name) }

View File

@@ -18,12 +18,17 @@
package xyz.quaver.hitomi
import org.junit.Assert.assertEquals
import org.junit.Test
import xyz.quaver.availableInHiyobi
class UnitTest {
@Test
fun test() {
assertEquals(
"6/2d/c26014fc6153ef717932d85f4d26c75195560fb2ce1da60b431ef376501642d6",
fullPathFromHash("c26014fc6153ef717932d85f4d26c75195560fb2ce1da60b431ef376501642d6")
)
}
@Test
@@ -63,20 +68,36 @@ class UnitTest {
@Test
fun test_getGallery() {
val gallery = getGallery(1405267)
val gallery = getGallery(1510566)
print(gallery)
}
@Test
fun test_getReader() {
val reader = getReader(1404693)
val reader = getReader(1442740)
print(reader)
}
@Test
fun test_hiyobi() {
xyz.quaver.hiyobi.getReader(1510567)
}
@Test
fun test_urlFromUrlFromHash() {
val url = urlFromUrlFromHash(1531795, GalleryInfo(
212, "719d46a7556be0d0021c5105878507129b5b3308b02cf67f18901b69dbb3b5ef", 1, "00.jpg", 300
), "webp")
print(url)
}
@Test
fun test_availableInHiyobi() {
val result = availableInHiyobi(1272781)
print(result)
}
}