Added update feature

This commit is contained in:
tom5079
2019-05-12 19:15:53 +09:00
parent 06c7d77497
commit abd2f3ae17
11 changed files with 174 additions and 10 deletions

View File

@@ -1,6 +1,7 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlinx-serialization'
android { android {
compileSdkVersion 28 compileSdkVersion 28
@@ -22,9 +23,10 @@ android {
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1' 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-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.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.1.0-alpha05' implementation 'androidx.preference:preference:1.1.0-alpha05'

View File

@@ -3,6 +3,8 @@
package="xyz.quaver.pupil"> package="xyz.quaver.pupil">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -12,6 +14,17 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="xyz.quaver.pupil.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths"/>
</provider>
<activity android:name=".GalleryActivity" <activity android:name=".GalleryActivity"
android:configChanges="keyboardHidden|orientation|screenSize"/> android:configChanges="keyboardHidden|orientation|screenSize"/>
<activity <activity

View File

@@ -1,14 +1,27 @@
package xyz.quaver.pupil package xyz.quaver.pupil
import android.Manifest
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.text.* import android.text.*
import android.text.style.AlignmentSpan import android.text.style.AlignmentSpan
import android.util.Log
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@@ -22,10 +35,14 @@ import xyz.quaver.hitomi.*
import xyz.quaver.pupil.adapters.GalleryBlockAdapter import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.TagSuggestion import xyz.quaver.pupil.types.TagSuggestion
import xyz.quaver.pupil.util.SetLineOverlap import xyz.quaver.pupil.util.SetLineOverlap
import xyz.quaver.pupil.util.checkUpdate
import xyz.quaver.pupil.util.getApkUrl
import java.io.File
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private val PERMISSION_REQUEST_CODE = 4585
private val galleries = ArrayList<Pair<GalleryBlock, Bitmap?>>() private val galleries = ArrayList<Pair<GalleryBlock, Bitmap?>>()
private var isLoading = false private var isLoading = false
@@ -35,6 +52,10 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
checkPermission()
update()
main_appbar_layout.addOnOffsetChangedListener( main_appbar_layout.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { _, p1 -> AppBarLayout.OnOffsetChangedListener { _, p1 ->
main_searchview.translationY = p1.toFloat() main_searchview.translationY = p1.toFloat()
@@ -58,6 +79,79 @@ class MainActivity : AppCompatActivity() {
fetchGalleries(query) fetchGalleries(query)
} }
private fun checkPermission() {
val permissions = arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
if (permissions.any { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }) {
if (permissions.any { ActivityCompat.shouldShowRequestPermissionRationale(this, it) })
AlertDialog.Builder(this).apply {
setTitle(R.string.warning)
setMessage(R.string.permission_explain)
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
else
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE)
}
}
private fun update() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
return
CoroutineScope(Dispatchers.Default).launch {
val update =
checkUpdate(getString(R.string.release_url), BuildConfig.VERSION_NAME) ?: return@launch
val (url, fileName) = getApkUrl(update, getString(R.string.release_name)) ?: return@launch
val dialog = AlertDialog.Builder(this@MainActivity).apply {
setTitle(R.string.update_title)
setMessage(getString(R.string.update_message, update["tag_name"], BuildConfig.VERSION_NAME))
setPositiveButton(android.R.string.yes) { _, _ ->
val dest = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName)
val desturi =
FileProvider.getUriForFile(
applicationContext,
"xyz.quaver.pupil.provider",
dest
)
if (dest.exists())
dest.delete()
val request = DownloadManager.Request(Uri.parse(url)).apply {
setDescription(getString(R.string.update_notification_description))
setTitle(getString(R.string.app_name))
setDestinationUri(Uri.fromFile(dest))
}
val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val id = manager.enqueue(request)
registerReceiver(object: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val install = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_GRANT_READ_URI_PERMISSION
setDataAndType(desturi, manager.getMimeTypeForDownloadedFile(id))
}
startActivity(install)
unregisterReceiver(this)
finish()
}
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
setNegativeButton(android.R.string.no) { _, _ ->}
}
launch(Dispatchers.Main) {
dialog.show()
}
}
}
private fun setupRecyclerView() { private fun setupRecyclerView() {
with(main_recyclerview) { with(main_recyclerview) {
adapter = GalleryBlockAdapter(galleries).apply { adapter = GalleryBlockAdapter(galleries).apply {

View File

@@ -51,8 +51,8 @@ class SettingsActivity : AppCompatActivity() {
setOnPreferenceClickListener { setOnPreferenceClickListener {
AlertDialog.Builder(context).apply { AlertDialog.Builder(context).apply {
setTitle(getString(R.string.settings_delete_cache_alert_title)) setTitle(R.string.warning)
setMessage(getString(R.string.settings_delete_cache_alert_message)) setMessage(R.string.settings_delete_cache_alert_message)
setPositiveButton(android.R.string.yes) { _, _ -> setPositiveButton(android.R.string.yes) { _, _ ->
with(context.cacheDir) { with(context.cacheDir) {
if (exists()) if (exists())

View File

@@ -0,0 +1,31 @@
package xyz.quaver.pupil.util
import kotlinx.serialization.json.*
import java.net.URL
fun getReleases(url: String) : JsonArray {
return URL(url).readText().let {
Json(JsonConfiguration.Stable).parse(JsonArray.serializer(), it)
}
}
fun checkUpdate(url: String, currentVersion: String) : JsonObject? {
val releases = getReleases(url)
if (releases.isEmpty())
return null
if (currentVersion != releases[0].jsonObject["tag_name"]?.content)
return releases[0].jsonObject
return null
}
fun getApkUrl(releases: JsonObject, releaseName: String) : Pair<String?, String?>? {
releases["assets"]?.jsonArray?.forEach {
if (Regex(releaseName).matches(it.jsonObject["name"]?.content ?: ""))
return Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content)
}
return null
}

View File

@@ -1,6 +1,17 @@
<resources> <resources>
<string name="warning">Warning</string>
<string name="app_name">Pupil</string> <string name="app_name">Pupil</string>
<string name="permission_explain">Denying any permission can deactivate some functions</string>
<string name="release_url">https://api.github.com/repos/tom5079/Pupil-issue/releases</string>
<string name="release_name">Pupil-v(\\d+\\.)+\\d+\\.apk</string>
<string name="update_title">Update available</string>
<string name="update_message">Version %1$s is available!\n(current version is %2$s)\nDo you want to update?</string>
<string name="update_notification_description">Downloading apk&#8230;</string>
<string name="main_settings">Settings</string> <string name="main_settings">Settings</string>
<string name="main_search">Search</string> <string name="main_search">Search</string>
<string name="main_no_result">No result</string> <string name="main_no_result">No result</string>
@@ -26,7 +37,6 @@
<string name="settings_delete_cache">Delete Cache</string> <string name="settings_delete_cache">Delete Cache</string>
<string name="settings_delete_cache_summary">Currently using %1$d%2$s of cache</string> <string name="settings_delete_cache_summary">Currently using %1$d%2$s of cache</string>
<string name="settings_delete_cache_alert_title">Warning</string>
<string name="settings_delete_cache_alert_message">Deleting cache can affect image loading speed. Do you want to continue?</string> <string name="settings_delete_cache_alert_message">Deleting cache can affect image loading speed. Do you want to continue?</string>
</resources> </resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="Download" path="Download"/>
</paths>

View File

@@ -1,5 +1,8 @@
package xyz.quaver.pupil package xyz.quaver.pupil
import org.junit.Test
import xyz.quaver.pupil.util.checkUpdate
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).
* *
@@ -8,4 +11,9 @@ package xyz.quaver.pupil
class ExampleUnitTest { class ExampleUnitTest {
@Test
fun test() {
print(checkUpdate("https://api.github.com/repos/tom5079/Pupil-issue/releases", "0.0.1"))
}
} }

View File

@@ -5,12 +5,12 @@ buildscript {
repositories { repositories {
google() google()
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.4.0' classpath 'com.android.tools.build:gradle:3.4.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} }

View File

@@ -4,7 +4,7 @@ apply plugin: 'kotlinx-serialization'
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0" implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0"
implementation 'org.jsoup:jsoup:1.11.3' implementation 'org.jsoup:jsoup:1.11.3'
@@ -19,7 +19,6 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
} }

View File

@@ -3,6 +3,8 @@ package xyz.quaver.hitomi
import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.list
import kotlinx.serialization.parseList import kotlinx.serialization.parseList
import org.jsoup.Jsoup import org.jsoup.Jsoup
import java.net.URL import java.net.URL
@@ -21,7 +23,6 @@ data class Reader(
val images: List<Pair<URL, GalleryInfo?>> val images: List<Pair<URL, GalleryInfo?>>
) )
//Set header `Referer` to reader url to avoid 403 error //Set header `Referer` to reader url to avoid 403 error
@UseExperimental(ImplicitReflectionSerializer::class)
fun getReader(galleryID: Int) : Reader { fun getReader(galleryID: Int) : Reader {
val readerUrl = "https://hitomi.la/reader/$galleryID.html" val readerUrl = "https://hitomi.la/reader/$galleryID.html"
val galleryInfoUrl = "https://ltn.hitomi.la/galleries/$galleryID.js" val galleryInfoUrl = "https://ltn.hitomi.la/galleries/$galleryID.js"
@@ -36,8 +37,10 @@ fun getReader(galleryID: Int) : Reader {
val galleryInfo = ArrayList<GalleryInfo?>() val galleryInfo = ArrayList<GalleryInfo?>()
galleryInfo.addAll(Json.parseList( galleryInfo.addAll(
Regex("""\[.+\]""").find( Json(JsonConfiguration.Stable).parse(
GalleryInfo.serializer().list,
Regex("""\[.+]""").find(
URL(galleryInfoUrl).readText() URL(galleryInfoUrl).readText()
)?.value ?: "[]" )?.value ?: "[]"
) )