diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index c33b7e4e..45b56541 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,8 @@ + + @@ -14,6 +17,7 @@ xmlns:android + ^$ @@ -24,6 +28,7 @@ xmlns:.* + ^$ @@ -35,6 +40,7 @@ .*:id + http://schemas.android.com/apk/res/android @@ -45,6 +51,7 @@ .*:name + http://schemas.android.com/apk/res/android @@ -55,6 +62,7 @@ name + ^$ @@ -65,6 +73,7 @@ style + ^$ @@ -75,6 +84,7 @@ .* + ^$ @@ -86,6 +96,7 @@ .* + http://schemas.android.com/apk/res/android @@ -97,6 +108,7 @@ .* + .* diff --git a/app/build.gradle b/app/build.gradle index 9aecd601..4fd94ce0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "xyz.quaver.pupil" minSdkVersion 16 targetSdkVersion 29 - versionCode 27 - versionName "3.2-beta2" + versionCode 29 + versionName "4.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" multiDexEnabled true vectorDrawables.useSupportLibrary = true @@ -25,7 +25,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } buildTypes.each { - it.buildConfigField('boolean', 'PRERELEASE', 'true') + it.buildConfigField('boolean', 'PRERELEASE', 'false') it.buildConfigField('boolean', 'CENSOR', 'false') } } @@ -47,18 +47,20 @@ dependencies { 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 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation 'androidx.preference:preference:1.1.0-rc01' + implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.gridlayout:gridlayout:1.0.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.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-rc02" implementation 'com.android.support:multidex:1.0.3' - implementation 'com.google.android.material:material:1.1.0-alpha09' - implementation 'com.google.firebase:firebase-core:17.1.0' - implementation 'com.google.firebase:firebase-perf:19.0.0' + implementation "com.daimajia.swipelayout:library:1.2.0@aar" + implementation 'com.google.android.material:material:1.2.0-alpha01' + implementation 'com.google.firebase:firebase-core:17.2.1' + implementation 'com.google.firebase:firebase-perf:19.0.1' 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' @@ -67,7 +69,6 @@ dependencies { transitive = false } implementation 'com.andrognito.patternlockview:patternlockview:1.0.0' - implementation 'com.jsibbold:zoomage:1.3.0' implementation "ru.noties.markwon:core:${markwonVersion}" kapt 'com.github.bumptech.glide:compiler:4.9.0' testImplementation 'junit:junit:4.12' diff --git a/app/release/output.json b/app/release/output.json index 16d1dc23..0d8ff808 100644 --- a/app/release/output.json +++ b/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":25,"versionName":"3.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":29,"versionName":"4.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt index b283f4c0..6d23423b 100644 --- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt @@ -26,11 +26,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.test.rule.ActivityTestRule import org.junit.Assert.assertEquals -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import xyz.quaver.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 @@ -50,8 +49,6 @@ class ExampleInstrumentedTest { // Context of the app under test. val appContext = InstrumentationRegistry.getInstrumentation().targetContext assertEquals("xyz.quaver.pupil", appContext.packageName) - - Log.d("Pupil", fetchNozomi().first.size.toString()) } @Test @@ -70,7 +67,7 @@ class ExampleInstrumentedTest { 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) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 08cc55a4..be11229f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,11 +1,13 @@ + + android:theme="@style/AppTheme" + tools:replace="android:theme"> >>) : RecyclerView.Adapter() { +class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List>>) : RecyclerSwipeAdapter(), SwipeAdapterInterface { enum class ViewType { NEXT, @@ -64,7 +68,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri private lateinit var favorites: Histories - inner class GalleryViewHolder(val view: CardView) : RecyclerView.ViewHolder(view) { + inner class GalleryViewHolder(val view: View) : RecyclerView.ViewHolder(view) { fun bind(item: Pair>) { with(view) { val resources = context.resources @@ -110,7 +114,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri .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 @@ -135,7 +139,7 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri 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 } @@ -276,6 +280,8 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri val completeFlag = SparseBooleanArray() val onChipClickedHandler = ArrayList<((Tag) -> Unit)>() + var onDownloadClickedHandler: ((Int) -> Unit)? = null + var onDeleteClickedHandler: ((Int) -> Unit)? = null var showNext = false var showPrev = false @@ -301,8 +307,47 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if (holder is GalleryViewHolder) - holder.bind(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(R.string.main_cancel_download) + } + } + + 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) { @@ -328,4 +373,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri else -> ViewType.GALLERY }.ordinal } + + override fun getSwipeLayoutResourceId(position: Int) = R.id.galleryblock_swipe_layout } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt index 9b4781a8..10e42f86 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt @@ -18,18 +18,13 @@ package xyz.quaver.pupil.adapters -import android.graphics.drawable.Drawable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.RequestManager -import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.target.Target import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.R import xyz.quaver.pupil.util.getCachedGallery @@ -40,7 +35,6 @@ class ReaderAdapter(private val glide: RequestManager, private val images: List) : RecyclerView.Adapter() { var isFullScreen = false - private var prev : Drawable? = null class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) @@ -64,29 +58,7 @@ class ReaderAdapter(private val glide: RequestManager, .apply { if (BuildConfig.CENSOR) override(5, 8) - if (isFullScreen) - placeholder(prev) } - .listener(object: RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ) = false - - override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean { - prev = resource?.constantState?.newDrawable()?.mutate() - - return false - } - }) .into(holder.view) } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/GalleryDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/GalleryDialog.kt index 37d6ff9e..aa02bbea 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/GalleryDialog.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/GalleryDialog.kt @@ -22,19 +22,14 @@ import android.app.Dialog import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.Gravity import android.view.LayoutInflater import android.view.View -import android.widget.ImageView -import android.widget.LinearLayout import android.widget.LinearLayout.LayoutParams import androidx.core.content.ContextCompat -import androidx.gridlayout.widget.GridLayout import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide -import com.bumptech.glide.RequestManager import com.google.android.material.chip.Chip import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.dialog_galleryblock.* @@ -45,6 +40,7 @@ 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 @@ -113,8 +109,11 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte } Glide.with(context) - .load(gallery.thumbnails.firstOrNull()) - .into(gallery_thumbnail) + .load(gallery.cover) + .apply { + if (BuildConfig.CENSOR) + override(5, 8) + }.into(gallery_cover) addDetails(gallery) addThumbnails(gallery) diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index 24fcc389..775df295 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -371,7 +371,7 @@ class MainActivity : AppCompatActivity() { } private fun checkPermissions() { - if (this.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) + if (!hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 13489) } @@ -535,6 +535,52 @@ 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() + } + } + + completeFlag.put(galleryID, false) + } + + closeAllItems() + } } ItemClickSupport.addTo(this) .setOnItemClickListener { _, position, v -> diff --git a/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt index f542a019..58c83689 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/PatternLockFragment.kt @@ -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 { diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt index d88a7eef..938228b0 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -249,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 = { @@ -282,7 +282,7 @@ class ReaderActivity : AppCompatActivity() { onErrorHandler = { Snackbar .make(reader_layout, it.message ?: it.javaClass.name, Snackbar.LENGTH_INDEFINITE) - .setAction(R.string.reader_help) { _ -> + .setAction(R.string.reader_help) { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.error_help)))) } .show() diff --git a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt index 38725a09..32a18e34 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt @@ -29,13 +29,15 @@ 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.availableInHiyobi 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 @@ -83,7 +85,7 @@ class GalleryDownloader( onNotifyChangedHandler?.invoke(value) } - private val reader: Deferred? + private val reader: Deferred? private var downloadJob: Job? = null private lateinit var notificationBuilder: NotificationCompat.Builder @@ -121,11 +123,8 @@ class GalleryDownloader( 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 = availableInHiyobi(galleryID) onReaderLoadedHandler?.invoke(cached) @@ -148,7 +147,7 @@ class GalleryDownloader( } } - if (reader.readerItems.isNotEmpty()) { + if (reader.galleryInfo.isNotEmpty()) { //Save cache if (cache.parentFile?.exists() == false) cache.parentFile!!.mkdirs() @@ -159,7 +158,8 @@ class GalleryDownloader( reader } catch (e: Exception) { Crashlytics.logException(e) - Reader("", listOf()) + onErrorHandler?.invoke(e) + null } } } @@ -168,29 +168,36 @@ class GalleryDownloader( fun start() { downloadJob = CoroutineScope(Dispatchers.Default).launch { - val reader = reader!!.await() + val reader = reader!!.await() ?: return@launch notificationBuilder.setContentTitle(reader.title) - if (reader.readerItems.isEmpty()) { - onErrorHandler?.invoke(IOException(getString(R.string.unable_to_connect))) - return@launch - } - val list = ArrayList() 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.haswebp) { + 1 -> webpUrlFromUrl( + urlFromUrlFromHash( + galleryID, + galleryInfo, + true + ) + ) + else -> urlFromUrlFromHash(galleryID, galleryInfo) + } + } val name = "$index".padStart(4, '0') val ext = url.split('.').last() @@ -234,8 +241,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()) diff --git a/app/src/main/java/xyz/quaver/pupil/util/file.kt b/app/src/main/java/xyz/quaver/pupil/util/file.kt index 07032896..d1f3ba47 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/file.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/file.kt @@ -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 { diff --git a/app/src/main/java/xyz/quaver/pupil/util/update.kt b/app/src/main/java/xyz/quaver/pupil/util/update.kt index 6acfaf75..1c48f8e5 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/update.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt @@ -54,7 +54,7 @@ fun checkUpdate(url: String) : JsonObject? { fun getApkUrl(releases: JsonObject) : Pair? { return releases["assets"]?.jsonArray?.firstOrNull { - Regex("Pupil-v(\\d+\\.)+\\d+\\.apk").matches(it.jsonObject["name"]?.content ?: "") + Regex("Pupil-v.+\\.apk").matches(it.jsonObject["name"]?.content ?: "") }.let { if (it == null) null diff --git a/app/src/main/res/layout/activity_lock.xml b/app/src/main/res/layout/activity_lock.xml index 4aab2840..00cd2898 100644 --- a/app/src/main/res/layout/activity_lock.xml +++ b/app/src/main/res/layout/activity_lock.xml @@ -29,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"/> + + + + + + @@ -59,16 +81,6 @@ app:backgroundTint="@color/dark_gray" app:fabSize="mini"/> - - @@ -66,7 +66,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintTop_toBottomOf="@id/gallery_title" - app:layout_constraintLeft_toRightOf="@id/gallery_thumbnail" + app:layout_constraintLeft_toRightOf="@id/gallery_cover" app:layout_constraintRight_toRightOf="parent" android:layout_marginLeft="8dp" android:layout_marginStart="8dp"/> @@ -83,7 +83,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintLeft_toRightOf="@id/gallery_thumbnail" + app:layout_constraintLeft_toRightOf="@id/gallery_cover" android:layout_marginLeft="8dp" android:layout_marginStart="8dp"/> diff --git a/app/src/main/res/layout/item_galleryblock.xml b/app/src/main/res/layout/item_galleryblock.xml index 5a7f0094..5d654286 100644 --- a/app/src/main/res/layout/item_galleryblock.xml +++ b/app/src/main/res/layout/item_galleryblock.xml @@ -23,161 +23,203 @@ 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"> - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:drag_edge="right" + app:show_mode="pull_out"> + android:id="@+id/galleryblock_secondary" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + 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"/> - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index f9356dd4..cd3270c6 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -99,7 +99,10 @@ タグ サムネイル おすすめ - イメージを隠す - イメージをギャラリーから見えなくする + イメージをギャラリーから見えなくする + イメージを隠す ヘルプ + 削除 + ダウンロード + キャンセル \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 6b01b3ee..7a261d1f 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -99,7 +99,10 @@ 태그 관련 갤러리 미리보기 - 이미지 숨기기 - 갤러리에서 이미지 검색이 되지 않도록 합니다 + 갤러리에서 이미지 검색이 되지 않도록 합니다 + 이미지 숨기기 도움말 + 삭제 + 다운로드 + 취소 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 40eb0bf9..e444f882 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -71,6 +71,10 @@ Open Folder Error occurred during export + DOWNLOAD + CANCEL + DELETE + Update available Download started Downloading apk… diff --git a/build.gradle b/build.gradle index 5fcfbacf..eb4a1760 100644 --- a/build.gradle +++ b/build.gradle @@ -5,16 +5,14 @@ buildscript { 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.5.0' + classpath 'com.android.tools.build:gradle:3.5.1' 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.1' + classpath 'com.google.gms:google-services:4.3.2' // 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' diff --git a/libpupil/src/main/java/xyz/quaver/Utils.kt b/libpupil/src/main/java/xyz/quaver/Utils.kt new file mode 100644 index 00000000..777a1821 --- /dev/null +++ b/libpupil/src/main/java/xyz/quaver/Utils.kt @@ -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 + } +} \ No newline at end of file diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/common.kt b/libpupil/src/main/java/xyz/quaver/hitomi/common.kt index 9e35af1d..11c14bee 100644 --- a/libpupil/src/main/java/xyz/quaver/hitomi/common.kt +++ b/libpupil/src/main/java/xyz/quaver/hitomi/common.kt @@ -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 { + 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.isNullOrEmpty()) 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("""/images/[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,22 @@ 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/") -} \ No newline at end of file + 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, oldMethod: Boolean) : String { + return when { + oldMethod or image.hash.isNullOrEmpty() -> "$protocol//a.hitomi.la/galleries/$galleryID/${image.name}" + else -> "$protocol//a.hitomi.la/images/${fullPathFromHash(image.hash)}.${image.name.split('.').last()}" + } +} + +fun urlFromUrlFromHash(galleryID: Int, image: GalleryInfo, oldMethod: Boolean = false) = + urlFromURL(urlFromHash(galleryID, image, oldMethod)) \ No newline at end of file diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt b/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt index 754108ce..dd52d235 100644 --- a/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt +++ b/libpupil/src/main/java/xyz/quaver/hitomi/galleries.kt @@ -17,7 +17,6 @@ package xyz.quaver.hitomi import org.jsoup.Jsoup -import java.net.URL import java.net.URLDecoder data class Gallery( @@ -35,7 +34,8 @@ data class Gallery( val thumbnails: List ) 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() @@ -46,7 +46,7 @@ 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 = protocol + doc.selectFirst(".cover img").attr("src") @@ -68,11 +68,9 @@ fun getGallery(galleryID: Int) : Gallery { href.slice(5 until href.indexOf('-')) } - val thumbnails = Regex("'(//tn.hitomi.la/smalltn/\\d+/.+)',") - .findAll(doc.select("script").last().html()) - .map { - 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) } \ No newline at end of file diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt b/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt index 4196d98e..c2e985f0 100644 --- a/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt +++ b/libpupil/src/main/java/xyz/quaver/hitomi/galleryblock.kt @@ -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, val title: String, val artists: List, @@ -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 } diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt b/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt index 1b1d6b08..89e793a9 100644 --- a/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt +++ b/libpupil/src/main/java/xyz/quaver/hitomi/reader.kt @@ -17,72 +17,28 @@ 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 hash: String?, val haswebp: Int, 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) +open class Reader(val title: String, val galleryInfo: List) //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.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(doc.title(), getGalleryInfo(galleryID)) } \ No newline at end of file diff --git a/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt b/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt index fb9a5459..4873aef1 100644 --- a/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt +++ b/libpupil/src/main/java/xyz/quaver/hiyobi/reader.kt @@ -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 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) : 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) - }) -} \ No newline at end of file + return Reader(title, galleryInfo) +} + +fun createImgList(galleryID: Int, reader: Reader) = + reader.galleryInfo.map { Images("$protocol//$hiyobi/data/$galleryID/${it.name}", galleryID, it.name) } \ No newline at end of file diff --git a/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt b/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt index 79091867..b838048c 100644 --- a/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt +++ b/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt @@ -18,12 +18,16 @@ package xyz.quaver.hitomi +import org.junit.Assert.assertEquals import org.junit.Test class UnitTest { @Test fun test() { - + assertEquals( + "6/2d/c26014fc6153ef717932d85f4d26c75195560fb2ce1da60b431ef376501642d6", + fullPathFromHash("c26014fc6153ef717932d85f4d26c75195560fb2ce1da60b431ef376501642d6") + ) } @Test @@ -63,7 +67,7 @@ class UnitTest { @Test fun test_getGallery() { - val gallery = getGallery(1405267) + val gallery = getGallery(1510566) print(gallery) } @@ -77,6 +81,15 @@ class UnitTest { @Test fun test_hiyobi() { + xyz.quaver.hiyobi.getReader(1510567) + } + @Test + fun test_urlFromUrlFromHash() { + val url = urlFromUrlFromHash(1510702, GalleryInfo( + 210, "56e9e1b8bb72194777ed93fee11b06070b905039dd11348b070bcf1793aaed7b", 1, "6.jpg", 300 + )) + + print(url) } } \ No newline at end of file