Open from homescreen/add shortcut to launcher (#435)

* Add very basic "Add to homescreen" action in manga info fragment.

* Fix open from homescreen opening current manga (if a manga is open).
Code cleanup.

* Improve fix for "Opening from homescreen opens currently open manga if a manga is currently open" and fix "Going back to the main app via a Manga opened through a shortcut repeats the launcher open animation".

* Implement custom icons, add star icon and optimize some things.

* Remove Tachiyomi and custom image icon types.

* Move icon creation task into an observable.
Added some extra error handling.
This commit is contained in:
Andy Bao 2016-09-29 12:38:29 -04:00 committed by inorichi
parent a81609fd2c
commit d352405ba6
8 changed files with 135 additions and 1 deletions

View file

@ -153,6 +153,8 @@ dependencies {
// Image library // Image library
compile 'com.github.bumptech.glide:glide:3.7.0' compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar' compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
// Transformations
compile 'jp.wasabeef:glide-transformations:2.0.1'
// Logging // Logging
compile 'com.jakewharton.timber:timber:4.3.0' compile 'com.jakewharton.timber:timber:4.3.0'

View file

@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<application <application
android:name=".App" android:name=".App"
@ -28,7 +29,8 @@
</activity> </activity>
<activity <activity
android:name=".ui.manga.MangaActivity" android:name=".ui.manga.MangaActivity"
android:parentActivityName=".ui.main.MainActivity" > android:parentActivityName=".ui.main.MainActivity"
android:exported="true">
</activity> </activity>
<activity <activity
android:name=".ui.reader.ReaderActivity" android:name=".ui.reader.ReaderActivity"

View file

@ -24,6 +24,7 @@ class MangaActivity : BaseRxActivity<MangaPresenter>() {
const val FROM_CATALOGUE_EXTRA = "from_catalogue" const val FROM_CATALOGUE_EXTRA = "from_catalogue"
const val MANGA_EXTRA = "manga" const val MANGA_EXTRA = "manga"
const val FROM_LAUNCHER_EXTRA = "from_launcher"
const val INFO_FRAGMENT = 0 const val INFO_FRAGMENT = 0
const val CHAPTERS_FRAGMENT = 1 const val CHAPTERS_FRAGMENT = 1
const val MYANIMELIST_FRAGMENT = 2 const val MYANIMELIST_FRAGMENT = 2
@ -47,6 +48,11 @@ class MangaActivity : BaseRxActivity<MangaPresenter>() {
super.onCreate(savedState) super.onCreate(savedState)
setContentView(R.layout.activity_manga) setContentView(R.layout.activity_manga)
val fromLauncher = intent.getBooleanExtra(FROM_LAUNCHER_EXTRA, false)
//Remove any current manga if we are launching from launcher
if(fromLauncher) SharedData.remove(MangaEvent::class.java)
presenter.setMangaEvent(SharedData.getOrPut(MangaEvent::class.java) { presenter.setMangaEvent(SharedData.getOrPut(MangaEvent::class.java) {
val id = intent.getLongExtra(MANGA_EXTRA, 0) val id = intent.getLongExtra(MANGA_EXTRA, 0)
MangaEvent(presenter.db.getManga(id).executeAsBlocking()!!) MangaEvent(presenter.db.getManga(id).executeAsBlocking()!!)

View file

@ -1,21 +1,44 @@
package eu.kanade.tachiyomi.ui.manga.info package eu.kanade.tachiyomi.ui.manga.info
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.customtabs.CustomTabsIntent import android.support.customtabs.CustomTabsIntent
import android.support.design.widget.Snackbar
import android.util.SparseArray
import android.view.* import android.view.*
import com.afollestad.materialdialogs.MaterialDialog
import com.bumptech.glide.BitmapRequestBuilder
import com.bumptech.glide.BitmapTypeRequest
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.data.source.Source
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.OnlineSource
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.library.LibraryFragment
import eu.kanade.tachiyomi.ui.manga.MangaActivity
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import jp.wasabeef.glide.transformations.CropCircleTransformation
import jp.wasabeef.glide.transformations.CropSquareTransformation
import jp.wasabeef.glide.transformations.MaskTransformation
import jp.wasabeef.glide.transformations.RoundedCornersTransformation
import kotlinx.android.synthetic.main.fragment_manga_info.* import kotlinx.android.synthetic.main.fragment_manga_info.*
import kotlinx.android.synthetic.main.item_download.*
import nucleus.factory.RequiresPresenter import nucleus.factory.RequiresPresenter
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import timber.log.Timber
import java.io.IOException
import kotlin.concurrent.thread
/** /**
* Fragment that shows manga information. * Fragment that shows manga information.
@ -34,6 +57,7 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
fun newInstance(): MangaInfoFragment { fun newInstance(): MangaInfoFragment {
return MangaInfoFragment() return MangaInfoFragment()
} }
} }
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
@ -61,6 +85,7 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
when (item.itemId) { when (item.itemId) {
R.id.action_open_in_browser -> openInBrowser() R.id.action_open_in_browser -> openInBrowser()
R.id.action_share -> shareManga() R.id.action_share -> shareManga()
R.id.action_add_to_home_screen -> addToHomeScreen()
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
} }
return true return true
@ -178,6 +203,80 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
} }
} }
/**
* Add the manga to the home screen
*/
fun addToHomeScreen() {
val shortcutIntent = activity.intent
shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
.putExtra(MangaActivity.FROM_LAUNCHER_EXTRA, true)
val addIntent = Intent()
addIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent)
.action = "com.android.launcher.action.INSTALL_SHORTCUT"
//Set shortcut title
MaterialDialog.Builder(activity)
.title(R.string.shortcut_title)
.input("", presenter.manga.title, { md, text ->
//Set shortcut title
addIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, text.toString())
reshapeIconBitmap(addIntent,
Glide.with(context).load(presenter.manga).asBitmap())
})
.negativeText(android.R.string.cancel)
.onNegative { materialDialog, dialogAction -> materialDialog.cancel() }
.show()
}
fun reshapeIconBitmap(addIntent: Intent, request: BitmapTypeRequest<out Any>) {
val modes = intArrayOf(R.string.circular_icon,
R.string.rounded_icon,
R.string.square_icon,
R.string.star_icon)
fun BitmapRequestBuilder<out Any, Bitmap>.toIcon(): Bitmap {
return this.into(96, 96).get()
}
MaterialDialog.Builder(activity)
.title(R.string.icon_shape)
.negativeText(android.R.string.cancel)
.items(modes.map { getString(it) })
.itemsCallback { dialog, view, i, charSequence ->
Observable.fromCallable {
// i = 0: Circular icon
// i = 1: Rounded icon
// i = 2: Square icon
// i = 3: Star icon (because boredom)
when (i) {
0 -> request.transform(CropCircleTransformation(context)).toIcon()
1 -> request.transform(RoundedCornersTransformation(context, 5, 0)).toIcon()
2 -> request.transform(CropSquareTransformation(context)).toIcon()
3 -> request.transform(CenterCrop(context), MaskTransformation(context, R.drawable.mask_star)).toIcon()
else -> null
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ if (it != null) createShortcut(addIntent, it) },
{ context.toast(R.string.icon_creation_fail) })
}.show()
}
fun createShortcut(addIntent: Intent, icon: Bitmap) {
//Send shortcut intent
addIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon)
context.sendBroadcast(addIntent)
//Go to launcher to show this shiny new shortcut!
val startMain = Intent(Intent.ACTION_MAIN)
startMain.addCategory(Intent.CATEGORY_HOME)
.flags = Intent.FLAG_ACTIVITY_NEW_TASK
activity.runOnUiThread {
startActivity(startMain)
}
}
/** /**
* Update FAB with correct drawable. * Update FAB with correct drawable.
* *

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#ffffff"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

View file

@ -12,4 +12,8 @@
android:title="@string/action_open_in_browser" android:title="@string/action_open_in_browser"
app:showAsAction="never"/> app:showAsAction="never"/>
<item android:id="@+id/action_add_to_home_screen"
android:title="@string/action_add_to_home_screen"
app:showAsAction="never"/>
</menu> </menu>

View file

@ -51,6 +51,7 @@
<string name="action_remove">Remove</string> <string name="action_remove">Remove</string>
<string name="action_resume">Resume</string> <string name="action_resume">Resume</string>
<string name="action_open_in_browser">Open in browser</string> <string name="action_open_in_browser">Open in browser</string>
<string name="action_add_to_home_screen">Add to home screen</string>
<string name="action_display_mode">Change display mode</string> <string name="action_display_mode">Change display mode</string>
<string name="action_set_filter">Set filter</string> <string name="action_set_filter">Set filter</string>
<string name="action_cancel">Cancel</string> <string name="action_cancel">Cancel</string>
@ -225,6 +226,16 @@
<string name="manga_info_genres_label">Genres</string> <string name="manga_info_genres_label">Genres</string>
<string name="share_subject">Share…</string> <string name="share_subject">Share…</string>
<string name="share_text">Check out %1$s! at %2$s</string> <string name="share_text">Check out %1$s! at %2$s</string>
<string name="added_to_home_screen">Manga added to home screen</string>
<string name="icon_type">Icon type</string>
<string name="tachiyomi_icon">Tachiyomi icon</string>
<string name="circular_icon">Circular icon</string>
<string name="rounded_icon">Rounded icon</string>
<string name="square_icon">Square icon</string>
<string name="star_icon">Star icon</string>
<string name="shortcut_title">Shortcut title</string>
<string name="icon_shape">Icon shape</string>
<string name="icon_creation_fail">Failed to create shortcut!</string>
<!-- Manga chapters fragment --> <!-- Manga chapters fragment -->
<string name="manga_chapters_tab">Chapters</string> <string name="manga_chapters_tab">Chapters</string>
@ -305,6 +316,7 @@
<!-- File Picker Titles --> <!-- File Picker Titles -->
<string name="file_select_cover">Select cover image</string> <string name="file_select_cover">Select cover image</string>
<string name="file_select_backup">Select backup file</string> <string name="file_select_backup">Select backup file</string>
<string name="file_select_icon">Select shortcut icon</string>
<!--UpdateCheck--> <!--UpdateCheck-->
<string name="update_check_title">New update available!</string> <string name="update_check_title">New update available!</string>