Skip to content

Commit

Permalink
- implemented downloading and install from app itself!
Browse files Browse the repository at this point in the history
  • Loading branch information
jakepurple13 committed Apr 9, 2021
1 parent 5f203a3 commit dc4adcf
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ interface GenericInfo {

fun customPreferences(preferenceScreen: SettingsDsl) = Unit

val apkString: String

}
168 changes: 163 additions & 5 deletions UIViews/src/main/java/com/programmersbox/uiviews/SettingsFragment.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package com.programmersbox.uiviews

import android.Manifest
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.webkit.URLUtil
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.FileProvider
import androidx.navigation.fragment.findNavController
import androidx.preference.*
import androidx.work.Constraints
Expand All @@ -13,7 +23,9 @@ import androidx.work.WorkManager
import com.bumptech.glide.Glide
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.firebase.auth.FirebaseUser
import com.programmersbox.helpfulutils.requestPermissions
import com.programmersbox.helpfulutils.runOnUIThread
import com.programmersbox.loggingutils.Loged
import com.programmersbox.models.sourcePublish
import com.programmersbox.thirdpartyutils.into
import com.programmersbox.thirdpartyutils.openInCustomChromeBrowser
Expand All @@ -22,11 +34,17 @@ import com.squareup.okhttp.OkHttpClient
import com.squareup.okhttp.Request
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.*
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.net.HttpURLConnection
import java.net.MalformedURLException
import java.net.URL
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.CoroutineContext

