From 2146e4766322fc48f4fef9bd3f8a821a5fe7a294 Mon Sep 17 00:00:00 2001 From: Syahriel Ibnu Irfansyah Date: Sun, 29 Nov 2020 06:11:23 +0700 Subject: [PATCH] ( #1 ) PSP Enhancement: Hide clock bar & show title-subtitle separator Background color can now be adjusted (now only for main Vsh View) --- app/src/main/java/id/psw/vshlauncher/Patch.kt | 29 ++++++ app/src/main/java/id/psw/vshlauncher/VSH.kt | 45 ++++++++- .../psw/vshlauncher/VshActivityExtension.kt | 34 +++++++ .../vshlauncher/customtypes/HexInputFilter.kt | 31 ++++++ .../id/psw/vshlauncher/icontypes/VideoIcon.kt | 98 +++++++++---------- .../java/id/psw/vshlauncher/views/VshView.kt | 20 +++- app/src/main/res/layout/activity_xmbvp.xml | 1 + 7 files changed, 198 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/id/psw/vshlauncher/customtypes/HexInputFilter.kt diff --git a/app/src/main/java/id/psw/vshlauncher/Patch.kt b/app/src/main/java/id/psw/vshlauncher/Patch.kt index 57f4f01..727493e 100644 --- a/app/src/main/java/id/psw/vshlauncher/Patch.kt +++ b/app/src/main/java/id/psw/vshlauncher/Patch.kt @@ -3,6 +3,12 @@ package id.psw.vshlauncher import android.graphics.Bitmap import android.graphics.Color import android.graphics.Rect +import android.os.Handler +import android.os.HandlerThread +import android.view.PixelCopy +import android.widget.VideoView +import java.lang.Exception +import java.lang.Integer.parseInt import kotlin.math.ceil import kotlin.math.floor import kotlin.math.roundToInt @@ -71,4 +77,27 @@ fun Bitmap.getDominantColorMean(alsoCalculateAlpha:Boolean = false):Int{ val finalAlpha = if(alsoCalculateAlpha) a / validPixels else 255 return Color.argb(finalAlpha,r/validPixels,g/validPixels,b/validPixels) +} + +fun VideoView.getFrame(width:Int, height:Int, callback: (Bitmap?) -> Unit){ + val bitmap : Bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565) + try{ + val ht = HandlerThread("") + + }catch(e:Exception){} +} + +fun String.hexColorToInt():Int{ + if(this.length <= 8) throw IllegalArgumentException("Please use #AARRGGBB hex format, e.g #FF00FF00 for opaque green") + if(this[0] != '#') throw IllegalArgumentException("Use # prefix please") + val bd = StringBuilder(2) + bd.clear().append(this[1]).append(this[2]) + val aa = parseInt(bd.toString(), 16) + bd.clear().append(this[3]).append(this[4]) + val rr = parseInt(bd.toString(), 16) + bd.clear().append(this[5]).append(this[6]) + val gg = parseInt(bd.toString(), 16) + bd.clear().append(this[7]).append(this[8]) + val bb = parseInt(bd.toString(), 16) + return Color.argb(aa,rr,gg,bb) } \ No newline at end of file diff --git a/app/src/main/java/id/psw/vshlauncher/VSH.kt b/app/src/main/java/id/psw/vshlauncher/VSH.kt index 0ae38bf..44678ff 100644 --- a/app/src/main/java/id/psw/vshlauncher/VSH.kt +++ b/app/src/main/java/id/psw/vshlauncher/VSH.kt @@ -67,6 +67,7 @@ class VSH : AppCompatActivity(), VshDialogView.IDialogBackable { const val PREF_USE_GAMEBOOT = "xmb_USE_GAMEBOOT" const val PREF_DYNAMIC_TWINKLE = "xmb_DYNAMIC_P3T" const val PREF_IS_FIRST_RUN = "xmb_not_new_user" + const val PREF_BACKGROUND_COLOR = "xmb_menu_backgroundColor" } private var returnFromGameboot = false @@ -195,6 +196,7 @@ class VSH : AppCompatActivity(), VshDialogView.IDialogBackable { scrOrientation = prefs.getInt(PREF_ORIENTATION_KEY, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) useGameBoot = prefs.getBoolean(PREF_USE_GAMEBOOT, true) dynamicThemeTwinkles = prefs.getBoolean(PREF_DYNAMIC_TWINKLE, true) + VshView.menuBackgroundColor = prefs.getInt(PREF_BACKGROUND_COLOR, Color.argb(0,0,0,0)) } private fun checkFileReadWritePermission(){ @@ -309,7 +311,34 @@ class VSH : AppCompatActivity(), VshDialogView.IDialogBackable { settings.items.add( VshSettingIcon( - 0xd18034, this, + 0xd1805, this, + "Set Menu Background Color", VshSettingIcon.ICON_ANDROID, + { showBackgroundColorDialog() }, + { "Menu background (hidden when menu is hidden)" } + ) + ) + + settings.items.add( + VshSettingIcon( + 0xd1806, this, + "Hide Clock Bar", VshSettingIcon.ICON_ANDROID, + { VshView.hideClock = !VshView.hideClock }, + { VshView.hideClock.toLocalizedString() } + ) + ) + + settings.items.add( + VshSettingIcon( + 0xd1807, this, + "Show Description Separator", VshSettingIcon.ICON_ANDROID, + { VshView.descriptionSeparator = !VshView.descriptionSeparator }, + { VshView.descriptionSeparator.toLocalizedString() } + ) + ) + + settings.items.add( + VshSettingIcon( + 0xd1808, this, getString(R.string.setting_gameboot_custom_guide), VshSettingIcon.ICON_ANDROID, { launchURL("https://github.com/EmiyaSyahriel/CrossLauncher#animation-modding" )}, { getString(R.string.common_click_here) } @@ -394,7 +423,6 @@ class VSH : AppCompatActivity(), VshDialogView.IDialogBackable { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { if(!isOnMenu) return false var retval = false - val confirmButton = xMarksTheSpot.choose(KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B) when(keyCode){ KeyEvent.KEYCODE_DPAD_UP -> { @@ -413,7 +441,7 @@ class VSH : AppCompatActivity(), VshDialogView.IDialogBackable { vsh.setSelection(1,0) retval = true } - KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_DPAD_CENTER, confirmButton ->{ + KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_DPAD_CENTER, mimickedConfirmButton ->{ if(vsh.isOnOptions){ vsh.executeCurrentOptionItem() }else{ @@ -421,6 +449,13 @@ class VSH : AppCompatActivity(), VshDialogView.IDialogBackable { } retval = true } + KeyEvent.KEYCODE_DEL, mimickedCancelButton ->{ + if(vsh.isOnOptions){ + vsh.switchOptionPopupVisibility() + }else{ + vsh.hideMenu = !vsh.hideMenu + } + } KeyEvent.KEYCODE_MENU, KeyEvent.KEYCODE_TAB, KeyEvent.KEYCODE_BUTTON_Y -> { vsh.isOnOptions = !vsh.isOnOptions retval = true @@ -437,7 +472,7 @@ class VSH : AppCompatActivity(), VshDialogView.IDialogBackable { if(yIndex < 0) yIndex = vsh.selectedY vsh.setSelectionAbs(vsh.selectedX, yIndex) } - }catch (e:java.lang.Exception){} + }catch (e : Exception){} } } return retval || super.onKeyDown(keyCode, event) @@ -612,7 +647,7 @@ class VSH : AppCompatActivity(), VshDialogView.IDialogBackable { try{ startActivity(intent) overridePendingTransition(R.anim.anim_ps3_zoomfadein, R.anim.anim_ps3_zoomfadeout) - }catch (e:java.lang.Exception){ + }catch (e: Exception){ Toast.makeText(this, "This video type is not supported", Toast.LENGTH_SHORT).show() } } diff --git a/app/src/main/java/id/psw/vshlauncher/VshActivityExtension.kt b/app/src/main/java/id/psw/vshlauncher/VshActivityExtension.kt index e6759a5..58aaa63 100644 --- a/app/src/main/java/id/psw/vshlauncher/VshActivityExtension.kt +++ b/app/src/main/java/id/psw/vshlauncher/VshActivityExtension.kt @@ -1,9 +1,16 @@ package id.psw.vshlauncher +import android.app.AlertDialog import android.content.Intent import android.content.pm.PackageManager import android.net.Uri +import android.text.Editable +import android.text.InputType +import android.text.method.DigitsKeyListener +import android.widget.EditText import android.widget.Toast +import id.psw.vshlauncher.customtypes.HexInputFilter +import id.psw.vshlauncher.views.VshView /** * A file that contains extensions for the main VSH Activity @@ -40,3 +47,30 @@ fun VSH.launchURL(url:String){ Toast.makeText(this, "No default browser found in device.", Toast.LENGTH_SHORT).show() } } + +/** TODO: Extend to use XMB-like dialog instead */ +fun VSH.showBackgroundColorDialog(){ + val textView = EditText(this) + val currentColor = VshView.menuBackgroundColor.toString(16) + textView.inputType= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS + textView.filters = arrayOf(HexInputFilter()) + textView.text.clear() + textView.text.append("#$currentColor") + AlertDialog.Builder(this) + .setView(textView) + .setPositiveButton("Set"){ dialog,_ -> + try{ + VshView.menuBackgroundColor = textView.text.toString().hexColorToInt() + prefs.edit().putInt(VSH.PREF_BACKGROUND_COLOR, VshView.menuBackgroundColor).apply() + dialog.dismiss() + }catch(e:Exception){ + Toast.makeText(this, e.message, Toast.LENGTH_LONG).show() + } + } + .setNegativeButton("Cancel"){ dialog,_ -> + dialog.dismiss() + } + .setTitle("Set Menu Background Color") + .create() + .show() +} \ No newline at end of file diff --git a/app/src/main/java/id/psw/vshlauncher/customtypes/HexInputFilter.kt b/app/src/main/java/id/psw/vshlauncher/customtypes/HexInputFilter.kt new file mode 100644 index 0000000..71e10b1 --- /dev/null +++ b/app/src/main/java/id/psw/vshlauncher/customtypes/HexInputFilter.kt @@ -0,0 +1,31 @@ +package id.psw.vshlauncher.customtypes + +import android.text.InputFilter +import android.text.Spanned +import java.util.* + + +class HexInputFilter : InputFilter { + companion object{ + private val hexItems = "#0123456789ABCDEFabcdef" + } + + override fun filter( + source: CharSequence?, + start: Int, + end: Int, + dest: Spanned?, + dstart: Int, + dend: Int + ): CharSequence { + + val sb = StringBuilder() + + for (i in start until end) { + val chr = (source?.get(i) ?: ' ').toUpperCase() + val validHex = hexItems.contains(chr) + if(validHex) sb.append(chr) + } + return sb.toString().toUpperCase(Locale.ROOT) + } +} \ No newline at end of file diff --git a/app/src/main/java/id/psw/vshlauncher/icontypes/VideoIcon.kt b/app/src/main/java/id/psw/vshlauncher/icontypes/VideoIcon.kt index 5b7b3fa..b7eb936 100644 --- a/app/src/main/java/id/psw/vshlauncher/icontypes/VideoIcon.kt +++ b/app/src/main/java/id/psw/vshlauncher/icontypes/VideoIcon.kt @@ -1,21 +1,24 @@ package id.psw.vshlauncher.icontypes import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Color import android.graphics.Point +import android.graphics.SurfaceTexture +import android.media.MediaMetadataRetriever +import android.media.MediaPlayer import android.media.ThumbnailUtils import android.net.Uri import android.provider.MediaStore +import android.view.Surface +import android.view.SurfaceView +import android.view.TextureView import android.widget.Toast -import android.widget.VideoView import androidx.core.graphics.scale import id.psw.vshlauncher.* +import id.psw.vshlauncher.views.VshView import java.io.File import java.lang.Exception -// TODO : fix cursor is mostly null when created this icon, causing the icon appear corrupted -class VideoIcon(itemID:Int, private val vsh: VSH, private val path: String) : VshY(itemID) { +class VideoIcon(itemID:Int, private val vsh: VSH, private val path: String) : VshY(itemID), TextureView.SurfaceTextureListener { companion object{ var doVideoPreview = false @@ -34,14 +37,21 @@ class VideoIcon(itemID:Int, private val vsh: VSH, private val path: String) : Vs private var metadata : VideoMetadata private var isValid = false - private var thumbnailVp : VideoView? = null private var videoReady = false - private var lastPreviewPos = 0 - private var thumbnailStart = 0 - private var thumbnailEnd = 0 + private var currentTime = 0f + private var isPaused = false + private var thumbnailStart = 0f + private var thumbnailEnd = 10f + private var fps = 30 + + private var mp = MediaPlayer() + private var texView = TextureView(vsh) + private var srView = SurfaceView(vsh) + private var surface : Surface? = null + private var scaledSelectedAlbumArt : Bitmap = transparentBitmap private var scaledUnselectedAlbumArt : Bitmap = transparentBitmap - + private var currentVideoBitmap : Bitmap = transparentBitmap init{ loadCorruptedIcon() @@ -51,9 +61,9 @@ class VideoIcon(itemID:Int, private val vsh: VSH, private val path: String) : Vs val size = file.length().toSize() val albumArt = ThumbnailUtils.createVideoThumbnail(file.absolutePath, MediaStore.Video.Thumbnails.MINI_KIND) ?: transparentBitmap bakeAlbumArt(albumArt) - loadVideoPreview() - thumbnailStart = 0 - thumbnailEnd = 30000 + thumbnailStart = 0f + thumbnailEnd = 10f + startGrabberThread() isValid=true VideoMetadata(itemID, file, fileName, size, albumArt) }catch(e:Exception){ @@ -85,59 +95,45 @@ class VideoIcon(itemID:Int, private val vsh: VSH, private val path: String) : Vs * TODO: Can be optimized further by caching the canvas and bitmap to variable */ private fun getCurrentVideoBitmap(selected:Boolean = true) : Bitmap{ + val width = (if(selected) selectedIconSizeWidth else unselectedIconSize).toInt() * vsh.vsh.density val height = (if(selected) selectedIconSize else unselectedIconSize).toInt()* vsh.vsh.density if(doVideoPreview) { - val tvp = thumbnailVp - if(tvp != null){ - val retval = Bitmap.createBitmap(width.toInt(), height.toInt(), Bitmap.Config.ARGB_8888) - val retCan = Canvas(retval) - retCan.drawColor(Color.BLACK) - tvp.draw(retCan) - return retval - } + currentTime += VshView.deltaTime + if(currentTime >= thumbnailEnd) currentTime = thumbnailStart + return currentVideoBitmap } - return if(selected) scaledSelectedAlbumArt else scaledUnselectedAlbumArt } - /** - * Load video preview of the view in case user do set it to true - */ - private fun loadVideoPreview(){ - if(doVideoPreview){ - val tvp = VideoView(vsh) - // set it to loop and mute audio since we shouldn't play the audio on preview anyway - tvp.setOnPreparedListener { - with(it) { - isLooping = true - setVolume(0f, 0f) - } - videoReady = true - } - tvp.setVideoPath(path) - thumbnailVp = tvp - } + override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) { + this.surface = Surface(surface) } + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) { } + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean = false + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) { this.surface = Surface(surface) } + /** * Pause the video player if */ override fun onHidden() { - val tvp = thumbnailVp - if(tvp != null && doVideoPreview){ - if(tvp.canPause()){ - lastPreviewPos = tvp.currentPosition - tvp.pause() - } - } + isPaused = true } override fun onScreen() { - val tvp = thumbnailVp - if(tvp != null && doVideoPreview && videoReady){ - tvp.seekTo(lastPreviewPos) - tvp.start() + isPaused = false + } + + private fun startGrabberThread(){ + if(doVideoPreview){ + mp.setDataSource(path) + mp.setSurface(surface) + texView.surfaceTextureListener = this + mp.isLooping = true + mp.setOnPreparedListener { mp.start() } } } @@ -151,7 +147,7 @@ class VideoIcon(itemID:Int, private val vsh: VSH, private val path: String) : Vs } } - // TODO: Create a small video thumbnail and fix squared aspect ratio problem + // TODO: Create a small video thumbnail override val selectedIcon: Bitmap get() = getCurrentVideoBitmap(true) diff --git a/app/src/main/java/id/psw/vshlauncher/views/VshView.kt b/app/src/main/java/id/psw/vshlauncher/views/VshView.kt index 33bff0c..54fdbe9 100644 --- a/app/src/main/java/id/psw/vshlauncher/views/VshView.kt +++ b/app/src/main/java/id/psw/vshlauncher/views/VshView.kt @@ -25,7 +25,7 @@ import kotlin.math.roundToInt */ class VshView : View { - companion object{ + companion object { const val Deg2Rad = PI / 180f const val Rad2Deg = 180f / PI var padding = RectF(0f,0f,0f,0f) @@ -37,6 +37,11 @@ class VshView : View { // to launch option right in it's location var optionLaunchArea = RectF(0f,0f,0f,0f) var customTypeface = Typeface.SANS_SERIF + var deltaTime = 0.016f + var hideClock = false + var descriptionSeparator = false + var menuBackgroundColor = Color.argb(32,0,0,0) + val alphaColor = Color.argb(0,0,0,0) } /// region Variable @@ -309,6 +314,7 @@ class VshView : View { //TODO: update this function to support both PS3-style and PSP-style XMB clock private fun lClock(canvas: Canvas){ + if(hideClock) return if(clockExpandInfo != lastClockExpandInfo || lastWidth != width || lastHeight != height || expandInfoClipRect.isEmpty){ recalculateClockRect() } @@ -430,7 +436,7 @@ class VshView : View { val y = screenY + (icon.height / 2f) canvas.drawBitmap(icon, pivotX - (icon.width / 2f), screenY, iconPaint) if(data.hasDescription){ - if(isSelected && usePspStyle){ + if(isSelected && descriptionSeparator){ canvas.drawLine(x, y, width * 1f, y, paintStatusBoxOutline) } canvas.drawText(data.name, x, y - sd(2f), textPaint) @@ -522,6 +528,9 @@ class VshView : View { fun setOptionPopupVisibility(shown:Boolean){ if(isSelectionValid){ isOnOptions = shown && category[selectedX].items[selectedY].hasOptions + + // reset option position when the item opens up + if(isOnOptions) optionSelectedIndex = 0 } } @@ -530,7 +539,7 @@ class VshView : View { private fun mUpdate(){ selectedYf = 0.75f.toLerp(selectedY.toFloat(), selectedYf) selectedXf = 0.75f.toLerp(selectedX.toFloat(), selectedXf) - backgroundAlpha = 0.1f.toLerp(backgroundAlpha, hideMenu.choose(0f, 0.5f)) + backgroundAlpha = 0.1f.toLerp(backgroundAlpha, hideMenu.choose(0f, 1f)) frame++ @@ -548,6 +557,8 @@ class VshView : View { fpsRect.bottom = fpsRect.top + sd(25f) } + deltaTime = ms / 1000f + val fps = (1000f / ms).roundToInt() canvas.drawRoundRect(fpsRect, 10f,10f,paintStatusBoxFill) canvas.drawRoundRect(fpsRect, 10f,10f,paintStatusBoxOutline) @@ -664,7 +675,8 @@ class VshView : View { override fun onDraw(canvas: Canvas) { super.onDraw(canvas) mUpdate() - canvas.drawColor(Color.argb((255 * backgroundAlpha).floorToInt(),0,0,0)) + val backgroundColor = backgroundAlpha.toLerpColor(alphaColor, menuBackgroundColor) + canvas.drawColor(backgroundColor) if(!hideMenu){ lVerticalItems(canvas) lHorizontalMenu(canvas) diff --git a/app/src/main/res/layout/activity_xmbvp.xml b/app/src/main/res/layout/activity_xmbvp.xml index 2f67409..36b2f30 100644 --- a/app/src/main/res/layout/activity_xmbvp.xml +++ b/app/src/main/res/layout/activity_xmbvp.xml @@ -32,4 +32,5 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + \ No newline at end of file