diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index c78d9f02..94a25f7f 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,6 +2,5 @@
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 66bcddaa..396af997 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,10 +7,10 @@ android {
compileSdkVersion 28
defaultConfig {
applicationId "xyz.quaver.pupil"
- minSdkVersion 15
+ minSdkVersion 16
targetSdkVersion 28
- versionCode 2
- versionName "1.1"
+ versionCode 3
+ versionName "1.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@@ -19,9 +19,14 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
+ kotlinOptions {
+ freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
+ }
}
dependencies {
+ def markwonVersion = "3.0.1"
+
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'
@@ -34,6 +39,7 @@ dependencies {
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation "ru.noties.markwon:core:${markwonVersion}"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test:runner:1.1.1'
diff --git a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
index 73714252..5a6f14f7 100644
--- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
@@ -11,7 +11,9 @@ import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
+import xyz.quaver.hiyobi.getReader
import java.io.File
+import java.util.*
/**
* Instrumented test, which will execute on an Android device.
@@ -38,16 +40,7 @@ class ExampleInstrumentedTest {
}
@Test
- @ExperimentalUnsignedTypes
fun test_doSearch() {
- Log.d("TEST", "Starting...")
- runBlocking {
- CoroutineScope(Dispatchers.Main).launch {
- Log.d("TEST", "This is started! wow")
- }.join()
- }
-
- Log.d("TEST", "Finished! ...Really?")
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/GalleryActivity.kt b/app/src/main/java/xyz/quaver/pupil/GalleryActivity.kt
index 3a87a040..24fe0795 100644
--- a/app/src/main/java/xyz/quaver/pupil/GalleryActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/GalleryActivity.kt
@@ -1,9 +1,11 @@
package xyz.quaver.pupil
import android.os.Bundle
+import android.util.Log
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
+import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.activity_gallery.*
import kotlinx.coroutines.*
import xyz.quaver.hitomi.Reader
@@ -32,9 +34,20 @@ class GalleryActivity : AppCompatActivity() {
setContentView(R.layout.activity_gallery)
+ supportActionBar?.title = intent.getStringExtra("GALLERY_TITLE")
+
galleryID = intent.getIntExtra("GALLERY_ID", 0)
CoroutineScope(Dispatchers.Unconfined).launch {
reader = async(Dispatchers.IO) {
+ val preference = PreferenceManager.getDefaultSharedPreferences(this@GalleryActivity)
+ if (preference.getBoolean("use_hiyobi", false)) {
+ try {
+ xyz.quaver.hiyobi.getReader(galleryID)
+ Log.d("Pupil", "Using Hiyobi.me")
+ } catch (e: Exception) {
+ getReader(galleryID)
+ }
+ }
getReader(galleryID)
}
}
@@ -43,6 +56,18 @@ class GalleryActivity : AppCompatActivity() {
loadImages()
}
+ override fun onResume() {
+ val preferences = android.preference.PreferenceManager.getDefaultSharedPreferences(this)
+
+ if (preferences.getBoolean("security_mode", false))
+ window.setFlags(
+ WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.FLAG_SECURE)
+ else
+ window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ super.onResume()
+ }
+
override fun onDestroy() {
super.onDestroy()
loadJob?.cancel()
@@ -78,17 +103,15 @@ class GalleryActivity : AppCompatActivity() {
val reader = reader.await()
launch(Dispatchers.Main) {
- supportActionBar?.title = reader.title
-
with(gallery_progressbar) {
- max = reader.images.size
+ max = reader.size
progress = 0
visibility = View.VISIBLE
}
}
- reader.images.chunked(8).forEach { chunked ->
+ reader.chunked(8).forEach { chunked ->
chunked.map {
async(Dispatchers.IO) {
val url = if (it.second?.haswebp == 1) webpUrlFromUrl(it.first) else it.first
diff --git a/app/src/main/java/xyz/quaver/pupil/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/MainActivity.kt
index dd4422e6..65ea4cec 100644
--- a/app/src/main/java/xyz/quaver/pupil/MainActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/MainActivity.kt
@@ -10,6 +10,7 @@ import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
+import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.preference.PreferenceManager
@@ -18,12 +19,15 @@ import android.text.style.AlignmentSpan
import android.util.Log
import android.view.View
import android.view.WindowManager
+import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
+import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.res.ResourcesCompat
+import androidx.core.view.GravityCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.arlib.floatingsearchview.FloatingSearchView
@@ -31,36 +35,43 @@ import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import com.arlib.floatingsearchview.util.view.SearchInputView
import com.google.android.material.appbar.AppBarLayout
import kotlinx.android.synthetic.main.activity_main.*
+import kotlinx.android.synthetic.main.activity_main_content.*
import kotlinx.coroutines.*
+import kotlinx.serialization.json.JsonObject
+import kotlinx.serialization.json.content
+import ru.noties.markwon.Markwon
import xyz.quaver.hitomi.*
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.TagSuggestion
+import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.SetLineOverlap
import xyz.quaver.pupil.util.checkUpdate
import xyz.quaver.pupil.util.getApkUrl
import java.io.File
+import java.lang.StringBuilder
+import java.util.*
import javax.net.ssl.HttpsURLConnection
+import kotlin.collections.ArrayList
class MainActivity : AppCompatActivity() {
- private val PERMISSION_REQUEST_CODE = 4585
+ private val permissionRequestCode = 4585
private val galleries = ArrayList>()
- private var isLoading = false
private var query = ""
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
+ private var galleryIDs: Deferred>? = null
+ private var loadingJob: Job? = null
- window.setFlags(
- WindowManager.LayoutParams.FLAG_SECURE,
- WindowManager.LayoutParams.FLAG_SECURE)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ Histories.default = Histories(File(cacheDir, "histories.json"))
+ super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
checkPermission()
- update()
+ checkUpdate()
main_appbar_layout.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { _, p1 ->
@@ -73,16 +84,60 @@ class MainActivity : AppCompatActivity() {
setProgressViewOffset(false, 0, resources.getDimensionPixelSize(R.dimen.progress_view_offset))
setOnRefreshListener {
- runBlocking {
- cleanJob?.join()
+ CoroutineScope(Dispatchers.Main).launch {
+ cancelFetch()
+ clearGalleries()
+ fetchGalleries(query)
+ loadBlocks()
}
- fetchGalleries(query, true)
}
}
+ main_nav_view.setNavigationItemSelectedListener {
+ CoroutineScope(Dispatchers.Main).launch {
+ main_drawer_layout.closeDrawers()
+
+ cancelFetch()
+ clearGalleries()
+ when(it.itemId) {
+ R.id.main_drawer_home -> {
+ query = query.replace("HISTORY", "")
+ fetchGalleries(query)
+ }
+ R.id.main_drawer_history -> {
+ query += "HISTORY"
+ fetchGalleries(query)
+ }
+ }
+ loadBlocks()
+ }
+
+ true
+ }
+
setupRecyclerView()
setupSearchBar()
fetchGalleries(query)
+ loadBlocks()
+ }
+
+ override fun onBackPressed() {
+ if (main_drawer_layout.isDrawerOpen(GravityCompat.START))
+ main_drawer_layout.closeDrawer(GravityCompat.START)
+ else
+ super.onBackPressed()
+ }
+
+ override fun onResume() {
+ val preferences = PreferenceManager.getDefaultSharedPreferences(this)
+
+ if (preferences.getBoolean("security_mode", false))
+ window.setFlags(
+ WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.FLAG_SECURE)
+ else
+ window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ super.onResume()
}
private fun checkPermission() {
@@ -98,11 +153,54 @@ class MainActivity : AppCompatActivity() {
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
else
- ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE)
+ ActivityCompat.requestPermissions(this, permissions, permissionRequestCode)
}
}
- private fun update() {
+ private fun checkUpdate() {
+
+ fun extractReleaseNote(update: JsonObject, locale: String) : String {
+ val markdown = update["body"]!!.content
+
+ val target = when(locale) {
+ "ko" -> "한국어"
+ "ja" -> "日本語"
+ else -> "English"
+ }
+
+ val releaseNote = Regex("^# Release Note.+$")
+ val language = Regex("^## $target$")
+ val end = Regex("^#.+$")
+
+ var releaseNoteFlag = false
+ var languageFlag = false
+
+ val result = StringBuilder()
+
+ for(line in markdown.split('\n')) {
+ if (releaseNote.matches(line)) {
+ releaseNoteFlag = true
+ continue
+ }
+
+ if (releaseNoteFlag) {
+ if (language.matches(line)) {
+ languageFlag = true
+ continue
+ }
+ }
+
+ if (languageFlag) {
+ if (end.matches(line))
+ break
+
+ result.append(line+"\n")
+ }
+ }
+
+ return getString(R.string.update_release_note, update["tag_name"]?.content, result.toString())
+ }
+
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
return
@@ -114,8 +212,13 @@ class MainActivity : AppCompatActivity() {
val dialog = AlertDialog.Builder(this@MainActivity).apply {
setTitle(R.string.update_title)
- setMessage(getString(R.string.update_message, update["tag_name"], BuildConfig.VERSION_NAME))
+ val msg = extractReleaseNote(update, Locale.getDefault().language)
+ setMessage(Markwon.create(context).toMarkdown(msg))
setPositiveButton(android.R.string.yes) { _, _ ->
+ Toast.makeText(
+ context, getString(R.string.update_download_started), Toast.LENGTH_SHORT
+ ).show()
+
val dest = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName)
val desturi =
FileProvider.getUriForFile(
@@ -131,6 +234,7 @@ class MainActivity : AppCompatActivity() {
setDescription(getString(R.string.update_notification_description))
setTitle(getString(R.string.app_name))
setDestinationUri(Uri.fromFile(dest))
+ setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
}
val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
@@ -161,12 +265,15 @@ class MainActivity : AppCompatActivity() {
private fun setupRecyclerView() {
with(main_recyclerview) {
adapter = GalleryBlockAdapter(galleries).apply {
- setClickListener {
+ setClickListener { galleryID, title ->
val intent = Intent(this@MainActivity, GalleryActivity::class.java)
- intent.putExtra("GALLERY_ID", it)
+ intent.putExtra("GALLERY_ID", galleryID)
+ intent.putExtra("GALLERY_TITLE", title)
//TODO: Maybe sprinke some transitions will be nice :D
startActivity(intent)
+
+ Histories.default.add(galleryID)
}
}
addOnScrollListener(
@@ -176,9 +283,9 @@ class MainActivity : AppCompatActivity() {
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
- if (!isLoading)
+ if (loadingJob?.isActive != true)
if (layoutManager.findLastCompletelyVisibleItemPosition() == galleries.size)
- fetchGalleries(query)
+ loadBlocks()
}
}
)
@@ -295,101 +402,107 @@ class MainActivity : AppCompatActivity() {
if (query != this@MainActivity.query) {
this@MainActivity.query = query
- fetchGalleries(query, true)
+ cancelFetch()
+ clearGalleries()
+ fetchGalleries(query)
}
}
})
+
+ attachNavigationDrawerToMenuButton(main_drawer_layout)
}
}
- private val cache = ArrayList()
- private var currentFetchingJob: Job? = null
- private var cleanJob: Job? = null
-
private fun cancelFetch() {
- isLoading = false
-
runBlocking {
- cleanJob?.join()
- currentFetchingJob?.cancelAndJoin()
+ galleryIDs?.cancelAndJoin()
+ loadingJob?.cancelAndJoin()
}
}
- private fun fetchGalleries(query: String, clear: Boolean = false) {
+ private fun clearGalleries() {
+ galleries.clear()
+
+ main_recyclerview.adapter?.notifyDataSetChanged()
+
+ main_noresult.visibility = View.INVISIBLE
+ main_progressbar.show()
+ main_swipe_layout.isRefreshing = false
+ }
+
+ private fun fetchGalleries(query: String, from: Int = 0) {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
val defaultQuery = preference.getString("default_query", "")!!
- if (clear) {
- cancelFetch()
- cleanJob = CoroutineScope(Dispatchers.Main).launch {
- cache.clear()
- galleries.clear()
+ galleryIDs = null
- main_recyclerview.adapter?.notifyDataSetChanged()
-
- main_noresult.visibility = View.INVISIBLE
- main_progressbar.show()
- main_swipe_layout.isRefreshing = false
- }
- }
-
- if (isLoading)
+ if (galleryIDs?.isActive == true)
return
- isLoading = true
+ galleryIDs = CoroutineScope(Dispatchers.IO).async {
+ when {
+ query.contains("HISTORY") ->
+ Histories.default.toList()
+ query.isEmpty() and defaultQuery.isEmpty() ->
+ fetchNozomi(start = from, count = perPage)
+ else ->
+ doSearch("$defaultQuery $query")
+ }
+ }
+ }
- currentFetchingJob = CoroutineScope(Dispatchers.IO).launch {
- try {
- val galleryIDs: List
+ private fun loadBlocks() {
+ val preference = PreferenceManager.getDefaultSharedPreferences(this)
+ val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
+ val defaultQuery = preference.getString("default_query", "")!!
- cleanJob?.join()
+ loadingJob = CoroutineScope(Dispatchers.IO).launch {
+ val galleryIDs = galleryIDs?.await()
- if (query.isEmpty() && defaultQuery.isEmpty())
- galleryIDs = fetchNozomi(start = galleries.size, count = perPage)
- else {
- if (cache.isEmpty())
- cache.addAll(doSearch("$defaultQuery $query"))
-
- galleryIDs = cache.slice(galleries.size until Math.min(galleries.size + perPage, cache.size))
-
- with(main_recyclerview.adapter as GalleryBlockAdapter) {
- noMore = galleries.size + perPage >= cache.size
- }
+ if (galleryIDs.isNullOrEmpty()) { //No result
+ withContext(Dispatchers.Main) {
+ main_noresult.visibility = View.VISIBLE
+ main_progressbar.hide()
}
- if (query.isNotEmpty() and defaultQuery.isNotEmpty() and cache.isNullOrEmpty()) {
+ return@launch
+ }
+
+ if (query.isEmpty() and defaultQuery.isEmpty())
+ fetchGalleries("", galleries.size+perPage)
+ else
+ with(main_recyclerview.adapter as GalleryBlockAdapter) {
+ noMore = galleries.size + perPage >= galleryIDs.size
+ }
+
+ when {
+ query.isEmpty() and defaultQuery.isEmpty() ->
+ galleryIDs
+ else ->
+ galleryIDs.slice(galleries.size until Math.min(galleries.size+perPage, galleryIDs.size))
+ }.chunked(4).forEach { chunked ->
+ chunked.map {
+ async {
+ val galleryBlock = getGalleryBlock(it)
+ val thumbnail: Bitmap
+
+ with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
+ thumbnail = BitmapFactory.decodeStream(inputStream)
+ }
+
+ Pair(galleryBlock, thumbnail)
+ }
+ }.forEach {
+ val galleryBlock = it.await()
+
withContext(Dispatchers.Main) {
- main_noresult.visibility = View.VISIBLE
main_progressbar.hide()
+
+ galleries.add(galleryBlock)
+ main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
}
}
-
- galleryIDs.chunked(4).forEach { chunked ->
- chunked.map {
- async {
- val galleryBlock = getGalleryBlock(it)
- val thumbnail: Bitmap
-
- with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
- thumbnail = BitmapFactory.decodeStream(inputStream)
- }
-
- Pair(galleryBlock, thumbnail)
- }
- }.forEach {
- val galleryBlock = it.await()
-
- withContext(Dispatchers.Main) {
- main_progressbar.hide()
-
- galleries.add(galleryBlock)
- main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
- }
- }
- }
- } finally {
- isLoading = false
}
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt b/app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt
index 40624e27..d5c301ce 100644
--- a/app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt
@@ -1,12 +1,15 @@
package xyz.quaver.pupil
import android.os.Bundle
+import android.preference.PreferenceManager
import android.view.MenuItem
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
+import xyz.quaver.pupil.util.Histories
+import java.io.File
class SettingsActivity : AppCompatActivity() {
@@ -25,6 +28,18 @@ class SettingsActivity : AppCompatActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
+ override fun onResume() {
+ val preferences = PreferenceManager.getDefaultSharedPreferences(this)
+
+ if (preferences.getBoolean("security_mode", false))
+ window.setFlags(
+ WindowManager.LayoutParams.FLAG_SECURE,
+ WindowManager.LayoutParams.FLAG_SECURE)
+ else
+ window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ super.onResume()
+ }
+
class SettingsFragment : PreferenceFragmentCompat() {
private val suffix = listOf(
@@ -35,8 +50,8 @@ class SettingsActivity : AppCompatActivity() {
"TB" //really?
)
- private fun getCacheSize() : String {
- var size = context!!.cacheDir.walk().map { it.length() }.sum()
+ private fun getCacheSize(dir: File) : String {
+ var size = dir.walk().map { it.length() }.sum()
var suffixIndex = 0
while (size >= 1024) {
@@ -50,22 +65,43 @@ class SettingsActivity : AppCompatActivity() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
- with(findPreference("delete_cache")) {
+ with(findPreference("delete_image_cache")) {
this ?: return@with
- summary = getCacheSize()
+ val dir = File(context.cacheDir, "imageCache")
+
+ summary = getCacheSize(dir)
setOnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_cache_alert_message)
setPositiveButton(android.R.string.yes) { _, _ ->
- with(context.cacheDir) {
- if (exists())
- deleteRecursively()
- }
+ if (dir.exists())
+ dir.deleteRecursively()
- summary = getCacheSize()
+ summary = getCacheSize(dir)
+ }
+ setNegativeButton(android.R.string.no) { _, _ -> }
+ }.show()
+
+ true
+ }
+ }
+ with(findPreference("clear_history")) {
+ this ?: return@with
+
+ val histories = Histories.default
+
+ summary = getString(R.string.settings_clear_history_summary, histories.size)
+
+ setOnPreferenceClickListener {
+ AlertDialog.Builder(context).apply {
+ setTitle(R.string.warning)
+ setMessage(R.string.settings_clear_history_alert_message)
+ setPositiveButton(android.R.string.yes) { _, _ ->
+ histories.clear()
+ summary = getString(R.string.settings_clear_history_summary, histories.size)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()
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 b94db781..71851505 100644
--- a/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
+++ b/app/src/main/java/xyz/quaver/pupil/adapters/GalleryBlockAdapter.kt
@@ -33,8 +33,8 @@ class GalleryBlockAdapter(private val galleries: List Unit)? = null
- fun setClickListener(callback: ((Int) -> Unit)?) {
+ private var callback: ((Int, String) -> Unit)? = null
+ fun setClickListener(callback: ((Int, String) -> Unit)?) {
this.callback = callback
}
@@ -74,7 +74,7 @@ class GalleryBlockAdapter(private val galleries: List() {
+
+ init {
+ if (!file.exists())
+ file.parentFile.mkdirs()
+
+ try {
+ load()
+ } catch (e: Exception) {
+ save()
+ }
+ }
+
+ companion object {
+ lateinit var default: Histories
+
+ fun load(file: File) : Histories {
+ return Histories(file).load()
+ }
+ }
+
+ @UseExperimental(ImplicitReflectionSerializer::class)
+ fun load() : Histories {
+ return apply {
+ super.clear()
+ addAll(
+ Json(JsonConfiguration.Stable).parseList(
+ file.bufferedReader().use { it.readText() }
+ )
+ )
+ }
+ }
+
+ @UseExperimental(ImplicitReflectionSerializer::class)
+ fun save() {
+ file.writeText(Json(JsonConfiguration.Stable).stringify(this))
+ }
+
+ override fun add(element: Int): Boolean {
+ load()
+
+ if (contains(element))
+ super.remove(element)
+
+ super.add(0, element)
+
+ save()
+
+ return true
+ }
+
+ override fun remove(element: Int): Boolean {
+ load()
+ val retval = super.remove(element)
+ save()
+
+ return retval
+ }
+
+ override fun clear() {
+ super.clear()
+ save()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-anydpi/ic_history.xml b/app/src/main/res/drawable-anydpi/ic_history.xml
new file mode 100644
index 00000000..e5e6c6c8
--- /dev/null
+++ b/app/src/main/res/drawable-anydpi/ic_history.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable-anydpi/ic_home.xml b/app/src/main/res/drawable-anydpi/ic_home.xml
new file mode 100644
index 00000000..08ebbdf2
--- /dev/null
+++ b/app/src/main/res/drawable-anydpi/ic_home.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable-hdpi/ic_history.png b/app/src/main/res/drawable-hdpi/ic_history.png
new file mode 100644
index 00000000..8610919f
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_history.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_home.png b/app/src/main/res/drawable-hdpi/ic_home.png
new file mode 100644
index 00000000..caccfac0
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_home.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_history.png b/app/src/main/res/drawable-mdpi/ic_history.png
new file mode 100644
index 00000000..e64059c8
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_history.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_home.png b/app/src/main/res/drawable-mdpi/ic_home.png
new file mode 100644
index 00000000..1eccf9f3
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_home.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_history.png b/app/src/main/res/drawable-xhdpi/ic_history.png
new file mode 100644
index 00000000..4a9622d2
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_history.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_home.png b/app/src/main/res/drawable-xhdpi/ic_home.png
new file mode 100644
index 00000000..cea7b6c5
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_home.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_history.png b/app/src/main/res/drawable-xxhdpi/ic_history.png
new file mode 100644
index 00000000..c22644eb
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_history.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_home.png b/app/src/main/res/drawable-xxhdpi/ic_home.png
new file mode 100644
index 00000000..a576a1a4
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_home.png differ
diff --git a/app/src/main/res/drawable/side_nav_bar.png b/app/src/main/res/drawable/side_nav_bar.png
new file mode 100644
index 00000000..a399a2d5
Binary files /dev/null and b/app/src/main/res/drawable/side_nav_bar.png differ
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 77677f3d..3d702355 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,83 +1,25 @@
-
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/main_drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
+ tools:openDrawer="start">
-
+ android:layout_height="match_parent"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_gravity="start"
+ android:fitsSystemWindows="true"
+ app:headerLayout="@layout/nav_header_main"
+ app:menu="@menu/activity_main_drawer"/>
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main_content.xml b/app/src/main/res/layout/activity_main_content.xml
new file mode 100644
index 00000000..f0d84786
--- /dev/null
+++ b/app/src/main/res/layout/activity_main_content.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml
new file mode 100644
index 00000000..d801cf79
--- /dev/null
+++ b/app/src/main/res/layout/nav_header_main.xml
@@ -0,0 +1,12 @@
+
+
\ 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
new file mode 100644
index 00000000..1877c34f
--- /dev/null
+++ b/app/src/main/res/menu/activity_main_drawer.xml
@@ -0,0 +1,15 @@
+
+
\ 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 2d50f270..b102b437 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -1,6 +1,5 @@
- Pupil
言語: %1$s
シリーズ: %1$s
タイプ: %1$s
@@ -8,7 +7,7 @@
検索
ギャラリー検索
キャッシュ
- キャッシュクリア
+ イメージキャッシュクリア
キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?
キャッシュサイズ: %1$d%2$s
デフォルト検索キーワード
@@ -16,8 +15,19 @@
一回にロードするギャラリー数
検索設定
設定
- 新バージョン%1$sをリリースしました\n(現バージョン: %2$s)\nアップデートしますか?
アップデートダウンロード中
新しいアップデートがあります
注意
+ その他
+ ロード速度を向上させるためhiyobi.meからイメージロード
+ hiyobi.meからロード
+ 履歴の削除
+ 履歴を削除しますか?
+ 履歴数: %1$d
+ 履歴
+ トップ
+ ダウンロード中
+ # リリースノート(v%1$s)\n%2$s
+ セキュリティーモード
+ アプリ履歴でアプリの画面を表示しない
\ 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 c443a4b4..95f970f6 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -1,23 +1,33 @@
- Pupil
언어: %1$s
시리즈: %1$s
종류: %1$s
권한을 거부하면 일부 기능이 작동하지 않을 수 있습니다
갤러리 검색
기본 검색어
- 캐시 정리하기
+ 이미지 캐시 정리하기
캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?
현재 캐시 사용량: %1$d%2$s
한 번에 로드할 갤러리 수
검색 설정
설정
- 버전 %1$s이 출시되었습니다.\n(현재 %2$s)\n업데이트 하시겠습니까?
apk 다운로드중…
업데이트가 있습니다!
경고
결과 없음
검색
캐시
+ 기타
+ 속도 향상을 위해 가능하면 hiyobi.me에서 이미지 로드
+ hiyobi.me 사용
+ 히스토리 삭제
+ 히스토리를 삭제하시겠습니까?
+ 히스토리 %1$d개 저장됨
+ 히스토리
+ 홈
+ 다운로드 중
+ # 릴리즈 노트(v%1$s)\n%2$s
+ 최근 앱 목록 창에서 앱 화면을 보이지 않게 합니다
+ 보안 모드 활성화
\ No newline at end of file
diff --git a/app/src/main/res/values-v23/styles.xml b/app/src/main/res/values-v23/styles.xml
index 9c3f02d2..0cce6118 100644
--- a/app/src/main/res/values-v23/styles.xml
+++ b/app/src/main/res/values-v23/styles.xml
@@ -1,7 +1,6 @@