Saturday, July 29, 2017

3.0 Work in Progress 6: Mini Player

This update mainly focuses on the addition of the mini player to the bottom of the library view (and will also be on other views where it makes sense) but I did also make some improvements in the play / pause animation.



Above shows the new mini player.  Just like every other view so far, the metadata portion will be customizable and you will be able to choose how many buttons are shown.   Right now you can choose between prev track, play/pause, and next track.  I may open it up to some of the other media actions like shuffle / repeat but i havent decided on that yet.

In the WIP4 post I had shown off a little chunk of the code that added a bunch of "behaviors" to a view.  The behaviors contain all the common code for a particular behavior of a UI view.  This allows me to rewrite the same (or very similar) chunks of code for different UIs.  GMMP 1/2.x was loaded with repeated code despite my best efforts to refactor.  Anyway, since the mini player shares a lot of the same abilities as now playing, I created 2 new behaviors: PlayingInfoBehavior and MediaButtonBehavior.

So here are the behaviors for the Library view:


view?.let {
            addBehavior(LifecycleBehavior::class, StatusBarBehavior(context, it))
            addBehavior(LifecycleBehavior::class, ToolbarBehavior(this@LibraryPagerPresenter, it))
            addBehavior(LifecycleBehavior::class, PlayingInfoBehavior(context, this@LibraryPagerPresenter, it, state))
            addBehavior(LifecycleBehavior::class, MediaButtonBehavior(context, this@LibraryPagerPresenter, it, state))
            addBehavior(FragmentContainerBehavior::class, FragmentContainerBehavior(it, state))
            addBehavior(MenuBehavior::class, HasMenuBehavior())
        }


I now set up the state of the library view for the above screenshot:


state.metadataLinesModel = MetadataLinesModel(theme).apply {
            addMetadataLine("<align=left><typeface=sans-serif><size=16>%tr%")
            addMetadataLine("<align=left><typeface=sans-serif><size=12><color=secondary>%ar%")
        }

state.buttonDefinitions = mapOf(R.id.miniPlayerPlayPause to
                                                MediaButtonDefinition(-1, R.drawable.ic_gm_pause_to_play, 0, MusicControlEvent.PLAY_PAUSE, theme.iconColorNormal),
                                        R.id.miniPlayerButton1 to
                                                MediaButtonDefinition(0, R.drawable.ic_gm_skip_previous, 0, MusicControlEvent.PREVIOUS_TRACK, theme.iconColorNormal, View.GONE),
                                        R.id.miniPlayerButton3 to
                                                MediaButtonDefinition(1, R.drawable.ic_gm_skip_next, 0, MusicControlEvent.NEXT_TRACK, theme.iconColorNormal, View.GONE))

The first part defines how the metadata is going to display and the second part defines the media buttons.  Tells it what icon to use, the size, the action that will happen on press, the color, and finally the visibility.  This is all "hardcoded" right now but will eventually hooked up to the preferences making it really easy to customize.


Small change in the code here to get the 3 buttons:


state.metadataLinesModel = MetadataLinesModel(theme).apply {
            addMetadataLine("<align=left><typeface=sans-serif><size=16>%tr%")
            addMetadataLine("<align=left><typeface=sans-serif><size=12><color=secondary>%ar%")
        }

state.buttonDefinitions = mapOf(R.id.miniPlayerPlayPause to
                                                MediaButtonDefinition(-1, R.drawable.ic_gm_pause_to_play, 0, MusicControlEvent.PLAY_PAUSE, theme.iconColorNormal, View.VISIBLE),
                                        R.id.miniPlayerButton1 to
                                                MediaButtonDefinition(0, R.drawable.ic_gm_skip_previous, 0, MusicControlEvent.PREVIOUS_TRACK, theme.iconColorNormal, View.VISIBLE),
                                        R.id.miniPlayerButton3 to
                                                MediaButtonDefinition(1, R.drawable.ic_gm_skip_next, 0, MusicControlEvent.NEXT_TRACK, theme.iconColorNormal, View.VISIBLE))


Now playing is now configured the same way:


