From eece22750863ab0703c540dabdf2f5422a3dd6ce Mon Sep 17 00:00:00 2001 From: thyttan <6uuxstm66@mozmail.comā©> Date: Mon, 19 Sep 2022 20:31:25 +0200 Subject: [PATCH] Spotify remote initial release restructure menu Requires Gadgetbridge 71.0 fix textinput->search menu screenshots --- apps/spotrem/ChangeLog | 5 + apps/spotrem/README.md | 21 +++ apps/spotrem/app-icon.js | 1 + apps/spotrem/app.js | 244 +++++++++++++++++++++++++++++++++++ apps/spotrem/app.png | Bin 0 -> 1877 bytes apps/spotrem/metadata.json | 17 +++ apps/spotrem/screenshot1.png | Bin 0 -> 1816 bytes apps/spotrem/screenshot2.png | Bin 0 -> 3282 bytes 8 files changed, 288 insertions(+) create mode 100644 apps/spotrem/ChangeLog create mode 100644 apps/spotrem/README.md create mode 100644 apps/spotrem/app-icon.js create mode 100644 apps/spotrem/app.js create mode 100644 apps/spotrem/app.png create mode 100644 apps/spotrem/metadata.json create mode 100644 apps/spotrem/screenshot1.png create mode 100644 apps/spotrem/screenshot2.png diff --git a/apps/spotrem/ChangeLog b/apps/spotrem/ChangeLog new file mode 100644 index 000000000..8e3d8b652 --- /dev/null +++ b/apps/spotrem/ChangeLog @@ -0,0 +1,5 @@ +0.01: New app. +0.02: Restructure menu. +0.03: change handling of intent extras. +0.04: New layout. +0.05: Add widgets field. Tweak layout. diff --git a/apps/spotrem/README.md b/apps/spotrem/README.md new file mode 100644 index 000000000..346ec9eba --- /dev/null +++ b/apps/spotrem/README.md @@ -0,0 +1,21 @@ +Requires Gadgetbridge 71.0 or later. Allow intents in Gadgetbridge in order for this app to work. + +Touch input: + +Press the different ui elements to control Podcast Addict and open menus. Press left or right arrow to go to previous/next track. + +Swipe input: + +Swipe left/right to go to previous/next track. Swipe up/down to change the volume. + +It's possible to start tracks by searching with the remote. Search term without tags will override search with tags. + +To start playing 'from cold' the command for previous/next track via touch or swipe can be used. Pressing just play/pause is not guaranteed to initiate spotify in all circumstances (this will probably change with subsequent releases). + +In order to search to play or start music from the 'Saved' menu the Android device must be awake and unlocked. The remote can wake and unlock the device if the Bangle.js has been added as a 'trusted device' under Android Settings->Security->Smart Lock->Trusted devices. + +The swipe logic was inspired by the implementation in [rigrig](https://git.tubul.net/rigrig/)'s Scrolling Messages. + +Spotify Remote was created by [thyttan](https://github.com/thyttan/). + +Spotify icon by Icons8 diff --git a/apps/spotrem/app-icon.js b/apps/spotrem/app-icon.js new file mode 100644 index 000000000..8da55b9a5 --- /dev/null +++ b/apps/spotrem/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AFV3AAQVVDKQWHDB0HC5NwCyoYMCxZJKFxgwKCxowJC6xGOJBAWPGA4MGogXOIwdCmf/AAczkhIKC4VzCogAD+YZEC49PC5AABmgXO+czJYoYDC4gfCuRYGoUjDAZ4GUJlyn4XNukjIwMzmVHBAU/+YXKoZ0GmQLCDgQXIU5IVDC5JVCIwIECDA5HIR4hkBDAX0C5YAHOoIXJa4QRDoUikiOEm7vKE4YADmZ1FC5N/R48nC5tzFQMiokimYYHC4h4KJwX3Ow6QMOwoXGSAoAKIwrBNFxIXZJBxGHGB4WIGBouJDBgWLJJYWMDBIWODIwVRAH4AXA=")) diff --git a/apps/spotrem/app.js b/apps/spotrem/app.js new file mode 100644 index 000000000..2e68fb0fb --- /dev/null +++ b/apps/spotrem/app.js @@ -0,0 +1,244 @@ +/* +Bluetooth.println(JSON.stringify({t:"intent", action:"", flags:["flag1", "flag2",...], categories:["category1","category2",...], mimetype:"", data:"", package:"", class:"", target:"", extra:{someKey:"someValueOrString"}})); +*/ + +var R; +var backToMenu = false; +var isPaused = true; + +// The main layout of the app +function gfx() { + //Bangle.drawWidgets(); + R = Bangle.appRect; + marigin = 8; + // g.drawString(str, x, y, solid) + g.clearRect(R); + g.reset(); + + g.setFont("4x6:2"); + g.setFontAlign(1, 0, 0); + g.drawString("->", R.x2 - marigin, R.y + R.h/2); + + g.setFontAlign(-1, 0, 0); + g.drawString("<-", R.x + marigin, R.y + R.h/2); + + g.setFontAlign(-1, 0, 1); + g.drawString("<-", R.x + R.w/2, R.y + marigin); + + g.setFontAlign(1, 0, 1); + g.drawString("->", R.x + R.w/2, R.y2 - marigin); + + g.setFontAlign(0, 0, 0); + g.drawString("Play\nPause", R.x + R.w/2, R.y + R.h/2); + + g.setFontAlign(-1, -1, 0); + g.drawString("Menu", R.x + 2*marigin, R.y + 2*marigin); + + g.setFontAlign(-1, 1, 0); + g.drawString("Wake", R.x + 2*marigin, R.y + R.h - 2*marigin); + + g.setFontAlign(1, -1, 0); + g.drawString("Srch", R.x + R.w - 2*marigin, R.y + 2*marigin); + + g.setFontAlign(1, 1, 0); + g.drawString("Saved", R.x + R.w - 2*marigin, R.y + R.h - 2*marigin); +} + +// Touch handler for main layout +function touchHandler(_, xy) { + x = xy.x; + y = xy.y; + len = (R.wb-1 instead of a>b. + if ((R.x-1 { + if (ud) Bangle.musicControl(ud>0 ? "volumedown" : "volumeup"); + } + ); + Bangle.on("touch", touchHandler); + Bangle.on("swipe", swipeHandler); +} + + + +// Get back to the main layout +function backToGfx() { + E.showMenu(); + g.clear(); + g.reset(); + Bangle.removeAllListeners("touch"); + Bangle.removeAllListeners("swipe"); + setUI(); + gfx(); + backToMenu = false; +} + +/* +The functions for interacting with Android and the Spotify app +*/ + +simpleSearch = ""; +function simpleSearchTerm() { // input a simple search term without tags, overrides search with tags (artist and track) + require("textinput").input({text:simpleSearch}).then(result => {simpleSearch = result;}).then(() => {E.showMenu(searchMenu);}); +} + +artist = ""; +function artistSearchTerm() { // input artist to search for + require("textinput").input({text:artist}).then(result => {artist = result;}).then(() => {E.showMenu(searchMenu);}); +} + +track = ""; +function trackSearchTerm() { // input track to search for + require("textinput").input({text:track}).then(result => {track = result;}).then(() => {E.showMenu(searchMenu);}); +} + +album = ""; +function albumSearchTerm() { // input album to search for + require("textinput").input({text:album}).then(result => {album = result;}).then(() => {E.showMenu(searchMenu);}); +} + +function searchPlayWOTags() {//make a spotify search and play using entered terms + searchString = simpleSearch; + Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); +} + +function searchPlayWTags() {//make a spotify search and play using entered terms + searchString = (artist=="" ? "":("artist:\""+artist+"\"")) + ((artist!="" && track!="") ? " ":"") + (track=="" ? "":("track:\""+track+"\"")) + (((artist!="" && album!="") || (track!="" && album!="")) ? " ":"") + (album=="" ? "":(" album:\""+album+"\"")); + Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:searchString}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); +} + +function playVreden() {//Play the track "Vreden" by Sara Parkman via spotify uri-link + Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); +} + +function playVredenAlternate() {//Play the track "Vreden" by Sara Parkman via spotify uri-link + Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:track:5QEFFJ5tAeRlVquCUNpAJY:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK"]})); +} + +function searchPlayVreden() {//Play the track "Vreden" by Sara Parkman via search and play + Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'artist:"Sara Parkman" track:"Vreden"'}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); +} + +function openAlbum() {//Play EP "The Blue Room" by Coldplay + Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:album:3MVb2CWB36x7VwYo5sZmf2", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK"]})); +} + +function searchPlayAlbum() {//Play EP "The Blue Room" by Coldplay via search and play + Bluetooth.println(JSON.stringify({t:"intent", action:"android.media.action.MEDIA_PLAY_FROM_SEARCH", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", target:"activity", extra:{query:'album:"The blue room" artist:"Coldplay"', "android.intent.extra.focus":"vnd.android.cursor.item/album"}, flags:["FLAG_ACTIVITY_NEW_TASK"]})); +} + +function spotifyWidget(action) { + Bluetooth.println(JSON.stringify({t:"intent", action:("com.spotify.mobile.android.ui.widget."+action), package:"com.spotify.music", target:"broadcastreceiver"})); +} + +function gadgetbridgeWake() { + Bluetooth.println(JSON.stringify({t:"intent", target:"activity", flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_CLEAR_TASK", "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", "FLAG_ACTIVITY_NO_ANIMATION"], package:"gadgetbridge", class:"nodomain.freeyourgadget.gadgetbridge.activities.WakeActivity"})); +} + +function spotifyPlaylistDW() { + Bluetooth.println(JSON.stringify({t:"intent", action:"android.intent.action.VIEW", categories:["android.intent.category.DEFAULT"], package:"com.spotify.music", data:"spotify:user:spotify:playlist:37i9dQZEVXcRfaeEbxXIgb:play", target:"activity" , flags:["FLAG_ACTIVITY_NEW_TASK", "FLAG_ACTIVITY_NO_ANIMATION"/*, "FLAG_ACTIVITY_CLEAR_TOP", "FLAG_ACTIVITY_PREVIOUS_IS_TOP"*/]})); +} + +// Spotify Remote Menu +var spotifyMenu = { + "" : { title : " ", + back: backToGfx }, + "Controls" : ()=>{E.showMenu(controlMenu);}, + "Search and play" : ()=>{E.showMenu(searchMenu);}, + "Saved music" : ()=>{E.showMenu(savedMenu);}, + "Wake the android" : function() {gadgetbridgeWake();gadgetbridgeWake();}, + "Exit Spotify Remote" : ()=>{load();} +}; + + +var controlMenu = { + "" : { title : " ", + back: () => {if (backToMenu) E.showMenu(spotifyMenu); + if (!backToMenu) backToGfx();} }, + "Play" : ()=>{Bangle.musicControl("play");}, + "Pause" : ()=>{Bangle.musicControl("pause");}, + "Previous" : ()=>{spotifyWidget("PREVIOUS");}, + "Next" : ()=>{spotifyWidget("NEXT");}, + "Play (widget, next then previous)" : ()=>{spotifyWidget("NEXT"); spotifyWidget("PREVIOUS");}, + "Messages Music Controls" : ()=>{load("messagesmusic.app.js");}, +}; + +var searchMenu = { + "" : { title : " ", + back: () => {if (backToMenu) E.showMenu(spotifyMenu); + if (!backToMenu) backToGfx();} }, + "Search term w/o tags" : ()=>{simpleSearchTerm();}, + "Execute search and play w/o tags" : ()=>{searchPlayWOTags();}, + "Search term w tag \"artist\"" : ()=>{artistSearchTerm();}, + "Search term w tag \"track\"" : ()=>{trackSearchTerm();}, + "Search term w tag \"album\"" : ()=>{albumSearchTerm();}, + "Execute search and play with tags" : ()=>{searchPlayWTags();}, + "Play \"Vreden\" by Sara Parkman via uri-link" : ()=>{playVreden();}, + "Play \"Vreden\" by Sara Parkman via search&play" : ()=>{searchPlayVreden();}, + "Open \"The Blue Room\" EP (no autoplay)" : ()=>{openAlbum();}, + "Play \"The Blue Room\" EP via search&play" : ()=>{searchPlayAlbum();}, + "Play playlist Discover Weekly" : ()=>{spotifyPlaylistDW();}, +}; + +var savedMenu = { + "" : { title : " ", + back: () => {if (backToMenu) E.showMenu(spotifyMenu); + if (!backToMenu) backToGfx();} }, + "Play \"Vreden\" by Sara Parkman via uri-link" : ()=>{playVreden();}, + "Open \"The Blue Room\" EP (no autoplay)" : ()=>{openAlbum();}, + "Play \"The Blue Room\" EP via search&play" : ()=>{searchPlayAlbum();}, + "Play playlist Discover Weekly" : ()=>{spotifyPlaylistDW();}, +}; + +Bangle.loadWidgets(); +setUI(); +gfx(); diff --git a/apps/spotrem/app.png b/apps/spotrem/app.png new file mode 100644 index 0000000000000000000000000000000000000000..3c0d65eee994757cfcee51e2191164cc49f3179b GIT binary patch literal 1877 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 literal 0 HcmV?d00001 diff --git a/apps/spotrem/screenshot2.png b/apps/spotrem/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..7801a3034027861558ee960b030561276d578bbc GIT binary patch literal 3282 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 qw~cha>S{eO_ zTl(gA!A{;qL3?54gB$#AVp6WbCvq&5JNp^fwCA5~u_INpxVw`_gAoXQzlP7K{ZnX+ z6Ow*eu7R}7{c6mY*4X_iPnwn@Z4PZcW&lcPt|=6gPBrL};wr#iB{e@h-_^+j40&_@ z9q!a24TJwt^^J{fO@{6Dp#C{o^~NKD{^=1d+|9CROuW~*di7WjIEf81qs--4S)?Rf zQSY1ITMga3+PFFpm$QC4u@~m^p?2evwM9U0%cVu+e8gHwUq_R@)+CAd$52%J$b3-S z9=_n7g93b#UXnn8)~Uh8dd7EpY)MT$j_7I$$O^G+$(T|Yh7+&6ufOO%v~uDO939WB zb)4Ws7j$wNt6x|$lDva#_E2A^nB3phc&P@nMvFEszx=}8-=|~I)4(;UgSzl_^KcB! z9K-hJ)ekU7K^gqVU-LR~lKahD@4uGAQ7 zr;tQtcy%YF0+3o z#l16VU`hW{tq8l`$98=t4!*Ms(fwzjfa5BQN3_B=;jx^iK-FSl?W)R>%3_{{i@9P1OJZ literal 0 HcmV?d00001