diff --git a/.idea/misc.xml b/.idea/misc.xml index fb8c126a..6de32e1d 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index ec998b34..9300b3f3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "xyz.quaver.pupil" minSdkVersion 16 targetSdkVersion 28 - versionCode 12 - versionName "2.5.3" + versionCode 13 + versionName "2.6" 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..93a96b25 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,6 +731,15 @@ 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) @@ -759,8 +792,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 +812,49 @@ class MainActivity : AppCompatActivity() { null) ) - val text = "${suggestion.s}\n ${suggestion.t}" + with(suggestionView.findViewById(R.id.right_icon)) { + setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star)) - val len = text.length - val left = suggestion.s.length + if (Tags(json.parse(serializer, favoritesFile.readText())).contains(tag)) + (drawable as Animatable).start() - 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)) { + setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star)) + favorites.remove(tag) + } + else { + (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 +877,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 +894,7 @@ class MainActivity : AppCompatActivity() { runOnUiThread { cancelFetch() clearGalleries() + currentPage = 0 fetchGalleries(query) loadBlocks() } @@ -910,6 +981,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/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/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/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/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 81f0d436..6bbd36f7 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -67,4 +67,5 @@ ダウンロード削除 ダウンロードしたギャラリーを全て削除します。\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..ef0c2380 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -67,4 +67,5 @@ 다운로드 삭제 다운로드 된 만화를 모두 삭제합니다.\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..0dffc533 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,6 +35,7 @@ Home History Downloads + Favorites Contact Help Visit homepage