override fun onViewAttached()
    {
        super.onViewAttached()

        view?.let {
            addBehavior(LifecycleBehavior::class, ToolbarBehavior(this@NowPlayingPresenter, it, false))
            addBehavior(LifecycleBehavior::class, PlayingInfoBehavior(context, this@NowPlayingPresenter, it, state))
            addBehavior(LifecycleBehavior::class, MediaButtonBehavior(context, this@NowPlayingPresenter, it, state))
        }
    }

    private fun createState()
    {
        //Read state
        val theme = GMThemeEngine.getInstance(context).currentTheme
        state.metadataLinesModel = MetadataLinesModel(theme).apply {
            addMetadataLine("<align=center><typeface=sans-serif-medium><size=24>%tr%")
            addMetadataLine("<align=center><typeface=sans-serif-medium><size=20>%ar%")
            addMetadataLine("<align=center><typeface=sans-serif-medium><size=20>%al%")
        }

        state.buttonDefinitions = mapOf(R.id.npPlayPause to MediaButtonDefinition(-1, R.drawable.ic_gm_pause_to_play_circle_outline, 0, MusicControlEvent.PLAY_PAUSE, theme.accentColor),
                                        R.id.npMediaBtn1 to MediaButtonDefinition(0, R.drawable.ic_gm_repeat, 0, MusicControlEvent.TOGGLE_REPEAT, theme.iconColorNormal),
                                        R.id.npMediaBtn2 to MediaButtonDefinition(1, R.drawable.ic_gm_skip_previous, 1, MusicControlEvent.PREVIOUS_TRACK, theme.iconColorNormal),
                                        R.id.npMediaBtn3 to MediaButtonDefinition(2, R.drawable.ic_gm_skip_next, 1, MusicControlEvent.NEXT_TRACK, theme.iconColorNormal),
                                        R.id.npMediaBtn4 to MediaButtonDefinition(3, R.drawable.ic_gm_shuffle, 0, MusicControlEvent.TOGGLE_SHUFFLE, theme.iconColorNormal))

    }

Here is a screenshot of the play pause in now playing using the icon from the mini player.  I plan on also adding the other play icon inside the filled circle (in addition to the current play with the circle outline.


Finally here is a video of everything in action.  The animations / transitions didnt seem to record as smooth as it appears on the device, so just keep that in mind.  I do most of my testing on a galaxy s3 running 7.1.2 and an original moto x running 5.1, so if things look smooth on those old devices they should be great on the newer ones.  This video also shows a bug with the 3 dot menu getting shifted to the left a bit when coming back to the library view.  I havent really looked into that much but it'll get fixed.. no worries.


I also want to note that when 3.0 is finally released I do think I will add 2 other options for how now playing looks.  One being the colored toolbar on top, with the album art underneath it taking up all the space between the toolbar and the metadata.  The other being smaller album art centered around a blurred background generated from the album art.  Some other players use that look and i really like it.  Plus it will be a compromise for current users of the holo themes with the blurred background

Sunday, July 16, 2017

3.0 Work in Progress 5: Library view

In this update I added a library view similar to the one in the old UI.  I also added in an artist list, genre list, and track list tabs.


The big difference with this one is the toolbar will collapse when you scroll down and if you scroll up it will become visible again


Here is a video of it in action


The plan with the library view is to allow the user to choose which views to add to the library (others will end up in the side navigation view if they are not in the library), the title of those views, and their order.  Below is the chunk of code responsible for building the library, its "hardcoded" currently but will be fairly simple to hook up to the preferences.

v.setupAdapter(listOf(BaseFragmentEntry("Artists", ArtistListFragment::class)
                                  , BaseFragmentEntry("Albums", AlbumListFragment::class)
                                  , BaseFragmentEntry("Genres", GenreListFragment::class)
                                  , BaseFragmentEntry("Songs", TrackListFragment::class)))

I am sure many of you are anxious to try out the new UI, but it is still a ways off.  Some of the views are not too straight forward to code like the equalizer and the browser / folder view, so I expect they will take some time to code.  My plan is to have a working alpha by this fall.  The alpha however will not really contain much customization.  Its really easy to add customization to the code, however actually creating UIs to configure everything is going to take a long time.  Because of this, the alpha will be through a separate group in google+ that users will have to join.  I didnt want to push something so incomplete to the beta channel