class SettingsFragment : PreferenceFragmentCompat() {

Expand All @@ -39,7 +57,7 @@ class SettingsFragment : PreferenceFragmentCompat() {

accountPreferences()
generalPreferences(genericInfo)
aboutPreferences()
aboutPreferences(genericInfo)

val settingsDsl = SettingsDsl()

Expand Down Expand Up @@ -171,7 +189,7 @@ class SettingsFragment : PreferenceFragmentCompat() {

}

private fun aboutPreferences() {
private fun aboutPreferences(genericInfo: GenericInfo) {
val checker = AtomicBoolean(false)
fun updateSetter() {
if (!checker.get()) {
Expand Down Expand Up @@ -211,7 +229,32 @@ class SettingsFragment : PreferenceFragmentCompat() {
p.isVisible = false
updateSetter()
p.setOnPreferenceClickListener {
requireContext().openInCustomChromeBrowser("https://github.com/jakepurple13/OtakuWorld/releases/latest")
MaterialAlertDialogBuilder(requireContext())
.setTitle("Update to ${p.summary}")
.setMessage("There's an update! Please update if you want to have the latest features!")
.setPositiveButton(R.string.update) { d, _ ->
activity?.requestPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE) {
if (it.isGranted) {
val isApkAlreadyThere =
File(context?.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!.absolutePath + "/", genericInfo.apkString)
if (isApkAlreadyThere.exists()) isApkAlreadyThere.delete()

DownloadApk(
requireContext(),
"https://github.com/jakepurple13/OtakuWorld/releases/latest/download/${genericInfo.apkString}",
genericInfo.apkString
).startDownloadingApk()
}
}
d.dismiss()
}
.setNeutralButton(R.string.gotoBrowser) { d, _ ->
context?.openInCustomChromeBrowser("https://github.com/jakepurple13/OtakuWorld/releases/latest")
d.dismiss()
}
.setNegativeButton(R.string.notNow) { d, _ -> d.dismiss() }
.show()

true
}
}
Expand Down Expand Up @@ -278,4 +321,119 @@ class SettingsDsl {
fun viewSettings(block: (PreferenceCategory) -> Unit) {
viewSettings = block
}
}

class DownloadApk(val context: Context, private val downloadUrl: String, private val outputName: String) : CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job // to run code in Main(UI) Thread

// call this method to cancel a coroutine when you don't need it anymore,
// e.g. when user closes the screen
fun cancel() {
job.cancel()
}

fun startDownloadingApk() {
if (URLUtil.isValidUrl(downloadUrl)) execute()
}

private lateinit var bar: AlertDialog

private fun execute() = launch {
onPreExecute()
val result = doInBackground() // runs in background thread without blocking the Main Thread
onPostExecute(result)
}

private suspend fun onProgressUpdate(vararg values: Int?) = withContext(Dispatchers.Main) {
val progress = values[0]
if (progress != null) {
bar.setMessage(if (progress > 99) "Finishing... " else "Downloading... $progress%")
}
}

@Suppress("BlockingMethodInNonBlockingContext")
private suspend fun doInBackground(): Boolean = withContext(Dispatchers.IO) { // to run code in Background Thread
// do async work
var flag = false

try {
val url = URL(downloadUrl)
val c = url.openConnection() as HttpURLConnection
c.requestMethod = "GET"
c.connect()
val path = Environment.getExternalStorageDirectory().toString() + "/Download/"
val file = File(path)
file.mkdirs()
val outputFile = File(file, outputName)

if (outputFile.exists()) outputFile.delete()

val fos = FileOutputStream(outputFile)
val inputStream = c.inputStream
val totalSize = c.contentLength.toFloat() //size of apk

val buffer = ByteArray(1024)
var len1: Int
var per: Float
var downloaded = 0f
while (inputStream.read(buffer).also { len1 = it } != -1) {
fos.write(buffer, 0, len1)
downloaded += len1
per = (downloaded * 100 / totalSize)
onProgressUpdate(per.toInt())
}
fos.close()
inputStream.close()
openNewVersion(path)
flag = true
} catch (e: MalformedURLException) {
Loged.e("Update Error: " + e.message, "DownloadApk")
flag = false
} catch (e: IOException) {
e.printStackTrace()
}
return@withContext flag
}

// Runs on the Main(UI) Thread
private fun onPreExecute() {
// show progress
bar = MaterialAlertDialogBuilder(context)
.setTitle("Updating...")
.setMessage("Downloading...")
.setCancelable(false)
.setIcon(OtakuApp.logo)
.show()
}

// Runs on the Main(UI) Thread
private fun onPostExecute(result: Boolean?) {
// hide progress
bar.dismiss()
if (result != null && result) {
Toast.makeText(context, "Update Done", Toast.LENGTH_SHORT)
.show()
} else {
Toast.makeText(context, "Error: Try Again", Toast.LENGTH_SHORT)
.show()
}
}

private fun openNewVersion(location: String) {
val intent = Intent(Intent.ACTION_VIEW)
intent.setDataAndType(getUriFromFile(location), "application/vnd.android.package-archive")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(intent)
}

private fun getUriFromFile(location: String): Uri {
return if (Build.VERSION.SDK_INT < 24) {
Uri.fromFile(File(location + outputName))
} else {
FileProvider.getUriForFile(context, context.packageName + ".provider", File(location + outputName))
}
}
}
12 changes: 12 additions & 0 deletions UIViews/src/main/res/xml/provider_paths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="external_files"
path="." />
<external-files-path
name="Download"
path="Download/" />
<external-path
name="Download1"
path="Download/" />
</paths>
6 changes: 4 additions & 2 deletions animeworld/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

<application
android:name=".AnimeApp"
Expand All @@ -18,6 +19,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.OtakuWorld"
android:usesCleartextTraffic="true">

<receiver
android:name="com.programmersbox.uiviews.DeleteNotificationReceiver"
android:enabled="true"
Expand Down Expand Up @@ -75,8 +77,8 @@
</receiver>

<provider
android:name=".GenericFileProvider"
android:authorities="com.programmersbox.animeworld.GenericFileProvider"
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class MainActivity : BaseMainActivity() {

}

override val apkString: String
get() = "animeworld-debug.apk"

override fun createAdapter(context: Context, baseListFragment: BaseListFragment): ItemListAdapter<RecyclerView.ViewHolder> =
(AnimeAdapter(context, baseListFragment) as ItemListAdapter<RecyclerView.ViewHolder>)

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
buildscript {
ext.kotlin_version = "1.4.32"
ext.jakepurple13 = "10.6.0"
ext.otakuVersionName = "3.0"
ext.otakuVersionName = "4.0"
repositories {
google()
jcenter()
Expand Down
11 changes: 11 additions & 0 deletions mangaworld/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

<application
android:name=".MangaApp"
Expand All @@ -16,6 +17,16 @@
android:theme="@style/Theme.OtakuWorld"
android:usesCleartextTraffic="true">

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>

<receiver
android:name="com.programmersbox.uiviews.DeleteNotificationReceiver"
android:enabled="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class MainActivity : BaseMainActivity() {
}
}

override val apkString: String
get() = "mangaworld-debug.apk"

override fun createAdapter(context: Context, baseListFragment: BaseListFragment): ItemListAdapter<RecyclerView.ViewHolder> =
(MangaGalleryAdapter(context, baseListFragment) as ItemListAdapter<RecyclerView.ViewHolder>)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.programmersbox.mangaworld

import androidx.core.content.FileProvider
import com.programmersbox.manga_sources.MangaContext
import com.programmersbox.manga_sources.Sources
import com.programmersbox.uiviews.OtakuApp
Expand Down Expand Up @@ -30,4 +31,6 @@ class MangaApp : OtakuApp() {
FirebaseDb.READ_OR_WATCHED_ID = "chapterCount"

}
}
}

class GenericFileProvider : FileProvider()

0 comments on commit dc4adcf

Please sign in to comment.