diff --git a/app/build.gradle b/app/build.gradle index b2f4b9aa..3935d108 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "xyz.quaver.pupil" minSdkVersion 16 targetSdkVersion 28 - versionCode 11 - versionName "2.5.2" + versionCode 14 + versionName "2.7" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/src/main/java/xyz/quaver/pupil/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/MainActivity.kt index 28079aaf..feaa0bab 100644 --- a/app/src/main/java/xyz/quaver/pupil/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/MainActivity.kt @@ -3,6 +3,7 @@ package xyz.quaver.pupil import android.Manifest import android.content.Intent import android.content.pm.PackageManager +import android.graphics.drawable.Animatable import android.net.Uri import android.os.Bundle import android.os.Environment @@ -21,6 +22,7 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.core.view.GravityCompat +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 @@ -31,14 +33,20 @@ import kotlinx.android.synthetic.main.activity_main_content.* import kotlinx.android.synthetic.main.dialog_galleryblock.view.* import kotlinx.coroutines.* import kotlinx.io.IOException +import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.content +import kotlinx.serialization.list +import kotlinx.serialization.parseList +import kotlinx.serialization.stringify import ru.noties.markwon.Markwon import xyz.quaver.hitomi.* import xyz.quaver.pupil.adapters.GalleryBlockAdapter +import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.TagSuggestion +import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.util.* import java.io.File import java.io.FileInputStream @@ -56,7 +64,8 @@ class MainActivity : AppCompatActivity() { enum class Mode { SEARCH, HISTORY, - DOWNLOAD + DOWNLOAD, + FAVORITE } private val galleries = ArrayList>>() @@ -73,6 +82,7 @@ class MainActivity : AppCompatActivity() { private lateinit var histories: Histories private lateinit var downloads: Histories + private lateinit var favorites: Histories override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -96,6 +106,7 @@ class MainActivity : AppCompatActivity() { with(application as Pupil) { this@MainActivity.histories = histories this@MainActivity.downloads = downloads + this@MainActivity.favorites = favorites } setContentView(R.layout.activity_main) @@ -274,6 +285,7 @@ class MainActivity : AppCompatActivity() { R.id.main_drawer_home -> { cancelFetch() clearGalleries() + currentPage = 0 query = "" mode = Mode.SEARCH fetchGalleries(query) @@ -282,6 +294,7 @@ class MainActivity : AppCompatActivity() { R.id.main_drawer_history -> { cancelFetch() clearGalleries() + currentPage = 0 query = "" mode = Mode.HISTORY fetchGalleries(query) @@ -290,11 +303,21 @@ class MainActivity : AppCompatActivity() { R.id.main_drawer_downloads -> { cancelFetch() clearGalleries() + currentPage = 0 query = "" mode = Mode.DOWNLOAD fetchGalleries(query) loadBlocks() } + R.id.main_drawer_favorite -> { + cancelFetch() + clearGalleries() + currentPage = 0 + query = "" + mode = Mode.FAVORITE + fetchGalleries(query) + loadBlocks() + } R.id.main_drawer_help -> { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help)))) } @@ -686,6 +709,7 @@ class MainActivity : AppCompatActivity() { } private var suggestionJob : Job? = null + @UseExperimental(ImplicitReflectionSerializer::class) private fun setupSearchBar() { val searchInputView = findViewById(R.id.search_bar_text) //Change upper case letters to lower case @@ -707,16 +731,24 @@ class MainActivity : AppCompatActivity() { }) with(main_searchview as FloatingSearchView) { + val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json") + val json = Json(JsonConfiguration.Stable) + val serializer = Tag.serializer().list + + if (!favoritesFile.exists()) { + favoritesFile.createNewFile() + favoritesFile.writeText(json.stringify(Tags(listOf()))) + } + setOnMenuItemClickListener { when(it.itemId) { R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), SETTINGS) - R.id.main_menu_page_indicator -> { + R.id.main_menu_jump -> { val preference = PreferenceManager.getDefaultSharedPreferences(context) val perPage = preference.getString("per_page", "25")!!.toInt() val editText = EditText(context) AlertDialog.Builder(context).apply { - title = getString(R.string.reader_go_to_page) setView(editText) setTitle(R.string.main_jump_title) setMessage(getString( @@ -737,6 +769,32 @@ class MainActivity : AppCompatActivity() { } }.show() } + R.id.main_menu_id -> { + val editText = EditText(context) + + AlertDialog.Builder(context).apply { + setView(editText) + setTitle(R.string.main_open_gallery_by_id) + + setPositiveButton(android.R.string.ok) { _, _ -> + CoroutineScope(Dispatchers.Default).launch { + try { + val intent = Intent(this@MainActivity, ReaderActivity::class.java) + val gallery = + getGalleryBlock(editText.text.toString().toInt()) ?: throw Exception() + intent.putExtra( + "galleryblock", + Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery) + ) + + startActivity(intent) + } catch (e: Exception) { + Snackbar.make(main_layout, R.string.main_open_gallery_by_id_error, Snackbar.LENGTH_LONG).show() + } + } + } + }.show() + } } } @@ -751,7 +809,15 @@ class MainActivity : AppCompatActivity() { suggestionJob?.cancel() suggestionJob = CoroutineScope(Dispatchers.IO).launch { - val suggestions = getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) } + val suggestions = ArrayList(getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }) + + suggestions.filter { + val tag = "${it.n}:${it.s.replace(Regex("\\s"), "_")}" + Tags(json.parse(serializer, favoritesFile.readText())).contains(tag) + }.reversed().forEach { + suggestions.remove(it) + suggestions.add(0, it) + } withContext(Dispatchers.Main) { swapSuggestions(suggestions) @@ -759,8 +825,9 @@ class MainActivity : AppCompatActivity() { } } - setOnBindSuggestionCallback { _, leftIcon, textView, item, _ -> + setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ -> val suggestion = item as TagSuggestion + val tag = "${suggestion.n}:${suggestion.s.replace(Regex("\\s"), "_")}" leftIcon.setImageDrawable( ResourcesCompat.getDrawable( @@ -778,16 +845,52 @@ class MainActivity : AppCompatActivity() { null) ) - val text = "${suggestion.s}\n ${suggestion.t}" + with(suggestionView.findViewById(R.id.right_icon)) { - val len = text.length - val left = suggestion.s.length + if (Tags(json.parse(serializer, favoritesFile.readText())).contains(tag)) + setImageResource(R.drawable.ic_star_filled) + else + setImageResource(R.drawable.ic_star_empty) - textView.text = SpannableString(text).apply { - val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE) - setSpan(s, left, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - setSpan(SetLineOverlap(true), 1, len-2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - setSpan(SetLineOverlap(false), len-1, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + 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())) + + if (favorites.contains(tag)) { + setImageResource(R.drawable.ic_star_empty) + favorites.remove(tag) + } + else { + setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star)) + (drawable as Animatable).start() + + favorites.add(tag) + } + + favoritesFile.writeText(json.stringify(favorites)) + } + } + + if (suggestion.t == -1) { + textView.text = suggestion.s + } else { + val text = "${suggestion.s}\n ${suggestion.t}" + + val len = text.length + val left = suggestion.s.length + + textView.text = SpannableString(text).apply { + val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE) + setSpan(s, left, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + setSpan(SetLineOverlap(true), 1, len-2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + setSpan(SetLineOverlap(false), len-1, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } } } @@ -810,7 +913,10 @@ class MainActivity : AppCompatActivity() { setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener { override fun onFocus() { - //Do Nothing + if (searchInputView.text.isEmpty()) + swapSuggestions(json.parse(serializer, favoritesFile.readText()).map { + TagSuggestion(it.tag, -1, "", it.area ?: "tag") + }) } override fun onFocusCleared() { @@ -824,6 +930,7 @@ class MainActivity : AppCompatActivity() { runOnUiThread { cancelFetch() clearGalleries() + currentPage = 0 fetchGalleries(query) loadBlocks() } @@ -910,6 +1017,19 @@ class MainActivity : AppCompatActivity() { } } } + Mode.FAVORITE -> { + when { + query.isEmpty() -> favorites.toList().apply { + totalItems = size + } + else -> { + val result = doSearch(query).sorted() + favorites.filter { result.binarySearch(it) >= 0 }.apply { + totalItems = size + } + } + } + } } } } diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt index c4115c85..368b183a 100644 --- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt +++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt @@ -16,12 +16,14 @@ class Pupil : ObservableApplication() { lateinit var histories: Histories lateinit var downloads: Histories + lateinit var favorites: Histories override fun onCreate() { val preference = PreferenceManager.getDefaultSharedPreferences(this) histories = Histories(File(ContextCompat.getDataDir(this), "histories.json")) downloads = Histories(File(ContextCompat.getDataDir(this), "downloads.json")) + favorites = Histories(File(ContextCompat.getDataDir(this), "favorites.json")) super.onCreate() Fn.init(this) diff --git a/app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt index 1f4c9abb..263eebd1 100644 --- a/app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ReaderActivity.kt @@ -1,10 +1,12 @@ package xyz.quaver.pupil +import android.graphics.drawable.Animatable import android.graphics.drawable.Drawable import android.os.Bundle import android.view.* import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSmoothScroller @@ -20,12 +22,19 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.io.IOException +import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonConfiguration +import kotlinx.serialization.list +import kotlinx.serialization.stringify import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.pupil.adapters.ReaderAdapter +import xyz.quaver.pupil.types.Tag +import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.util.GalleryDownloader +import xyz.quaver.pupil.util.Histories import xyz.quaver.pupil.util.ItemClickSupport +import java.io.File class ReaderActivity : AppCompatActivity() { @@ -43,9 +52,13 @@ class ReaderActivity : AppCompatActivity() { private var menu: Menu? = null + private lateinit var favorites: Histories + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + favorites = (application as Pupil).favorites + window.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE) @@ -81,8 +94,17 @@ class ReaderActivity : AppCompatActivity() { super.onResume() } + @UseExperimental(ImplicitReflectionSerializer::class) override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.reader, menu) + + with(menu?.findItem(R.id.reader_menu_favorite)) { + this ?: return@with + + if (favorites.contains(galleryBlock.id)) + (icon as Animatable).start() + } + this.menu = menu return true } @@ -106,6 +128,18 @@ class ReaderActivity : AppCompatActivity() { dialog.show() } + R.id.reader_menu_favorite -> { + val id = galleryBlock.id + val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true + + if (favorites.contains(id)) { + favorites.remove(id) + favorite.icon = AnimatedVectorDrawableCompat.create(this, R.drawable.avd_star) + } else { + favorites.add(id) + (favorite.icon as Animatable).start() + } + } } return true diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt index 183c90d9..96f5b609 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt @@ -1,6 +1,8 @@ package xyz.quaver.pupil.adapters import android.graphics.BitmapFactory +import android.graphics.drawable.Animatable +import android.util.Log import android.util.SparseBooleanArray import android.view.LayoutInflater import android.view.View @@ -21,8 +23,10 @@ import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.list import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.ReaderItem +import xyz.quaver.pupil.Pupil import xyz.quaver.pupil.R import xyz.quaver.pupil.types.Tag +import xyz.quaver.pupil.util.Histories import java.io.File import java.util.* import kotlin.collections.ArrayList @@ -37,6 +41,8 @@ class GalleryBlockAdapter(private val galleries: List>) { with(view) { @@ -202,6 +208,27 @@ class GalleryBlockAdapter(private val galleries: List favorites.add(gallery.id) + else -> favorites.remove(gallery.id) + } + } + setOnCheckedChangeListener { _, isChecked -> + when { + isChecked -> (background as Animatable).start() + else -> background = AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star) + } + } + } } } } 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 a579865e..7fe99463 100644 --- a/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt +++ b/app/src/main/java/xyz/quaver/pupil/adapters/ReaderAdapter.kt @@ -1,5 +1,6 @@ package xyz.quaver.pupil.adapters +import android.graphics.Bitmap import android.graphics.BitmapFactory import android.view.LayoutInflater import android.view.View diff --git a/app/src/main/java/xyz/quaver/pupil/types/Tags.kt b/app/src/main/java/xyz/quaver/pupil/types/Tags.kt index b7e8039b..70d0a119 100644 --- a/app/src/main/java/xyz/quaver/pupil/types/Tags.kt +++ b/app/src/main/java/xyz/quaver/pupil/types/Tags.kt @@ -1,5 +1,8 @@ package xyz.quaver.pupil.types +import kotlinx.serialization.Serializable + +@Serializable data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) { companion object { fun parse(tag: String) : Tag { 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 985096f2..3b5a892e 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/GalleryDownloader.kt @@ -104,7 +104,13 @@ class GalleryDownloader( val cached = json.parse(serializer, cache.readText()) if (cached.isNotEmpty()) { + useHiyobi = when { + cached.first().url.contains("hitomi.la") -> false + else -> true + } + onReaderLoadedHandler?.invoke(cached) + return@async cached } } diff --git a/app/src/main/res/drawable/avd_star.xml b/app/src/main/res/drawable/avd_star.xml new file mode 100644 index 00000000..6ae49f20 --- /dev/null +++ b/app/src/main/res/drawable/avd_star.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_hiyobi.png b/app/src/main/res/drawable/ic_hiyobi.png new file mode 100644 index 00000000..2af60c05 Binary files /dev/null and b/app/src/main/res/drawable/ic_hiyobi.png differ diff --git a/app/src/main/res/drawable/ic_star_empty.xml b/app/src/main/res/drawable/ic_star_empty.xml new file mode 100644 index 00000000..2231c7f8 --- /dev/null +++ b/app/src/main/res/drawable/ic_star_empty.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_star_filled.xml b/app/src/main/res/drawable/ic_star_filled.xml new file mode 100644 index 00000000..ae9a8278 --- /dev/null +++ b/app/src/main/res/drawable/ic_star_filled.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_galleryblock.xml b/app/src/main/res/layout/item_galleryblock.xml index c4184f3d..8186c6a5 100644 --- a/app/src/main/res/layout/item_galleryblock.xml +++ b/app/src/main/res/layout/item_galleryblock.xml @@ -14,111 +14,145 @@ android:focusable="true" android:clickable="true"> - + android:layout_height="wrap_content" + android:orientation="vertical"> - + android:layout_height="wrap_content"> - + - + - + - + - + - + - + + + + + + + + + + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_margin="8dp" + android:background="@android:color/darker_gray"/> - + android:orientation="horizontal" + android:gravity="end"> - + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index 4185cf90..3959e386 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -14,6 +14,10 @@ + + diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml index 5272419e..82a54109 100644 --- a/app/src/main/res/menu/main.xml +++ b/app/src/main/res/menu/main.xml @@ -2,9 +2,14 @@ - + + + + ダウンロード削除 ダウンロードしたギャラリーを全て削除します。\n実行しますか? ロード速度を向上させるため可能であればhiyobi.meからイメージロード + お気に入り + ギャラリー番号で見る + エラーが発生しました \ 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 7cfc9b9b..690c6b41 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -67,4 +67,7 @@ 다운로드 삭제 다운로드 된 만화를 모두 삭제합니다.\n계속하시겠습니까? 속도 향상을 위해 가능하면 hiyobi.me에서 이미지 로드 + 즐겨찾기 + 갤러리 번호로 열기 + 갤러리를 찾지 못했습니다 \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index efe64a23..efa2ab65 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -8,4 +8,5 @@ #d81b60 #1976d2 #00c853 + #ff9800 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f925497f..9ab1f20a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,8 +4,8 @@ https://api.github.com/repos/tom5079/Pupil-issue/releases Pupil-v(\\d+\\.)+\\d+\\.apk - https://tom5079.github.io/Pupil - https://tom5079.github.io/Pupil/2019/06/06/manual-kr.html + http://bit.ly/2ZlOjXJ + http://bit.ly/2Z7lNZE https://github.com/tom5079/Pupil-issue/issues/new/choose mailto:pupil.hentai@gmail.com @@ -35,6 +35,7 @@ Home History Downloads + Favorites Contact Help Visit homepage @@ -43,6 +44,8 @@ Jump to page Current page: %1$d\nMaximum page: %2$d + Open Gallery by ID + Failed to open gallery Move to page %1$d diff --git a/libpupil/src/main/java/xyz/quaver/hitomi/results.kt b/libpupil/src/main/java/xyz/quaver/hitomi/results.kt index 2e3084ea..0142dd2d 100644 --- a/libpupil/src/main/java/xyz/quaver/hitomi/results.kt +++ b/libpupil/src/main/java/xyz/quaver/hitomi/results.kt @@ -11,9 +11,11 @@ fun doSearch(query: String) : List { val terms = query .trim() .replace(Regex("""^\?"""), "") - .replace('_', ' ') .toLowerCase() .split(Regex("\\s+")) + .map { + it.replace('_', ' ') + } val positiveTerms = LinkedList() val negativeTerms = LinkedList() @@ -42,7 +44,8 @@ fun doSearch(query: String) : List { //positive results positiveTerms.map { launch(searchDispatcher) { - filterPositive(getGalleryIDsForQuery(it).sorted()) + val newResults = getGalleryIDsForQuery(it) + filterPositive(newResults.sorted()) } }.forEach { it.join() diff --git a/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt b/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt index 38cecc11..fe648e59 100644 --- a/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt +++ b/libpupil/src/test/java/xyz/quaver/hitomi/UnitTest.kt @@ -7,7 +7,9 @@ import java.net.URL class UnitTest { @Test fun test() { - print(File("C:\\asdf").list()?.size ?: 0) + val galleries = getGalleryIDsForQuery("series:touhou_project") + + println(galleries.size) } @Test